From d1c09f2561f75a911f044245c96ad2e862444c6c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 9 Nov 2018 19:34:55 -0500 Subject: [PATCH 001/548] parse a TDB2 file --- .gitignore | 2 + Cargo.lock | 468 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 9 + src/main.rs | 103 +++++++++++ src/nibbler.rs | 292 ++++++++++++++++++++++++++++++ 5 files changed, 874 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/nibbler.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..53eaa2196 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..81866a154 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,468 @@ +[[package]] +name = "aho-corasick" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clicolors-control" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "console" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "clicolors-control 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "indicatif" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "console 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lock_api" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "owning_ref" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rask" +version = "0.1.0" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "console 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "indicatif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stable_deref_trait" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termios" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ucd-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "utf8-ranges" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum clicolors-control 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f84dec9bc083ce2503908cd305af98bd363da6f54bf8d4bf0ac14ee749ad5d1" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum console 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd48adf136733979b49e15bc3b4c43cc0d3c85ece7bd08e6daa414c6fcb13e6" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum indicatif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a29b2fa6f00010c268bface64c18bb0310aaa70d46a195d5382d288c477fb016" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" +"checksum lock_api 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775751a3e69bde4df9b38dd00a1b5d6ac13791e4223d4a0506577f0dd27cfb7a" +"checksum memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0a3eb002f0535929f1199681417029ebea04aadc0c7a4224b46be99c7f5d6a16" +"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +"checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5" +"checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c" +"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" +"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" +"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" +"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" +"checksum regex 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ee84f70c8c08744ea9641a731c7fadb475bf2ecc52d7f627feb833e0b3990467" +"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +"checksum regex-syntax 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fbc557aac2b708fe84121caf261346cc2eed71978024337e42eb46b8a252ac6e" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "153ffa32fd170e9944f7e0838edf824a754ec4c1fc64746fcc9fe1f8fa602e5d" +"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum ucd-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d0f8bfa9ff0cadcd210129ad9d2c5f145c13e9ced3d3e5d948a6213487d52444" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..39089eff2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rask" +version = "0.1.0" +authors = ["Dustin J. Mitchell "] + +[dependencies] +clap = "2.32" +console = "0.6.2" +indicatif = "0.9.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 000000000..169c99a71 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,103 @@ +mod nibbler; + +use std::str; + +use std::io::{stdin, BufRead, Result, Error, ErrorKind}; +use nibbler::Nibbler; + +/// Decode the given byte slice into a string using Taskwarrior JSON's escaping The slice is +/// assumed to be ASCII; unicode escapes within it will be expanded. +// TODO: return Cow +fn json_decode(value: &[u8]) -> String { + let length = value.len(); + let mut rv = String::with_capacity(length); + + let mut pos = 0; + while pos < length { + let v = value[pos]; + if v == b'\\' { + pos += 1; + if pos == length { + rv.push(v as char); + break; + } + let v = value[pos]; + match v { + b'"' | b'\\' | b'/' => rv.push(v as char), + b'b' => rv.push(8 as char), + b'f' => rv.push(12 as char), + b'n' => rv.push('\n' as char), + b'r' => rv.push('\r' as char), + b't' => rv.push('\t' as char), + b'u' => panic!("omg please no"), + _ => { + rv.push(b'\\' as char); + rv.push(v as char); + } + } + } else { + rv.push(v as char) + } + pos += 1; + } + + rv +} + +fn decode(value: String) -> String { + if let Some(_) = value.find('&') { + return value.replace("&open;", "[").replace("&close;", "]"); + } + value +} + +fn parse_ff4(line: &str) -> Result<()> { + let mut nib = Nibbler::new(line.as_bytes()); + println!("{}", line); + + if !nib.skip(b'[') { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + if let Some(line) = nib.get_until(b']') { + let mut nib = Nibbler::new(line); + while !nib.depleted() { + if let Some(name) = nib.get_until(b':') { + if !nib.skip(b':') { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + if let Some(value) = nib.get_quoted(b'"') { + let value = json_decode(value); + let value = decode(value); + println!("{}={}", str::from_utf8(name).unwrap(), value); + } else { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + nib.skip(b' '); + } else { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + } + } else { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + if !nib.skip(b']') { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + if !nib.depleted() { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + Ok(()) +} + +fn parse_tdb2() -> Result<()> { + let input = stdin(); + for line in input.lock().lines() { + parse_ff4(&line?)?; + } + Ok(()) +} + +fn main() { + parse_tdb2().unwrap(); + println!("Done"); +} diff --git a/src/nibbler.rs b/src/nibbler.rs new file mode 100644 index 000000000..dbc3df920 --- /dev/null +++ b/src/nibbler.rs @@ -0,0 +1,292 @@ +pub struct Nibbler<'a> { + input: &'a [u8], + cursor: usize, +} + +impl<'a> Nibbler<'a> { + pub fn new(input: &'a [u8]) -> Self { + Nibbler { + input: input, + cursor: 0, + } + } + + pub fn get_until(&mut self, c: u8) -> Option<&'a [u8]> { + if self.cursor >= self.input.len() { + return None; + } + + let mut i = self.cursor; + while i < self.input.len() { + if self.input[i] == c { + let rv = &self.input[self.cursor..i]; + self.cursor = i; + return Some(rv); + } + i += 1; + } + let rv = &self.input[self.cursor..]; + self.cursor = self.input.len(); + Some(rv) + } + + // TODO: get_until_str + // TODO: get_until_one_of + // TODO: get_until_ws + + pub fn get_until_eos(&mut self) -> Option<&'a [u8]> { + if self.cursor >= self.input.len() { + return None; + } + let rv = &self.input[self.cursor..]; + self.cursor = self.input.len(); + return Some(rv); + } + + // TODO: get_n + + pub fn get_quoted(&mut self, c: u8) -> Option<&'a [u8]> { + let length = self.input.len(); + if self.cursor >= length || self.input[self.cursor] != c { + return None; + } + + let start = self.cursor + 1; + let mut i = start; + + while i < length { + while i < length && self.input[i] != c { + i += 1 + } + if i == length { + // unclosed quote + return None; + } + if i == start { + return Some(&self.input[i..i]); + } + + if self.input[i - 1] == b'\\' { + // work backward looking for escaped backslashes + let mut j = i - 2; + let mut quote = true; + while j >= start && self.input[j] == b'\\' { + quote = !quote; + j -= 1; + } + + if quote { + i += 1; + continue; + } + } + + // none of the above matched, so we are at the end + self.cursor = i + 1; + return Some(&self.input[start..i]); + } + + unreachable!(); + } + + // TODO: (missing funcs) + + pub fn skip_n(&mut self, n: usize) -> bool { + let length = self.input.len(); + if self.cursor < length && self.cursor + n <= length { + self.cursor += n; + return true; + } + + return false; + } + + pub fn skip(&mut self, c: u8) -> bool { + if self.cursor < self.input.len() && self.input[self.cursor] == c { + self.cursor += 1; + return true; + } + false + } + + // TODO: skip_all_one_of + // TODO: skip_ws + + pub fn next(&mut self) -> Option { + if self.cursor >= self.input.len() { + return None; + } + let rv = self.input[self.cursor]; + self.cursor += 1; + return Some(rv); + } + + // TODO: next_n + // TODO: cursor + // TODO: save + // TODO: restore + + pub fn depleted(&self) -> bool { + self.cursor >= self.input.len() + } +} + +#[cfg(test)] +mod test { + use super::Nibbler; + + #[test] + fn test_get_until() { + let s = b"abc:123"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_until(b':'), Some(&s[..3])); + } + + #[test] + fn test_get_until_empty() { + let s = b"abc:123"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_until(b'a'), Some(&s[..0])); + } + + #[test] + fn test_get_until_not_found() { + let s = b"abc:123"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_until(b'/'), Some(&s[..])); + } + + #[test] + fn test_get_until_eos() { + let s = b"abc:123"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_until_eos(), Some(&s[..])); + } + + #[test] + fn test_get_quoted() { + let s = b"'abcd'efg"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_quoted(b'\''), Some(&s[1..5])); + assert_eq!(nib.next(), Some(b'e')); + } + + #[test] + fn test_get_quoted_unopened() { + let s = b"abcd'efg"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_quoted(b'\''), None); + assert_eq!(nib.next(), Some(b'a')); // nothing consumed + } + + #[test] + fn test_get_quoted_unclosed() { + let s = b"'abcdefg"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_quoted(b'\''), None); + assert_eq!(nib.next(), Some(b'\'')); // nothing consumed + } + + #[test] + fn test_get_quoted_escaped() { + let s = b"'abc\\'de'fg"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_quoted(b'\''), Some(&s[1..8])); + assert_eq!(nib.next(), Some(b'f')); + } + + #[test] + fn test_get_quoted_double_escaped() { + let s = b"'abc\\\\'de'fg"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_quoted(b'\''), Some(&s[1..6])); + assert_eq!(nib.next(), Some(b'd')); + } + + #[test] + fn test_get_quoted_triple_escaped() { + let s = b"'abc\\\\\\'de'fg"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_quoted(b'\''), Some(&s[1..10])); + assert_eq!(nib.next(), Some(b'f')); + } + + #[test] + fn test_get_quoted_all_escapes() { + let s = b"'\\\\\\'\\\\'fg"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.get_quoted(b'\''), Some(&s[1..7])); + assert_eq!(nib.next(), Some(b'f')); + } + + #[test] + fn test_skip_n() { + let s = b"abc:123"; + let mut nib = Nibbler::new(s); + assert!(nib.skip_n(3)); + assert_eq!(nib.get_until_eos(), Some(&s[3..])); + } + + #[test] + fn test_skip_n_too_long() { + let s = b"abc:123"; + let mut nib = Nibbler::new(s); + assert!(!nib.skip_n(33)); + // nothing is consumed + assert_eq!(nib.get_until_eos(), Some(&s[..])); + } + + #[test] + fn test_skip_n_exact_eos() { + let s = b"abc:123"; + let mut nib = Nibbler::new(s); + assert!(nib.skip_n(7)); + assert_eq!(nib.get_until_eos(), None); + } + + #[test] + fn test_skip_match() { + let s = b"foo"; + let mut nib = Nibbler::new(s); + assert!(nib.skip(b'f')); + assert_eq!(nib.get_until_eos(), Some(&s[1..])); + } + + #[test] + fn test_skip_no_match() { + let s = b"foo"; + let mut nib = Nibbler::new(s); + assert!(!nib.skip(b'x')); + assert_eq!(nib.get_until_eos(), Some(&s[..])); + } + + #[test] + fn test_skip_eos() { + let s = b"foo"; + let mut nib = Nibbler::new(s); + assert!(nib.skip_n(3)); + assert!(!nib.skip(b'x')); + } + + #[test] + fn test_next() { + let s = b"foo"; + let mut nib = Nibbler::new(s); + assert_eq!(nib.next(), Some(b'f')); + assert_eq!(nib.next(), Some(b'o')); + assert_eq!(nib.next(), Some(b'o')); + assert_eq!(nib.next(), None); + assert_eq!(nib.next(), None); + } + + #[test] + fn test_depleted() { + let s = b"xy"; + let mut nib = Nibbler::new(s); + assert!(!nib.depleted()); + assert_eq!(nib.next(), Some(b'x')); + assert!(!nib.depleted()); + assert_eq!(nib.next(), Some(b'y')); + assert!(nib.depleted()); + } +} From de5e4e134c65999f2ddb417d289804bb87be6019 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 9 Nov 2018 20:48:29 -0500 Subject: [PATCH 002/548] parse into tasks --- src/main.rs | 107 ++---------------- src/task.rs | 17 +++ src/tdb2/ff4.rs | 223 ++++++++++++++++++++++++++++++++++++++ src/tdb2/mod.rs | 14 +++ src/{ => tdb2}/nibbler.rs | 3 + 5 files changed, 265 insertions(+), 99 deletions(-) create mode 100644 src/task.rs create mode 100644 src/tdb2/ff4.rs create mode 100644 src/tdb2/mod.rs rename src/{ => tdb2}/nibbler.rs (98%) diff --git a/src/main.rs b/src/main.rs index 169c99a71..af111278e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,103 +1,12 @@ -mod nibbler; +mod tdb2; +mod task; -use std::str; - -use std::io::{stdin, BufRead, Result, Error, ErrorKind}; -use nibbler::Nibbler; - -/// Decode the given byte slice into a string using Taskwarrior JSON's escaping The slice is -/// assumed to be ASCII; unicode escapes within it will be expanded. -// TODO: return Cow -fn json_decode(value: &[u8]) -> String { - let length = value.len(); - let mut rv = String::with_capacity(length); - - let mut pos = 0; - while pos < length { - let v = value[pos]; - if v == b'\\' { - pos += 1; - if pos == length { - rv.push(v as char); - break; - } - let v = value[pos]; - match v { - b'"' | b'\\' | b'/' => rv.push(v as char), - b'b' => rv.push(8 as char), - b'f' => rv.push(12 as char), - b'n' => rv.push('\n' as char), - b'r' => rv.push('\r' as char), - b't' => rv.push('\t' as char), - b'u' => panic!("omg please no"), - _ => { - rv.push(b'\\' as char); - rv.push(v as char); - } - } - } else { - rv.push(v as char) - } - pos += 1; - } - - rv -} - -fn decode(value: String) -> String { - if let Some(_) = value.find('&') { - return value.replace("&open;", "[").replace("&close;", "]"); - } - value -} - -fn parse_ff4(line: &str) -> Result<()> { - let mut nib = Nibbler::new(line.as_bytes()); - println!("{}", line); - - if !nib.skip(b'[') { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - if let Some(line) = nib.get_until(b']') { - let mut nib = Nibbler::new(line); - while !nib.depleted() { - if let Some(name) = nib.get_until(b':') { - if !nib.skip(b':') { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - if let Some(value) = nib.get_quoted(b'"') { - let value = json_decode(value); - let value = decode(value); - println!("{}={}", str::from_utf8(name).unwrap(), value); - } else { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - nib.skip(b' '); - } else { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - } - } else { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - if !nib.skip(b']') { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - if !nib.depleted() { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - Ok(()) -} - -fn parse_tdb2() -> Result<()> { - let input = stdin(); - for line in input.lock().lines() { - parse_ff4(&line?)?; - } - Ok(()) -} +use tdb2::parse; +use std::io::stdin; fn main() { - parse_tdb2().unwrap(); - println!("Done"); + let input = stdin(); + parse(input.lock()).unwrap().iter().for_each(|t| { + println!("{:?}", t); + }); } diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 000000000..4e1085588 --- /dev/null +++ b/src/task.rs @@ -0,0 +1,17 @@ +use std::collections::HashMap; + +#[derive(Debug)] +pub struct Task { + data: HashMap, +} + +impl Task { + /// Construct a Task from a hashmap containing named properties + pub fn from_data(data: HashMap) -> Self { + Self { data } + } + + pub fn description(&self) -> &str { + self.data.get("description").unwrap() + } +} diff --git a/src/tdb2/ff4.rs b/src/tdb2/ff4.rs new file mode 100644 index 000000000..69c9a81cd --- /dev/null +++ b/src/tdb2/ff4.rs @@ -0,0 +1,223 @@ +use std::str; +use std::io::{Result, Error, ErrorKind}; +use std::collections::HashMap; + +use super::nibbler::Nibbler; +use super::super::task::Task; + +/// Rust implementation of part of utf8_codepoint from Taskwarrior's src/utf8.cpp +/// +/// Note that the original function will return garbage for invalid hex sequences; +/// this panics instead. +fn hex_to_unicode(value: &[u8]) -> String { + if value.len() < 4 { + panic!(format!("unicode escape too short -- {:?}", value)); + } + + fn nyb(c: u8) -> u16 { + match c { + b'0'...b'9' => (c - b'0') as u16, + b'a'...b'f' => (c - b'a' + 10) as u16, + b'A'...b'F' => (c - b'A' + 10) as u16, + _ => panic!(format!("invalid hex character {:?}", c)), + } + }; + + let words = [ + nyb(value[0]) << 12 | nyb(value[1]) << 8 | nyb(value[2]) << 4 | nyb(value[3]), + ]; + return String::from_utf16(&words[..]).unwrap(); +} + +/// Rust implementation of JSON::decode in Taskwarrior's src/JSON.cpp +/// +/// Decode the given byte slice into a string using Taskwarrior JSON's escaping The slice is +/// assumed to be ASCII; unicode escapes within it will be expanded. +fn json_decode(value: &[u8]) -> String { + let length = value.len(); + let mut rv = String::with_capacity(length); + + let mut pos = 0; + while pos < length { + let v = value[pos]; + if v == b'\\' { + pos += 1; + if pos == length { + rv.push(v as char); + break; + } + let v = value[pos]; + match v { + b'"' | b'\\' | b'/' => rv.push(v as char), + b'b' => rv.push(8 as char), + b'f' => rv.push(12 as char), + b'n' => rv.push('\n' as char), + b'r' => rv.push('\r' as char), + b't' => rv.push('\t' as char), + b'u' => { + rv.push_str(&hex_to_unicode(&value[pos + 1..])); + pos += 4; + } + _ => { + rv.push(b'\\' as char); + rv.push(v as char); + } + } + } else { + rv.push(v as char) + } + pos += 1; + } + + rv +} + +/// Rust implementation of Task::decode in Taskwarrior's src/Task.cpp +/// +/// Note that the docstring for the C++ function does not match the +/// implementation! +fn decode(value: String) -> String { + if let Some(_) = value.find('&') { + return value.replace("&open;", "[").replace("&close;", "]"); + } + value +} + +/// Parse an "FF4" formatted task line. From Task::parse in Taskwarrior's src/Task.cpp. +/// +/// While Taskwarrior supports additional formats, this is the only format supported by rask. +pub(super) fn parse_ff4(line: &str) -> Result { + let mut nib = Nibbler::new(line.as_bytes()); + let mut data = HashMap::new(); + + if !nib.skip(b'[') { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + if let Some(line) = nib.get_until(b']') { + let mut nib = Nibbler::new(line); + while !nib.depleted() { + if let Some(name) = nib.get_until(b':') { + if !nib.skip(b':') { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + if let Some(value) = nib.get_quoted(b'"') { + let value = json_decode(value); + let value = decode(value); + data.insert(String::from_utf8(name.to_vec()).unwrap(), value); + } else { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + nib.skip(b' '); + } else { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + } + } else { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + if !nib.skip(b']') { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + if !nib.depleted() { + return Err(Error::new(ErrorKind::Other, "bad line")); + } + Ok(Task::from_data(data)) +} + +#[cfg(test)] +mod test { + use super::{decode, json_decode, hex_to_unicode, parse_ff4}; + + #[test] + fn test_hex_to_unicode_digits() { + assert_eq!(hex_to_unicode(b"1234"), "\u{1234}"); + } + + #[test] + fn test_hex_to_unicode_lower() { + assert_eq!(hex_to_unicode(b"abcd"), "\u{abcd}"); + } + + #[test] + fn test_hex_to_unicode_upper() { + assert_eq!(hex_to_unicode(b"ABCD"), "\u{abcd}"); + } + + #[test] + fn test_json_decode_no_change() { + assert_eq!(json_decode(b"abcd"), "abcd"); + } + + #[test] + fn test_json_decode_escape_quote() { + assert_eq!(json_decode(b"ab\\\"cd"), "ab\"cd"); + } + + #[test] + fn test_json_decode_escape_backslash() { + assert_eq!(json_decode(b"ab\\\\cd"), "ab\\cd"); + } + + #[test] + fn test_json_decode_escape_frontslash() { + assert_eq!(json_decode(b"ab\\/cd"), "ab/cd"); + } + + #[test] + fn test_json_decode_escape_b() { + assert_eq!(json_decode(b"ab\\bcd"), "ab\x08cd"); + } + + #[test] + fn test_json_decode_escape_f() { + assert_eq!(json_decode(b"ab\\fcd"), "ab\x0ccd"); + } + + #[test] + fn test_json_decode_escape_n() { + assert_eq!(json_decode(b"ab\\ncd"), "ab\ncd"); + } + + #[test] + fn test_json_decode_escape_r() { + assert_eq!(json_decode(b"ab\\rcd"), "ab\rcd"); + } + + #[test] + fn test_json_decode_escape_t() { + assert_eq!(json_decode(b"ab\\tcd"), "ab\tcd"); + } + + #[test] + fn test_json_decode_escape_other() { + assert_eq!(json_decode(b"ab\\xcd"), "ab\\xcd"); + } + + #[test] + fn test_json_decode_escape_eos() { + assert_eq!(json_decode(b"ab\\"), "ab\\"); + } + + #[test] + fn test_json_decode_escape_unicode() { + assert_eq!(json_decode(b"ab\\u1234"), "ab\u{1234}"); + } + + #[test] + fn test_decode_no_change() { + let s = "abcd " efgh &".to_string(); + assert_eq!(decode(s.clone()), s); + } + + #[test] + fn test_decode_multi() { + let s = "abcd &open; efgh &close; &open".to_string(); + assert_eq!(decode(s), "abcd [ efgh ] &open".to_string()); + } + + #[test] + fn test_parse_ff4() { + let task = parse_ff4("[description:\"desc\"]").unwrap(); + assert_eq!(task.description(), "desc"); + } +} diff --git a/src/tdb2/mod.rs b/src/tdb2/mod.rs new file mode 100644 index 000000000..ac15032bb --- /dev/null +++ b/src/tdb2/mod.rs @@ -0,0 +1,14 @@ +mod nibbler; +mod ff4; + +use std::io::{BufRead, Result}; +use super::task::Task; +use self::ff4::parse_ff4; + +pub(super) fn parse(reader: impl BufRead) -> Result> { + let mut tasks = vec![]; + for line in reader.lines() { + tasks.push(parse_ff4(&line?)?); + } + Ok(tasks) +} diff --git a/src/nibbler.rs b/src/tdb2/nibbler.rs similarity index 98% rename from src/nibbler.rs rename to src/tdb2/nibbler.rs index dbc3df920..57e4fe136 100644 --- a/src/nibbler.rs +++ b/src/tdb2/nibbler.rs @@ -1,3 +1,6 @@ +//! A minimal implementation of the "Nibbler" parsing utility from the Taskwarrior +//! source. + pub struct Nibbler<'a> { input: &'a [u8], cursor: usize, From 6a66b7a84ba69931f1033e960e4cf7d4dab2c819 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 10 Nov 2018 19:34:23 -0500 Subject: [PATCH 003/548] parse all defined fields in tasks --- Cargo.lock | 426 +++------------------------------------- Cargo.toml | 8 +- src/main.rs | 3 + src/task.rs | 17 -- src/task/mod.rs | 7 + src/task/task.rs | 55 ++++++ src/task/taskbuilder.rs | 194 ++++++++++++++++++ src/tdb2/ff4.rs | 19 +- 8 files changed, 298 insertions(+), 431 deletions(-) delete mode 100644 src/task.rs create mode 100644 src/task/mod.rs create mode 100644 src/task/task.rs create mode 100644 src/task/taskbuilder.rs diff --git a/Cargo.lock b/Cargo.lock index 81866a154..664e462ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,217 +1,37 @@ [[package]] -name = "aho-corasick" -version = "0.6.9" +name = "chrono" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "atty" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bitflags" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cfg-if" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "clap" -version = "2.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "clicolors-control" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "console" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "clicolors-control 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "indicatif" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "console 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lazy_static" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazy_static" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "libc" version = "0.2.43" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "lock_api" -version = "0.1.4" +name = "num-integer" +version = "0.1.39" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "memchr" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "owning_ref" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lock_api 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.0" +name = "num-traits" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rask" version = "0.1.0" dependencies = [ - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "console 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "indicatif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -220,172 +40,18 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "redox_termios" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex-syntax" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ucd-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex-syntax" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ucd-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "scopeguard" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "smallvec" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "stable_deref_trait" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "strsim" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "termion" -version = "1.5.1" +name = "time" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "termios" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "textwrap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ucd-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicode-width" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "utf8-ranges" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "vec_map" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.2.8" +name = "uuid" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -397,11 +63,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -413,56 +74,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" -"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" -"checksum clicolors-control 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f84dec9bc083ce2503908cd305af98bd363da6f54bf8d4bf0ac14ee749ad5d1" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum console 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd48adf136733979b49e15bc3b4c43cc0d3c85ece7bd08e6daa414c6fcb13e6" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum indicatif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a29b2fa6f00010c268bface64c18bb0310aaa70d46a195d5382d288c477fb016" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" -"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" -"checksum lock_api 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775751a3e69bde4df9b38dd00a1b5d6ac13791e4223d4a0506577f0dd27cfb7a" -"checksum memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0a3eb002f0535929f1199681417029ebea04aadc0c7a4224b46be99c7f5d6a16" -"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" -"checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5" -"checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c" -"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" -"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" -"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" +"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" +"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" -"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" -"checksum regex 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ee84f70c8c08744ea9641a731c7fadb475bf2ecc52d7f627feb833e0b3990467" -"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" -"checksum regex-syntax 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fbc557aac2b708fe84121caf261346cc2eed71978024337e42eb46b8a252ac6e" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "153ffa32fd170e9944f7e0838edf824a754ec4c1fc64746fcc9fe1f8fa602e5d" -"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" -"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" -"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625" -"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" -"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum ucd-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d0f8bfa9ff0cadcd210129ad9d2c5f145c13e9ced3d3e5d948a6213487d52444" -"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" -"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" -"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" +"checksum uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dab5c5526c5caa3d106653401a267fed923e7046f35895ffcb5ca42db64942e6" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 39089eff2..8f9fe62a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" authors = ["Dustin J. Mitchell "] [dependencies] -clap = "2.32" -console = "0.6.2" -indicatif = "0.9.0" +#clap = "2.32" +#console = "0.6.2" +#indicatif = "0.9.0" +uuid = "0.7" +chrono = "0.4" diff --git a/src/main.rs b/src/main.rs index af111278e..161b8513f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +extern crate chrono; +extern crate uuid; + mod tdb2; mod task; diff --git a/src/task.rs b/src/task.rs deleted file mode 100644 index 4e1085588..000000000 --- a/src/task.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::collections::HashMap; - -#[derive(Debug)] -pub struct Task { - data: HashMap, -} - -impl Task { - /// Construct a Task from a hashmap containing named properties - pub fn from_data(data: HashMap) -> Self { - Self { data } - } - - pub fn description(&self) -> &str { - self.data.get("description").unwrap() - } -} diff --git a/src/task/mod.rs b/src/task/mod.rs new file mode 100644 index 000000000..6587458ba --- /dev/null +++ b/src/task/mod.rs @@ -0,0 +1,7 @@ +mod task; +mod taskbuilder; + +pub use self::taskbuilder::TaskBuilder; +pub use self::task::{Task, Priority, Status, Timestamp, Annotation}; +pub use self::task::Priority::*; +pub use self::task::Status::*; diff --git a/src/task/task.rs b/src/task/task.rs new file mode 100644 index 000000000..29e8f4925 --- /dev/null +++ b/src/task/task.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; +use uuid::Uuid; +use chrono::prelude::*; + +pub type Timestamp = DateTime; + +#[derive(Debug, PartialEq)] +pub enum Priority { + L, + M, + H, +} + +#[derive(Debug, PartialEq)] +pub enum Status { + Pending, + Completed, + Deleted, + Recurring, + Waiting, +} + +#[derive(Debug, PartialEq)] +pub struct Annotation { + pub entry: Timestamp, + pub description: String, +} + +/// A task, the fundamental business object of this tool. +/// +/// This structure is based on https://taskwarrior.org/docs/design/task.html +#[derive(Debug)] +pub struct Task { + pub status: Status, + pub uuid: Uuid, + pub entry: Timestamp, + pub description: String, + pub start: Option, + pub end: Option, + pub due: Option, + pub until: Option, + pub wait: Option, + pub modified: Timestamp, + pub scheduled: Option, + pub recur: Option, + pub mask: Option, + pub imask: Option, + pub parent: Option, + pub project: Option, + pub priority: Option, + pub depends: Vec, + pub tags: Vec, + pub annotations: Vec, + pub udas: HashMap, +} diff --git a/src/task/taskbuilder.rs b/src/task/taskbuilder.rs new file mode 100644 index 000000000..c2a8d84ca --- /dev/null +++ b/src/task/taskbuilder.rs @@ -0,0 +1,194 @@ +use std::collections::HashMap; +use chrono::prelude::*; +use std::str; +use uuid::Uuid; +use task::{Task, Priority, Status, Timestamp, Annotation}; + +#[derive(Default)] +pub struct TaskBuilder { + status: Option, + uuid: Option, + entry: Option, + description: Option, + start: Option, + end: Option, + due: Option, + until: Option, + wait: Option, + modified: Option, + scheduled: Option, + recur: Option, + mask: Option, + imask: Option, + parent: Option, + project: Option, + priority: Option, + depends: Vec, + tags: Vec, + annotations: Vec, + udas: HashMap, +} + +/// Parse an "integer", allowing for occasional integers with trailing decimal zeroes +fn parse_int(value: &str) -> Result::Err> +where + T: str::FromStr, +{ + // some integers are rendered with following decimal zeroes + if let Some(i) = value.find('.') { + let mut nonzero = false; + for c in value[i + 1..].chars() { + if c != '0' { + nonzero = true; + break; + } + } + if !nonzero { + return value[..i].parse(); + } + } + value.parse() +} + +/// Parse a status into a Status enum value +fn parse_status(value: &str) -> Result { + match value { + "pending" => Ok(Status::Pending), + "completed" => Ok(Status::Completed), + "deleted" => Ok(Status::Deleted), + "recurring" => Ok(Status::Recurring), + "waiting" => Ok(Status::Waiting), + _ => Err(format!("invalid status {}", value)), + } +} + +/// Parse "L", "M", "H" into the Priority enum + +fn parse_priority(value: &str) -> Result { + match value { + "L" => Ok(Priority::L), + "M" => Ok(Priority::M), + "H" => Ok(Priority::H), + _ => Err(format!("invalid priority {}", value)), + } +} + +/// Parse a UNIX timestamp into a UTC DateTime +fn parse_timestamp(value: &str) -> Result::Err> { + Ok(Utc.timestamp(parse_int::(value)?, 0)) +} + +/// Parse depends, as a list of ,-separated UUIDs +fn parse_depends(value: &str) -> Result, uuid::parser::ParseError> { + value.split(',').map(|s| Uuid::parse_str(s)).collect() +} + +/// Parse tags, as a list of ,-separated strings +fn parse_tags(value: &str) -> Vec { + value.split(',').map(|s| s.to_string()).collect() +} + +impl TaskBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn set(mut self, name: &str, value: String) -> Self { + const ANNOTATION_PREFIX: &str = "annotation_"; + if name.starts_with(ANNOTATION_PREFIX) { + let entry = parse_timestamp(&name[ANNOTATION_PREFIX.len()..]).unwrap(); + self.annotations.push(Annotation { + entry, + description: value.to_string(), + }); + return self; + } + match name { + "status" => self.status = Some(parse_status(&value).unwrap()), + "uuid" => self.uuid = Some(Uuid::parse_str(&value).unwrap()), + "entry" => self.entry = Some(parse_timestamp(&value).unwrap()), + "description" => self.description = Some(value), + "start" => self.start = Some(parse_timestamp(&value).unwrap()), + "end" => self.end = Some(parse_timestamp(&value).unwrap()), + "due" => self.due = Some(parse_timestamp(&value).unwrap()), + "until" => self.until = Some(parse_timestamp(&value).unwrap()), + "wait" => self.wait = Some(parse_timestamp(&value).unwrap()), + "modified" => self.modified = Some(parse_timestamp(&value).unwrap()), + "scheduled" => self.scheduled = Some(parse_timestamp(&value).unwrap()), + "recur" => self.recur = Some(value), + "mask" => self.mask = Some(value), + "imask" => self.imask = Some(parse_int::(&value).unwrap()), + "parent" => self.uuid = Some(Uuid::parse_str(&value).unwrap()), + "project" => self.project = Some(value), + "priority" => self.priority = Some(parse_priority(&value).unwrap()), + "depends" => self.depends = parse_depends(&value).unwrap(), + "tags" => self.tags = parse_tags(&value), + _ => { + self.udas.insert(name.to_string(), value); + } + } + self + } + + pub fn finish(self) -> Task { + Task { + status: self.status.unwrap(), + uuid: self.uuid.unwrap(), + description: self.description.unwrap(), + entry: self.entry.unwrap(), + start: self.start, + end: self.end, + due: self.due, + until: self.until, + wait: self.wait, + modified: self.modified.unwrap(), + scheduled: self.scheduled, + recur: self.recur, + mask: self.mask, + imask: self.imask, + parent: self.parent, + project: self.project, + priority: self.priority, + depends: self.depends, + tags: self.tags, + annotations: self.annotations, + udas: self.udas, + } + + // TODO: check validity per https://taskwarrior.org/docs/design/task.html + } +} + +#[cfg(test)] +mod test { + use super::{parse_int, parse_depends}; + use uuid::Uuid; + + #[test] + fn test_parse_int() { + assert_eq!(parse_int::("123").unwrap(), 123u8); + assert_eq!(parse_int::("123000000").unwrap(), 123000000u32); + assert_eq!(parse_int::("-123000000").unwrap(), -123000000i32); + } + + #[test] + fn test_parse_int_decimals() { + assert_eq!(parse_int::("123.00").unwrap(), 123u8); + assert_eq!(parse_int::("123.0000").unwrap(), 123u32); + assert_eq!(parse_int::("-123.").unwrap(), -123i32); + } + + #[test] + fn test_parse_depends() { + let u1 = "123e4567-e89b-12d3-a456-426655440000"; + let u2 = "123e4567-e89b-12d3-a456-999999990000"; + assert_eq!( + parse_depends(u1).unwrap(), + vec![Uuid::parse_str(u1).unwrap()] + ); + assert_eq!( + parse_depends(&format!("{},{}", u1, u2)).unwrap(), + vec![Uuid::parse_str(u1).unwrap(), Uuid::parse_str(u2).unwrap()] + ); + } +} diff --git a/src/tdb2/ff4.rs b/src/tdb2/ff4.rs index 69c9a81cd..a30078a67 100644 --- a/src/tdb2/ff4.rs +++ b/src/tdb2/ff4.rs @@ -1,9 +1,8 @@ use std::str; use std::io::{Result, Error, ErrorKind}; -use std::collections::HashMap; use super::nibbler::Nibbler; -use super::super::task::Task; +use task::{TaskBuilder, Task}; /// Rust implementation of part of utf8_codepoint from Taskwarrior's src/utf8.cpp /// @@ -88,7 +87,7 @@ fn decode(value: String) -> String { /// While Taskwarrior supports additional formats, this is the only format supported by rask. pub(super) fn parse_ff4(line: &str) -> Result { let mut nib = Nibbler::new(line.as_bytes()); - let mut data = HashMap::new(); + let mut builder = TaskBuilder::new(); if !nib.skip(b'[') { return Err(Error::new(ErrorKind::Other, "bad line")); @@ -97,13 +96,14 @@ pub(super) fn parse_ff4(line: &str) -> Result { let mut nib = Nibbler::new(line); while !nib.depleted() { if let Some(name) = nib.get_until(b':') { + let name = str::from_utf8(name).unwrap(); if !nib.skip(b':') { return Err(Error::new(ErrorKind::Other, "bad line")); } if let Some(value) = nib.get_quoted(b'"') { let value = json_decode(value); let value = decode(value); - data.insert(String::from_utf8(name.to_vec()).unwrap(), value); + builder = builder.set(name, value); } else { return Err(Error::new(ErrorKind::Other, "bad line")); } @@ -121,12 +121,13 @@ pub(super) fn parse_ff4(line: &str) -> Result { if !nib.depleted() { return Err(Error::new(ErrorKind::Other, "bad line")); } - Ok(Task::from_data(data)) + Ok(builder.finish()) } #[cfg(test)] mod test { use super::{decode, json_decode, hex_to_unicode, parse_ff4}; + use task::Pending; #[test] fn test_hex_to_unicode_digits() { @@ -217,7 +218,11 @@ mod test { #[test] fn test_parse_ff4() { - let task = parse_ff4("[description:\"desc\"]").unwrap(); - assert_eq!(task.description(), "desc"); + let s = "[description:\"desc\" entry:\"1437855511\" modified:\"1479480556\" \ + priority:\"L\" project:\"lists\" status:\"pending\" tags:\"watch\" \ + uuid:\"83ce989e-8634-4d62-841c-eb309383ff1f\"]"; + let task = parse_ff4(s).unwrap(); + assert_eq!(task.status, Pending); + assert_eq!(task.description, "desc"); } } From f9d950e62154de2373ee636da80442bcf474671e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 10 Nov 2018 19:46:32 -0500 Subject: [PATCH 004/548] rename Nibbler to Pig as per latest code in Taskwarrior --- src/tdb2/ff4.rs | 24 +++--- src/tdb2/mod.rs | 2 +- src/tdb2/{nibbler.rs => pig.rs} | 128 ++++++++++++++++---------------- 3 files changed, 77 insertions(+), 77 deletions(-) rename src/tdb2/{nibbler.rs => pig.rs} (63%) diff --git a/src/tdb2/ff4.rs b/src/tdb2/ff4.rs index a30078a67..8800b503f 100644 --- a/src/tdb2/ff4.rs +++ b/src/tdb2/ff4.rs @@ -1,7 +1,7 @@ use std::str; use std::io::{Result, Error, ErrorKind}; -use super::nibbler::Nibbler; +use super::pig::Pig; use task::{TaskBuilder, Task}; /// Rust implementation of part of utf8_codepoint from Taskwarrior's src/utf8.cpp @@ -86,28 +86,28 @@ fn decode(value: String) -> String { /// /// While Taskwarrior supports additional formats, this is the only format supported by rask. pub(super) fn parse_ff4(line: &str) -> Result { - let mut nib = Nibbler::new(line.as_bytes()); + let mut pig = Pig::new(line.as_bytes()); let mut builder = TaskBuilder::new(); - if !nib.skip(b'[') { + if !pig.skip(b'[') { return Err(Error::new(ErrorKind::Other, "bad line")); } - if let Some(line) = nib.get_until(b']') { - let mut nib = Nibbler::new(line); - while !nib.depleted() { - if let Some(name) = nib.get_until(b':') { + if let Some(line) = pig.get_until(b']') { + let mut pig = Pig::new(line); + while !pig.depleted() { + if let Some(name) = pig.get_until(b':') { let name = str::from_utf8(name).unwrap(); - if !nib.skip(b':') { + if !pig.skip(b':') { return Err(Error::new(ErrorKind::Other, "bad line")); } - if let Some(value) = nib.get_quoted(b'"') { + if let Some(value) = pig.get_quoted(b'"') { let value = json_decode(value); let value = decode(value); builder = builder.set(name, value); } else { return Err(Error::new(ErrorKind::Other, "bad line")); } - nib.skip(b' '); + pig.skip(b' '); } else { return Err(Error::new(ErrorKind::Other, "bad line")); } @@ -115,10 +115,10 @@ pub(super) fn parse_ff4(line: &str) -> Result { } else { return Err(Error::new(ErrorKind::Other, "bad line")); } - if !nib.skip(b']') { + if !pig.skip(b']') { return Err(Error::new(ErrorKind::Other, "bad line")); } - if !nib.depleted() { + if !pig.depleted() { return Err(Error::new(ErrorKind::Other, "bad line")); } Ok(builder.finish()) diff --git a/src/tdb2/mod.rs b/src/tdb2/mod.rs index ac15032bb..54252bfac 100644 --- a/src/tdb2/mod.rs +++ b/src/tdb2/mod.rs @@ -1,4 +1,4 @@ -mod nibbler; +mod pig; mod ff4; use std::io::{BufRead, Result}; diff --git a/src/tdb2/nibbler.rs b/src/tdb2/pig.rs similarity index 63% rename from src/tdb2/nibbler.rs rename to src/tdb2/pig.rs index 57e4fe136..7a86c8a96 100644 --- a/src/tdb2/nibbler.rs +++ b/src/tdb2/pig.rs @@ -1,14 +1,14 @@ -//! A minimal implementation of the "Nibbler" parsing utility from the Taskwarrior +//! A minimal implementation of the "Pig" parsing utility from the Taskwarrior //! source. -pub struct Nibbler<'a> { +pub struct Pig<'a> { input: &'a [u8], cursor: usize, } -impl<'a> Nibbler<'a> { +impl<'a> Pig<'a> { pub fn new(input: &'a [u8]) -> Self { - Nibbler { + Pig { input: input, cursor: 0, } @@ -136,160 +136,160 @@ impl<'a> Nibbler<'a> { #[cfg(test)] mod test { - use super::Nibbler; + use super::Pig; #[test] fn test_get_until() { let s = b"abc:123"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_until(b':'), Some(&s[..3])); + let mut pig = Pig::new(s); + assert_eq!(pig.get_until(b':'), Some(&s[..3])); } #[test] fn test_get_until_empty() { let s = b"abc:123"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_until(b'a'), Some(&s[..0])); + let mut pig = Pig::new(s); + assert_eq!(pig.get_until(b'a'), Some(&s[..0])); } #[test] fn test_get_until_not_found() { let s = b"abc:123"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_until(b'/'), Some(&s[..])); + let mut pig = Pig::new(s); + assert_eq!(pig.get_until(b'/'), Some(&s[..])); } #[test] fn test_get_until_eos() { let s = b"abc:123"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_until_eos(), Some(&s[..])); + let mut pig = Pig::new(s); + assert_eq!(pig.get_until_eos(), Some(&s[..])); } #[test] fn test_get_quoted() { let s = b"'abcd'efg"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_quoted(b'\''), Some(&s[1..5])); - assert_eq!(nib.next(), Some(b'e')); + let mut pig = Pig::new(s); + assert_eq!(pig.get_quoted(b'\''), Some(&s[1..5])); + assert_eq!(pig.next(), Some(b'e')); } #[test] fn test_get_quoted_unopened() { let s = b"abcd'efg"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_quoted(b'\''), None); - assert_eq!(nib.next(), Some(b'a')); // nothing consumed + let mut pig = Pig::new(s); + assert_eq!(pig.get_quoted(b'\''), None); + assert_eq!(pig.next(), Some(b'a')); // nothing consumed } #[test] fn test_get_quoted_unclosed() { let s = b"'abcdefg"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_quoted(b'\''), None); - assert_eq!(nib.next(), Some(b'\'')); // nothing consumed + let mut pig = Pig::new(s); + assert_eq!(pig.get_quoted(b'\''), None); + assert_eq!(pig.next(), Some(b'\'')); // nothing consumed } #[test] fn test_get_quoted_escaped() { let s = b"'abc\\'de'fg"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_quoted(b'\''), Some(&s[1..8])); - assert_eq!(nib.next(), Some(b'f')); + let mut pig = Pig::new(s); + assert_eq!(pig.get_quoted(b'\''), Some(&s[1..8])); + assert_eq!(pig.next(), Some(b'f')); } #[test] fn test_get_quoted_double_escaped() { let s = b"'abc\\\\'de'fg"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_quoted(b'\''), Some(&s[1..6])); - assert_eq!(nib.next(), Some(b'd')); + let mut pig = Pig::new(s); + assert_eq!(pig.get_quoted(b'\''), Some(&s[1..6])); + assert_eq!(pig.next(), Some(b'd')); } #[test] fn test_get_quoted_triple_escaped() { let s = b"'abc\\\\\\'de'fg"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_quoted(b'\''), Some(&s[1..10])); - assert_eq!(nib.next(), Some(b'f')); + let mut pig = Pig::new(s); + assert_eq!(pig.get_quoted(b'\''), Some(&s[1..10])); + assert_eq!(pig.next(), Some(b'f')); } #[test] fn test_get_quoted_all_escapes() { let s = b"'\\\\\\'\\\\'fg"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.get_quoted(b'\''), Some(&s[1..7])); - assert_eq!(nib.next(), Some(b'f')); + let mut pig = Pig::new(s); + assert_eq!(pig.get_quoted(b'\''), Some(&s[1..7])); + assert_eq!(pig.next(), Some(b'f')); } #[test] fn test_skip_n() { let s = b"abc:123"; - let mut nib = Nibbler::new(s); - assert!(nib.skip_n(3)); - assert_eq!(nib.get_until_eos(), Some(&s[3..])); + let mut pig = Pig::new(s); + assert!(pig.skip_n(3)); + assert_eq!(pig.get_until_eos(), Some(&s[3..])); } #[test] fn test_skip_n_too_long() { let s = b"abc:123"; - let mut nib = Nibbler::new(s); - assert!(!nib.skip_n(33)); + let mut pig = Pig::new(s); + assert!(!pig.skip_n(33)); // nothing is consumed - assert_eq!(nib.get_until_eos(), Some(&s[..])); + assert_eq!(pig.get_until_eos(), Some(&s[..])); } #[test] fn test_skip_n_exact_eos() { let s = b"abc:123"; - let mut nib = Nibbler::new(s); - assert!(nib.skip_n(7)); - assert_eq!(nib.get_until_eos(), None); + let mut pig = Pig::new(s); + assert!(pig.skip_n(7)); + assert_eq!(pig.get_until_eos(), None); } #[test] fn test_skip_match() { let s = b"foo"; - let mut nib = Nibbler::new(s); - assert!(nib.skip(b'f')); - assert_eq!(nib.get_until_eos(), Some(&s[1..])); + let mut pig = Pig::new(s); + assert!(pig.skip(b'f')); + assert_eq!(pig.get_until_eos(), Some(&s[1..])); } #[test] fn test_skip_no_match() { let s = b"foo"; - let mut nib = Nibbler::new(s); - assert!(!nib.skip(b'x')); - assert_eq!(nib.get_until_eos(), Some(&s[..])); + let mut pig = Pig::new(s); + assert!(!pig.skip(b'x')); + assert_eq!(pig.get_until_eos(), Some(&s[..])); } #[test] fn test_skip_eos() { let s = b"foo"; - let mut nib = Nibbler::new(s); - assert!(nib.skip_n(3)); - assert!(!nib.skip(b'x')); + let mut pig = Pig::new(s); + assert!(pig.skip_n(3)); + assert!(!pig.skip(b'x')); } #[test] fn test_next() { let s = b"foo"; - let mut nib = Nibbler::new(s); - assert_eq!(nib.next(), Some(b'f')); - assert_eq!(nib.next(), Some(b'o')); - assert_eq!(nib.next(), Some(b'o')); - assert_eq!(nib.next(), None); - assert_eq!(nib.next(), None); + let mut pig = Pig::new(s); + assert_eq!(pig.next(), Some(b'f')); + assert_eq!(pig.next(), Some(b'o')); + assert_eq!(pig.next(), Some(b'o')); + assert_eq!(pig.next(), None); + assert_eq!(pig.next(), None); } #[test] fn test_depleted() { let s = b"xy"; - let mut nib = Nibbler::new(s); - assert!(!nib.depleted()); - assert_eq!(nib.next(), Some(b'x')); - assert!(!nib.depleted()); - assert_eq!(nib.next(), Some(b'y')); - assert!(nib.depleted()); + let mut pig = Pig::new(s); + assert!(!pig.depleted()); + assert_eq!(pig.next(), Some(b'x')); + assert!(!pig.depleted()); + assert_eq!(pig.next(), Some(b'y')); + assert!(pig.depleted()); } } From 1272acb893c99c87b98685b8cdc7570e179958f7 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 11 Nov 2018 21:09:49 -0500 Subject: [PATCH 005/548] Use error_chain --- Cargo.lock | 51 +++++++++++++++++++ Cargo.toml | 1 + src/errors.rs | 9 ++++ src/main.rs | 12 ++++- src/tdb2/ff4.rs | 133 ++++++++++++++++++++++++++++-------------------- src/tdb2/mod.rs | 5 +- src/tdb2/pig.rs | 31 ++++++----- 7 files changed, 169 insertions(+), 73 deletions(-) create mode 100644 src/errors.rs diff --git a/Cargo.lock b/Cargo.lock index 664e462ef..fc2361e68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,34 @@ +[[package]] +name = "backtrace" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "chrono" version = "0.4.6" @@ -8,6 +39,14 @@ dependencies = [ "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "error-chain" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libc" version = "0.2.43" @@ -31,6 +70,7 @@ name = "rask" version = "0.1.0" dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -39,6 +79,11 @@ name = "redox_syscall" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustc-demangle" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "time" version = "0.1.40" @@ -74,11 +119,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] +"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a" +"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" +"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" +"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" +"checksum error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07e791d3be96241c77c43846b665ef1384606da2cd2a48730abe606a12906e02" "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" "checksum uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dab5c5526c5caa3d106653401a267fed923e7046f35895ffcb5ca42db64942e6" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" diff --git a/Cargo.toml b/Cargo.toml index 8f9fe62a7..739b8c05a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ authors = ["Dustin J. Mitchell "] #indicatif = "0.9.0" uuid = "0.7" chrono = "0.4" +error-chain = "0.12.0" diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 000000000..5debda75a --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,9 @@ +error_chain!{ + foreign_links { + Io(::std::io::Error); + StrFromUtf8(::std::str::Utf8Error); + StringFromUtf8(::std::string::FromUtf8Error); + StringFromUtf16(::std::string::FromUtf16Error); + } + +} diff --git a/src/main.rs b/src/main.rs index 161b8513f..f8b3f6f82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,23 @@ extern crate chrono; extern crate uuid; +#[macro_use] +extern crate error_chain; mod tdb2; mod task; +mod errors; use tdb2::parse; use std::io::stdin; -fn main() { +use errors::*; + +quick_main!(run); + +fn run() -> Result<()> { let input = stdin(); - parse(input.lock()).unwrap().iter().for_each(|t| { + parse(input.lock())?.iter().for_each(|t| { println!("{:?}", t); }); + Ok(()) } diff --git a/src/tdb2/ff4.rs b/src/tdb2/ff4.rs index 8800b503f..63e353e73 100644 --- a/src/tdb2/ff4.rs +++ b/src/tdb2/ff4.rs @@ -1,38 +1,38 @@ use std::str; -use std::io::{Result, Error, ErrorKind}; use super::pig::Pig; use task::{TaskBuilder, Task}; +use errors::*; /// Rust implementation of part of utf8_codepoint from Taskwarrior's src/utf8.cpp /// /// Note that the original function will return garbage for invalid hex sequences; /// this panics instead. -fn hex_to_unicode(value: &[u8]) -> String { +fn hex_to_unicode(value: &[u8]) -> Result { if value.len() < 4 { - panic!(format!("unicode escape too short -- {:?}", value)); + bail!(format!("too short")); } - fn nyb(c: u8) -> u16 { + fn nyb(c: u8) -> Result { match c { - b'0'...b'9' => (c - b'0') as u16, - b'a'...b'f' => (c - b'a' + 10) as u16, - b'A'...b'F' => (c - b'A' + 10) as u16, - _ => panic!(format!("invalid hex character {:?}", c)), + b'0'...b'9' => Ok((c - b'0') as u16), + b'a'...b'f' => Ok((c - b'a' + 10) as u16), + b'A'...b'F' => Ok((c - b'A' + 10) as u16), + _ => bail!("invalid hex character"), } }; let words = [ - nyb(value[0]) << 12 | nyb(value[1]) << 8 | nyb(value[2]) << 4 | nyb(value[3]), + nyb(value[0])? << 12 | nyb(value[1])? << 8 | nyb(value[2])? << 4 | nyb(value[3])?, ]; - return String::from_utf16(&words[..]).unwrap(); + Ok(String::from_utf16(&words[..])?) } /// Rust implementation of JSON::decode in Taskwarrior's src/JSON.cpp /// /// Decode the given byte slice into a string using Taskwarrior JSON's escaping The slice is /// assumed to be ASCII; unicode escapes within it will be expanded. -fn json_decode(value: &[u8]) -> String { +fn json_decode(value: &[u8]) -> Result { let length = value.len(); let mut rv = String::with_capacity(length); @@ -54,7 +54,14 @@ fn json_decode(value: &[u8]) -> String { b'r' => rv.push('\r' as char), b't' => rv.push('\t' as char), b'u' => { - rv.push_str(&hex_to_unicode(&value[pos + 1..])); + let unicode = hex_to_unicode(&value[pos + 1..pos + 5]).chain_err(|| { + let esc = &value[pos - 1..pos + 5]; + match str::from_utf8(esc) { + Ok(s) => format!("invalid unicode escape `{}`", s), + Err(_) => format!("invalid unicode escape bytes {:?}", esc), + } + })?; + rv.push_str(&unicode); pos += 4; } _ => { @@ -68,7 +75,7 @@ fn json_decode(value: &[u8]) -> String { pos += 1; } - rv + Ok(rv) } /// Rust implementation of Task::decode in Taskwarrior's src/Task.cpp @@ -89,37 +96,25 @@ pub(super) fn parse_ff4(line: &str) -> Result { let mut pig = Pig::new(line.as_bytes()); let mut builder = TaskBuilder::new(); - if !pig.skip(b'[') { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - if let Some(line) = pig.get_until(b']') { - let mut pig = Pig::new(line); - while !pig.depleted() { - if let Some(name) = pig.get_until(b':') { - let name = str::from_utf8(name).unwrap(); - if !pig.skip(b':') { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - if let Some(value) = pig.get_quoted(b'"') { - let value = json_decode(value); - let value = decode(value); - builder = builder.set(name, value); - } else { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - pig.skip(b' '); - } else { - return Err(Error::new(ErrorKind::Other, "bad line")); - } + pig.skip(b'[')?; + let line = pig.get_until(b']')?; + let mut subpig = Pig::new(line); + while !subpig.depleted() { + let name = subpig.get_until(b':')?; + let name = str::from_utf8(name)?; + subpig.skip(b':')?; + if let Some(value) = subpig.get_quoted(b'"') { + let value = json_decode(value)?; + let value = decode(value); + builder = builder.set(name, value); + } else { + bail!("bad line 3"); } - } else { - return Err(Error::new(ErrorKind::Other, "bad line")); - } - if !pig.skip(b']') { - return Err(Error::new(ErrorKind::Other, "bad line")); + subpig.skip(b' ').ok(); // ignore if not found.. } + pig.skip(b']')?; if !pig.depleted() { - return Err(Error::new(ErrorKind::Other, "bad line")); + bail!("bad line 5"); } Ok(builder.finish()) } @@ -131,77 +126,96 @@ mod test { #[test] fn test_hex_to_unicode_digits() { - assert_eq!(hex_to_unicode(b"1234"), "\u{1234}"); + assert_eq!(hex_to_unicode(b"1234").unwrap(), "\u{1234}"); } #[test] fn test_hex_to_unicode_lower() { - assert_eq!(hex_to_unicode(b"abcd"), "\u{abcd}"); + assert_eq!(hex_to_unicode(b"abcd").unwrap(), "\u{abcd}"); } #[test] fn test_hex_to_unicode_upper() { - assert_eq!(hex_to_unicode(b"ABCD"), "\u{abcd}"); + assert_eq!(hex_to_unicode(b"ABCD").unwrap(), "\u{abcd}"); + } + + #[test] + fn test_hex_to_unicode_too_short() { + assert!(hex_to_unicode(b"AB").is_err()); + } + + #[test] + fn test_hex_to_unicode_invalid() { + assert!(hex_to_unicode(b"defg").is_err()); } #[test] fn test_json_decode_no_change() { - assert_eq!(json_decode(b"abcd"), "abcd"); + assert_eq!(json_decode(b"abcd").unwrap(), "abcd"); } #[test] fn test_json_decode_escape_quote() { - assert_eq!(json_decode(b"ab\\\"cd"), "ab\"cd"); + assert_eq!(json_decode(b"ab\\\"cd").unwrap(), "ab\"cd"); } #[test] fn test_json_decode_escape_backslash() { - assert_eq!(json_decode(b"ab\\\\cd"), "ab\\cd"); + assert_eq!(json_decode(b"ab\\\\cd").unwrap(), "ab\\cd"); } #[test] fn test_json_decode_escape_frontslash() { - assert_eq!(json_decode(b"ab\\/cd"), "ab/cd"); + assert_eq!(json_decode(b"ab\\/cd").unwrap(), "ab/cd"); } #[test] fn test_json_decode_escape_b() { - assert_eq!(json_decode(b"ab\\bcd"), "ab\x08cd"); + assert_eq!(json_decode(b"ab\\bcd").unwrap(), "ab\x08cd"); } #[test] fn test_json_decode_escape_f() { - assert_eq!(json_decode(b"ab\\fcd"), "ab\x0ccd"); + assert_eq!(json_decode(b"ab\\fcd").unwrap(), "ab\x0ccd"); } #[test] fn test_json_decode_escape_n() { - assert_eq!(json_decode(b"ab\\ncd"), "ab\ncd"); + assert_eq!(json_decode(b"ab\\ncd").unwrap(), "ab\ncd"); } #[test] fn test_json_decode_escape_r() { - assert_eq!(json_decode(b"ab\\rcd"), "ab\rcd"); + assert_eq!(json_decode(b"ab\\rcd").unwrap(), "ab\rcd"); } #[test] fn test_json_decode_escape_t() { - assert_eq!(json_decode(b"ab\\tcd"), "ab\tcd"); + assert_eq!(json_decode(b"ab\\tcd").unwrap(), "ab\tcd"); } #[test] fn test_json_decode_escape_other() { - assert_eq!(json_decode(b"ab\\xcd"), "ab\\xcd"); + assert_eq!(json_decode(b"ab\\xcd").unwrap(), "ab\\xcd"); } #[test] fn test_json_decode_escape_eos() { - assert_eq!(json_decode(b"ab\\"), "ab\\"); + assert_eq!(json_decode(b"ab\\").unwrap(), "ab\\"); } #[test] fn test_json_decode_escape_unicode() { - assert_eq!(json_decode(b"ab\\u1234"), "ab\u{1234}"); + assert_eq!(json_decode(b"ab\\u1234").unwrap(), "ab\u{1234}"); + } + + #[test] + fn test_json_decode_escape_unicode_bad() { + let rv = json_decode(b"ab\\uwxyz"); + assert_eq!( + rv.unwrap_err().to_string(), + "invalid unicode escape `\\uwxyz`" + ); } #[test] @@ -225,4 +239,11 @@ mod test { assert_eq!(task.status, Pending); assert_eq!(task.description, "desc"); } + + #[test] + fn test_parse_ff4_fail() { + assert!(parse_ff4("abc:10]").is_err()); + assert!(parse_ff4("[abc:10").is_err()); + assert!(parse_ff4("[abc:10 123:123]").is_err()); + } } diff --git a/src/tdb2/mod.rs b/src/tdb2/mod.rs index 54252bfac..0e29d4c0b 100644 --- a/src/tdb2/mod.rs +++ b/src/tdb2/mod.rs @@ -1,9 +1,10 @@ mod pig; mod ff4; -use std::io::{BufRead, Result}; -use super::task::Task; +use std::io::BufRead; +use task::Task; use self::ff4::parse_ff4; +use errors::*; pub(super) fn parse(reader: impl BufRead) -> Result> { let mut tasks = vec![]; diff --git a/src/tdb2/pig.rs b/src/tdb2/pig.rs index 7a86c8a96..16319ccba 100644 --- a/src/tdb2/pig.rs +++ b/src/tdb2/pig.rs @@ -1,6 +1,8 @@ //! A minimal implementation of the "Pig" parsing utility from the Taskwarrior //! source. +use errors::*; + pub struct Pig<'a> { input: &'a [u8], cursor: usize, @@ -14,9 +16,9 @@ impl<'a> Pig<'a> { } } - pub fn get_until(&mut self, c: u8) -> Option<&'a [u8]> { + pub fn get_until(&mut self, c: u8) -> Result<&'a [u8]> { if self.cursor >= self.input.len() { - return None; + return Err(Error::from("input truncated")); } let mut i = self.cursor; @@ -24,13 +26,13 @@ impl<'a> Pig<'a> { if self.input[i] == c { let rv = &self.input[self.cursor..i]; self.cursor = i; - return Some(rv); + return Ok(rv); } i += 1; } let rv = &self.input[self.cursor..]; self.cursor = self.input.len(); - Some(rv) + Ok(rv) } // TODO: get_until_str @@ -104,12 +106,15 @@ impl<'a> Pig<'a> { return false; } - pub fn skip(&mut self, c: u8) -> bool { + pub fn skip(&mut self, c: u8) -> Result<()> { if self.cursor < self.input.len() && self.input[self.cursor] == c { self.cursor += 1; - return true; + return Ok(()); } - false + bail!(format!( + "expected character `{}`", + String::from_utf8(vec![c])? + )); } // TODO: skip_all_one_of @@ -142,21 +147,21 @@ mod test { fn test_get_until() { let s = b"abc:123"; let mut pig = Pig::new(s); - assert_eq!(pig.get_until(b':'), Some(&s[..3])); + assert_eq!(pig.get_until(b':').unwrap(), &s[..3]); } #[test] fn test_get_until_empty() { let s = b"abc:123"; let mut pig = Pig::new(s); - assert_eq!(pig.get_until(b'a'), Some(&s[..0])); + assert_eq!(pig.get_until(b'a').unwrap(), &s[..0]); } #[test] fn test_get_until_not_found() { let s = b"abc:123"; let mut pig = Pig::new(s); - assert_eq!(pig.get_until(b'/'), Some(&s[..])); + assert_eq!(pig.get_until(b'/').unwrap(), &s[..]); } #[test] @@ -251,7 +256,7 @@ mod test { fn test_skip_match() { let s = b"foo"; let mut pig = Pig::new(s); - assert!(pig.skip(b'f')); + assert!(pig.skip(b'f').is_ok()); assert_eq!(pig.get_until_eos(), Some(&s[1..])); } @@ -259,7 +264,7 @@ mod test { fn test_skip_no_match() { let s = b"foo"; let mut pig = Pig::new(s); - assert!(!pig.skip(b'x')); + assert!(pig.skip(b'x').is_err()); assert_eq!(pig.get_until_eos(), Some(&s[..])); } @@ -268,7 +273,7 @@ mod test { let s = b"foo"; let mut pig = Pig::new(s); assert!(pig.skip_n(3)); - assert!(!pig.skip(b'x')); + assert!(pig.skip(b'x').is_err()); } #[test] From 7fd94f64952fac79d3563db1bce19512ae5cf353 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 12 Nov 2018 09:39:59 -0500 Subject: [PATCH 006/548] Remove unused methods from Pig --- src/tdb2/ff4.rs | 13 ++--- src/tdb2/pig.rs | 151 +++++++++--------------------------------------- 2 files changed, 32 insertions(+), 132 deletions(-) diff --git a/src/tdb2/ff4.rs b/src/tdb2/ff4.rs index 63e353e73..f51edc826 100644 --- a/src/tdb2/ff4.rs +++ b/src/tdb2/ff4.rs @@ -103,18 +103,15 @@ pub(super) fn parse_ff4(line: &str) -> Result { let name = subpig.get_until(b':')?; let name = str::from_utf8(name)?; subpig.skip(b':')?; - if let Some(value) = subpig.get_quoted(b'"') { - let value = json_decode(value)?; - let value = decode(value); - builder = builder.set(name, value); - } else { - bail!("bad line 3"); - } + let value = subpig.get_quoted(b'"')?; + let value = json_decode(value)?; + let value = decode(value); + builder = builder.set(name, value); subpig.skip(b' ').ok(); // ignore if not found.. } pig.skip(b']')?; if !pig.depleted() { - bail!("bad line 5"); + bail!("trailing characters on line"); } Ok(builder.finish()) } diff --git a/src/tdb2/pig.rs b/src/tdb2/pig.rs index 16319ccba..a8995a80e 100644 --- a/src/tdb2/pig.rs +++ b/src/tdb2/pig.rs @@ -1,5 +1,5 @@ //! A minimal implementation of the "Pig" parsing utility from the Taskwarrior -//! source. +//! source. This is just enough to parse FF4 lines. use errors::*; @@ -35,25 +35,12 @@ impl<'a> Pig<'a> { Ok(rv) } - // TODO: get_until_str - // TODO: get_until_one_of - // TODO: get_until_ws - - pub fn get_until_eos(&mut self) -> Option<&'a [u8]> { - if self.cursor >= self.input.len() { - return None; - } - let rv = &self.input[self.cursor..]; - self.cursor = self.input.len(); - return Some(rv); - } - - // TODO: get_n - - pub fn get_quoted(&mut self, c: u8) -> Option<&'a [u8]> { + pub fn get_quoted(&mut self, c: u8) -> Result<&'a [u8]> { let length = self.input.len(); if self.cursor >= length || self.input[self.cursor] != c { - return None; + return Err(Error::from( + "quoted string does not begin with quote character", + )); } let start = self.cursor + 1; @@ -64,11 +51,10 @@ impl<'a> Pig<'a> { i += 1 } if i == length { - // unclosed quote - return None; + return Err(Error::from("unclosed quote")); } if i == start { - return Some(&self.input[i..i]); + return Ok(&self.input[i..i]); } if self.input[i - 1] == b'\\' { @@ -88,24 +74,12 @@ impl<'a> Pig<'a> { // none of the above matched, so we are at the end self.cursor = i + 1; - return Some(&self.input[start..i]); + return Ok(&self.input[start..i]); } unreachable!(); } - // TODO: (missing funcs) - - pub fn skip_n(&mut self, n: usize) -> bool { - let length = self.input.len(); - if self.cursor < length && self.cursor + n <= length { - self.cursor += n; - return true; - } - - return false; - } - pub fn skip(&mut self, c: u8) -> Result<()> { if self.cursor < self.input.len() && self.input[self.cursor] == c { self.cursor += 1; @@ -117,23 +91,6 @@ impl<'a> Pig<'a> { )); } - // TODO: skip_all_one_of - // TODO: skip_ws - - pub fn next(&mut self) -> Option { - if self.cursor >= self.input.len() { - return None; - } - let rv = self.input[self.cursor]; - self.cursor += 1; - return Some(rv); - } - - // TODO: next_n - // TODO: cursor - // TODO: save - // TODO: restore - pub fn depleted(&self) -> bool { self.cursor >= self.input.len() } @@ -164,92 +121,60 @@ mod test { assert_eq!(pig.get_until(b'/').unwrap(), &s[..]); } - #[test] - fn test_get_until_eos() { - let s = b"abc:123"; - let mut pig = Pig::new(s); - assert_eq!(pig.get_until_eos(), Some(&s[..])); - } - #[test] fn test_get_quoted() { let s = b"'abcd'efg"; let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\''), Some(&s[1..5])); - assert_eq!(pig.next(), Some(b'e')); + assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..5]); + assert_eq!(pig.cursor, 6); } #[test] fn test_get_quoted_unopened() { let s = b"abcd'efg"; let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\''), None); - assert_eq!(pig.next(), Some(b'a')); // nothing consumed + assert!(pig.get_quoted(b'\'').is_err()); + assert_eq!(pig.cursor, 0); // nothing consumed } #[test] fn test_get_quoted_unclosed() { let s = b"'abcdefg"; let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\''), None); - assert_eq!(pig.next(), Some(b'\'')); // nothing consumed + assert!(pig.get_quoted(b'\'').is_err()); + assert_eq!(pig.cursor, 0); } #[test] fn test_get_quoted_escaped() { let s = b"'abc\\'de'fg"; let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\''), Some(&s[1..8])); - assert_eq!(pig.next(), Some(b'f')); + assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..8]); + assert_eq!(pig.cursor, 9); } #[test] fn test_get_quoted_double_escaped() { let s = b"'abc\\\\'de'fg"; let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\''), Some(&s[1..6])); - assert_eq!(pig.next(), Some(b'd')); + assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..6]); + assert_eq!(pig.cursor, 7); } #[test] fn test_get_quoted_triple_escaped() { let s = b"'abc\\\\\\'de'fg"; let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\''), Some(&s[1..10])); - assert_eq!(pig.next(), Some(b'f')); + assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..10]); + assert_eq!(pig.cursor, 11); } #[test] fn test_get_quoted_all_escapes() { let s = b"'\\\\\\'\\\\'fg"; let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\''), Some(&s[1..7])); - assert_eq!(pig.next(), Some(b'f')); - } - - #[test] - fn test_skip_n() { - let s = b"abc:123"; - let mut pig = Pig::new(s); - assert!(pig.skip_n(3)); - assert_eq!(pig.get_until_eos(), Some(&s[3..])); - } - - #[test] - fn test_skip_n_too_long() { - let s = b"abc:123"; - let mut pig = Pig::new(s); - assert!(!pig.skip_n(33)); - // nothing is consumed - assert_eq!(pig.get_until_eos(), Some(&s[..])); - } - - #[test] - fn test_skip_n_exact_eos() { - let s = b"abc:123"; - let mut pig = Pig::new(s); - assert!(pig.skip_n(7)); - assert_eq!(pig.get_until_eos(), None); + assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..7]); + assert_eq!(pig.cursor, 8); } #[test] @@ -257,7 +182,7 @@ mod test { let s = b"foo"; let mut pig = Pig::new(s); assert!(pig.skip(b'f').is_ok()); - assert_eq!(pig.get_until_eos(), Some(&s[1..])); + assert_eq!(pig.cursor, 1); } #[test] @@ -265,36 +190,14 @@ mod test { let s = b"foo"; let mut pig = Pig::new(s); assert!(pig.skip(b'x').is_err()); - assert_eq!(pig.get_until_eos(), Some(&s[..])); + assert_eq!(pig.cursor, 0); // nothing consumed } #[test] fn test_skip_eos() { - let s = b"foo"; + let s = b"f"; let mut pig = Pig::new(s); - assert!(pig.skip_n(3)); - assert!(pig.skip(b'x').is_err()); - } - - #[test] - fn test_next() { - let s = b"foo"; - let mut pig = Pig::new(s); - assert_eq!(pig.next(), Some(b'f')); - assert_eq!(pig.next(), Some(b'o')); - assert_eq!(pig.next(), Some(b'o')); - assert_eq!(pig.next(), None); - assert_eq!(pig.next(), None); - } - - #[test] - fn test_depleted() { - let s = b"xy"; - let mut pig = Pig::new(s); - assert!(!pig.depleted()); - assert_eq!(pig.next(), Some(b'x')); - assert!(!pig.depleted()); - assert_eq!(pig.next(), Some(b'y')); - assert!(pig.depleted()); + assert!(pig.skip(b'f').is_ok()); + assert!(pig.skip(b'f').is_err()); } } From 9f310c76bd3eb85437c045eb7600e1ed8f837da0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 12 Nov 2018 17:42:19 -0500 Subject: [PATCH 007/548] use a distinct error_chain for the tdb submodule --- src/errors.rs | 8 ++------ src/main.rs | 32 ++++++++++++++++++++++++++++---- src/tdb2/errors.rs | 15 +++++++++++++++ src/tdb2/ff4.rs | 2 +- src/tdb2/mod.rs | 14 ++++++++++---- src/tdb2/pig.rs | 2 +- 6 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 src/tdb2/errors.rs diff --git a/src/errors.rs b/src/errors.rs index 5debda75a..2473668c5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,9 +1,5 @@ error_chain!{ - foreign_links { - Io(::std::io::Error); - StrFromUtf8(::std::str::Utf8Error); - StringFromUtf8(::std::string::FromUtf8Error); - StringFromUtf16(::std::string::FromUtf16Error); + links { + Tdb2Error(::tdb2::errors::Error, ::tdb2::errors::ErrorKind); } - } diff --git a/src/main.rs b/src/main.rs index f8b3f6f82..26a81b551 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "1024"] + extern crate chrono; extern crate uuid; #[macro_use] @@ -12,12 +14,34 @@ use std::io::stdin; use errors::*; -quick_main!(run); +fn main() { + if let Err(ref e) = run() { + use std::io::Write; + let stderr = &mut ::std::io::stderr(); + let errmsg = "Error writing to stderr"; + + writeln!(stderr, "error: {}", e).expect(errmsg); + + for e in e.iter().skip(1) { + writeln!(stderr, "caused by: {}", e).expect(errmsg); + } + + // The backtrace is not always generated. Try to run this example + // with `RUST_BACKTRACE=1`. + if let Some(backtrace) = e.backtrace() { + writeln!(stderr, "backtrace: {:?}", backtrace).expect(errmsg); + } + + ::std::process::exit(1); + } +} fn run() -> Result<()> { let input = stdin(); - parse(input.lock())?.iter().for_each(|t| { - println!("{:?}", t); - }); + parse("".to_string(), input.lock())? + .iter() + .for_each(|t| { + println!("{:?}", t); + }); Ok(()) } diff --git a/src/tdb2/errors.rs b/src/tdb2/errors.rs new file mode 100644 index 000000000..5e4b014e9 --- /dev/null +++ b/src/tdb2/errors.rs @@ -0,0 +1,15 @@ +error_chain!{ + foreign_links { + Io(::std::io::Error); + StrFromUtf8(::std::str::Utf8Error); + StringFromUtf8(::std::string::FromUtf8Error); + StringFromUtf16(::std::string::FromUtf16Error); + } + + errors { + ParseError(filename: String, line: u64) { + description("TDB2 parse error"), + display("TDB2 parse error at {}:{}", filename, line), + } + } +} diff --git a/src/tdb2/ff4.rs b/src/tdb2/ff4.rs index f51edc826..c9b253c2d 100644 --- a/src/tdb2/ff4.rs +++ b/src/tdb2/ff4.rs @@ -2,7 +2,7 @@ use std::str; use super::pig::Pig; use task::{TaskBuilder, Task}; -use errors::*; +use super::errors::*; /// Rust implementation of part of utf8_codepoint from Taskwarrior's src/utf8.cpp /// diff --git a/src/tdb2/mod.rs b/src/tdb2/mod.rs index 0e29d4c0b..4f82aa007 100644 --- a/src/tdb2/mod.rs +++ b/src/tdb2/mod.rs @@ -1,15 +1,21 @@ +//! TDB2 is Taskwarrior's on-disk database format. This module implements +//! support for the data structure as a compatibility layer. + mod pig; mod ff4; +pub(super) mod errors; use std::io::BufRead; use task::Task; use self::ff4::parse_ff4; -use errors::*; +use self::errors::*; -pub(super) fn parse(reader: impl BufRead) -> Result> { +pub(super) fn parse(filename: String, reader: impl BufRead) -> Result> { let mut tasks = vec![]; - for line in reader.lines() { - tasks.push(parse_ff4(&line?)?); + for (i, line) in reader.lines().enumerate() { + tasks.push(parse_ff4(&line?).chain_err(|| { + ErrorKind::ParseError(filename.clone(), i as u64 + 1) + })?); } Ok(tasks) } diff --git a/src/tdb2/pig.rs b/src/tdb2/pig.rs index a8995a80e..1640c777b 100644 --- a/src/tdb2/pig.rs +++ b/src/tdb2/pig.rs @@ -1,7 +1,7 @@ //! A minimal implementation of the "Pig" parsing utility from the Taskwarrior //! source. This is just enough to parse FF4 lines. -use errors::*; +use super::errors::*; pub struct Pig<'a> { input: &'a [u8], From d0744d5178fb064dfbc812407052612086769127 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 12 Nov 2018 18:25:47 -0500 Subject: [PATCH 008/548] refactor to a library, add integration tests --- Cargo.lock | 18 +++++++-------- Cargo.toml | 2 +- src/lib.rs | 18 +++++++++++++++ src/main.rs | 47 --------------------------------------- src/task/taskbuilder.rs | 1 + src/tdb2/mod.rs | 4 ++-- tests/data/tdb2-test.data | 2 ++ tests/parse.rs | 23 +++++++++++++++++++ 8 files changed, 56 insertions(+), 59 deletions(-) create mode 100644 src/lib.rs delete mode 100644 src/main.rs create mode 100644 tests/data/tdb2-test.data create mode 100644 tests/parse.rs diff --git a/Cargo.lock b/Cargo.lock index fc2361e68..a4bb4ba26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,15 +65,6 @@ name = "num-traits" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "rask" -version = "0.1.0" -dependencies = [ - "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "redox_syscall" version = "0.1.40" @@ -84,6 +75,15 @@ name = "rustc-demangle" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "taskwarrior" +version = "0.1.0" +dependencies = [ + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.40" diff --git a/Cargo.toml b/Cargo.toml index 739b8c05a..f4a5f17f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rask" +name = "taskwarrior" version = "0.1.0" authors = ["Dustin J. Mitchell "] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..e3bb5c01f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,18 @@ +#![recursion_limit = "1024"] + +extern crate chrono; +extern crate uuid; +#[macro_use] +extern crate error_chain; + +mod tdb2; +mod task; +mod errors; + +use std::io::BufRead; +pub use task::*; +pub use errors::*; + +pub fn parse(filename: &str, reader: impl BufRead) -> Result> { + Ok(tdb2::parse(filename, reader)?) +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 26a81b551..000000000 --- a/src/main.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![recursion_limit = "1024"] - -extern crate chrono; -extern crate uuid; -#[macro_use] -extern crate error_chain; - -mod tdb2; -mod task; -mod errors; - -use tdb2::parse; -use std::io::stdin; - -use errors::*; - -fn main() { - if let Err(ref e) = run() { - use std::io::Write; - let stderr = &mut ::std::io::stderr(); - let errmsg = "Error writing to stderr"; - - writeln!(stderr, "error: {}", e).expect(errmsg); - - for e in e.iter().skip(1) { - writeln!(stderr, "caused by: {}", e).expect(errmsg); - } - - // The backtrace is not always generated. Try to run this example - // with `RUST_BACKTRACE=1`. - if let Some(backtrace) = e.backtrace() { - writeln!(stderr, "backtrace: {:?}", backtrace).expect(errmsg); - } - - ::std::process::exit(1); - } -} - -fn run() -> Result<()> { - let input = stdin(); - parse("".to_string(), input.lock())? - .iter() - .for_each(|t| { - println!("{:?}", t); - }); - Ok(()) -} diff --git a/src/task/taskbuilder.rs b/src/task/taskbuilder.rs index c2a8d84ca..6859322a4 100644 --- a/src/task/taskbuilder.rs +++ b/src/task/taskbuilder.rs @@ -97,6 +97,7 @@ impl TaskBuilder { const ANNOTATION_PREFIX: &str = "annotation_"; if name.starts_with(ANNOTATION_PREFIX) { let entry = parse_timestamp(&name[ANNOTATION_PREFIX.len()..]).unwrap(); + // TODO: sort by entry time self.annotations.push(Annotation { entry, description: value.to_string(), diff --git a/src/tdb2/mod.rs b/src/tdb2/mod.rs index 4f82aa007..552110bd7 100644 --- a/src/tdb2/mod.rs +++ b/src/tdb2/mod.rs @@ -10,11 +10,11 @@ use task::Task; use self::ff4::parse_ff4; use self::errors::*; -pub(super) fn parse(filename: String, reader: impl BufRead) -> Result> { +pub(crate) fn parse(filename: &str, reader: impl BufRead) -> Result> { let mut tasks = vec![]; for (i, line) in reader.lines().enumerate() { tasks.push(parse_ff4(&line?).chain_err(|| { - ErrorKind::ParseError(filename.clone(), i as u64 + 1) + ErrorKind::ParseError(filename.to_string(), i as u64 + 1) })?); } Ok(tasks) diff --git a/tests/data/tdb2-test.data b/tests/data/tdb2-test.data new file mode 100644 index 000000000..60477651b --- /dev/null +++ b/tests/data/tdb2-test.data @@ -0,0 +1,2 @@ +[description:"https:\/\/phabricator.services.example.com\/D7364 &open;taskgraph&close; Download debian packages" end:"1541705209" entry:"1538520624" modified:"1541705209" phabricatorid:"D7364" priority:"M" project:"moz" status:"completed" tags:"phabricator,respond" uuid:"ca33f6d6-1688-4503-90be-3b3526a32b5a" wait:"1570118809"] +[annotation_1541461824:"https:\/\/github.com\/taskcluster\/taskcluster-worker-manager\/pull\/3" description:"https:\/\/github.com\/taskcluster\/taskcluster-worker-manager\/pull\/3 More changes" end:"1541702602" entry:"1541451283" githubbody:"some notes:\n\n1. This is a huge PR, so I'm not expecting a quick turn around at all. If you have questions, let me know and I can hope on Vidyo.\n1. Data persistence is written in a semi-janky way. My intention is to use the time while this is under review to make progress on postgres stuff so that the next review cycle will be using new Postgres things. Which means.... There's a lot of bugs in the concurrency because there's no synchronisation at all in this janky-ish model.\n1. The API is the minimum api required to get provisioning-ish things working\n1. I intend to write a system for automatically testing provider and bidding strategy implementations, so that you can do instantiate a provider\/strategy, stub\/spy it as needed then run a test suite against it and have it do its thing. The idea is that each provider will need to mock their underlying api system in their own way, but the set of tests we run for Provider API conformance would be pretty standardized. This should make writing tests for new providers a lot easier.\n1. The provider\/strategy loading system is intentionally simple. The idea is that these aren't general purpose plugins, but rather special ones. The idea is that the config files would essentially declare instances and then provide constructor arguments to initialize them all... This would make enabling\/disabling providers\/strategies fairly trivial\n1. I decided to drop fake implementations of providers and strategies for testing the provisioning logic and instead opt for Sinon stubs, which I think give us a better testing story\n1. I still intend to have fake providers and bidding strategies for doing API testing.\n\nLet me know, and again, I don't expect or need a quick turn around on this PR.\n" githubcreatedon:"1541451283" githubnamespace:"djmitche" githubnumber:"3.000000" githubrepo:"taskcluster\/taskcluster-worker-manager" githubtitle:"More changes" githubtype:"pull_request" githubupdatedat:"1541699191" githuburl:"https:\/\/github.com\/taskcluster\/taskcluster-worker-manager\/pull\/3" githubuser:"jhford" modified:"1541702602" priority:"H" project:"moz" status:"completed" tags:"respond" uuid:"2186f981-d1f5-4642-b833-5b16b3a2d334"] diff --git a/tests/parse.rs b/tests/parse.rs new file mode 100644 index 000000000..9ea0fb448 --- /dev/null +++ b/tests/parse.rs @@ -0,0 +1,23 @@ +extern crate taskwarrior; +extern crate chrono; + +use std::fs::File; +use std::io::BufReader; +use chrono::prelude::*; + +#[test] +fn test_parse() { + let filename = "tests/data/tdb2-test.data"; + let file = File::open(filename).unwrap(); + let tasks = taskwarrior::parse(filename, BufReader::new(file)).unwrap(); + assert_eq!( + tasks[0].description, + "https://phabricator.services.example.com/D7364 [taskgraph] Download debian packages" + ); + assert_eq!(tasks[0].entry, Utc.timestamp(1538520624, 0)); + assert_eq!(tasks[0].udas.get("phabricatorid").unwrap(), "D7364"); + assert_eq!(tasks[1].annotations[0].entry, Utc.timestamp(1541461824, 0)); + assert!(tasks[1].annotations[0].description.starts_with( + "https://github.com", + )); +} From 63d5f78cb80baa0916bfa6a1ece5cb34bf9e1277 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 24 Nov 2018 16:55:01 -0500 Subject: [PATCH 009/548] add .taskcluster.yml --- .taskcluster.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .taskcluster.yml diff --git a/.taskcluster.yml b/.taskcluster.yml new file mode 100644 index 000000000..b3566f886 --- /dev/null +++ b/.taskcluster.yml @@ -0,0 +1,28 @@ +version: 0 +tasks: + - provisionerId: '{{ taskcluster.docker.provisionerId }}' + workerType: '{{ taskcluster.docker.workerType }}' + extra: + github: + events: + - pull_request.opened + - pull_request.reopened + - pull_request.synchronize + payload: + maxRunTime: 3600 + image: 'rust:latest' + command: + - /bin/bash + - '-c' + - >- + git clone {{event.head.repo.url}} repo && + cd repo && + git config advice.detachedHead false && + git checkout {{event.head.sha}} && + cargo test + metadata: + name: Test + description: 'Run tests' + owner: '{{ event.head.user.email }}' + source: '{{ event.head.repo.url }}' +allowPullRequests: collaborators From 1809fe36741c6e177ae9c67c9299c182b759870d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 24 Nov 2018 16:55:53 -0500 Subject: [PATCH 010/548] build on push --- .taskcluster.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.taskcluster.yml b/.taskcluster.yml index b3566f886..d6406c4ac 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -8,6 +8,7 @@ tasks: - pull_request.opened - pull_request.reopened - pull_request.synchronize + - push payload: maxRunTime: 3600 image: 'rust:latest' From 93ce28ed15d11ba601765933f95756e7fb76d2e3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 24 Nov 2018 17:00:44 -0500 Subject: [PATCH 011/548] add README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..ac389fd51 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +At the moment, this is sort of an make-work project to re-implement Taskwarrior in Rust. + +There's no great reason to do so, and lots of reasons not to. +But it's a nice way to practice some "basic" Rust that does not exercise all of the language's more esoteric features. From 05d26285e3fac39fa65b75851201103488f1c293 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 25 Dec 2019 10:51:44 -0500 Subject: [PATCH 012/548] implement TextOperation --- .gitignore | 2 + Cargo.lock | 6 + Cargo.toml | 9 + src/main.rs | 7 + src/operation.rs | 576 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 600 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/operation.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..53eaa2196 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..d7e8046f3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ot" +version = "0.1.0" + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..93ba0d087 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ot" +version = "0.1.0" +authors = ["Dustin J. Mitchell "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 000000000..d4a28f0a7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,7 @@ +mod operation; + +// see https://raw.githubusercontent.com/Operational-Transformation/ot.js/master/dist/ot.js + +fn main() { + println!("Hello, world!"); +} diff --git a/src/operation.rs b/src/operation.rs new file mode 100644 index 000000000..e643b228f --- /dev/null +++ b/src/operation.rs @@ -0,0 +1,576 @@ +#[derive(PartialEq, Debug, Clone)] +enum Op { + Retain(usize), + Insert(String), + Delete(usize), +} + +#[derive(PartialEq, Debug, Clone)] +struct TextOperation { + // When an operation is applied to an input string, you can think of this as if an imaginary + // cursor runs over the entire string and skips over some parts, deletes some parts and inserts + // characters at some positions. These actions (skip/delete/insert) are stored as an array in + // the "ops" property. + ops: Vec, + + // An operation's base_length is the length of every string the operation can be applied to. + base_length: usize, + + // The target_length is the length of every string that results from applying the operation on + // a valid input string. + target_length: usize, +} + +impl TextOperation { + // Create a new, empty TextOperation + pub fn new() -> Self { + Self { + ops: vec![], + base_length: 0, + target_length: 0, + } + } + + // Add a retain op to this TextOperation (used in the builder pattern) + pub fn retain(mut self: Self, n: usize) -> Self { + if n == 0 { + return self; + } + + self.base_length += n; + self.target_length += n; + + if let Some(&mut Op::Retain(ref mut l)) = self.ops.last_mut() { + *l += n; + } else { + self.ops.push(Op::Retain(n)); + } + + self + } + + // Add an insert op to this TextOperation (used in the builder pattern) + pub fn insert(mut self: Self, s: impl Into) -> Self { + let s = s.into(); + let l = s.len(); + + if l == 0 { + return self; + } + + if let Some(&Op::Delete(_)) = self.ops.last() { + // maintain the invariant that inserts never follow a delete by popping the + // delete. adding the insert, and then re-adding the delete + let del = self.ops.pop().unwrap(); + self = self.insert(s); + self.ops.push(del); + return self; + } + + self.target_length += l; + + if let Some(&mut Op::Insert(ref mut l)) = self.ops.last_mut() { + l.push_str(&s) + } else { + self.ops.push(Op::Insert(s)); + } + + self + } + + // Add a delete op to this TextOperation (used in the builder pattern) + pub fn delete(mut self: Self, n: usize) -> Self { + if n == 0 { + return self; + } + + self.base_length += n; + + if let Some(&mut Op::Delete(ref mut l)) = self.ops.last_mut() { + *l += n; + } else { + self.ops.push(Op::Delete(n)); + } + + self + } + + // Apply an operation to a string. Returns an error if there's a mismatch between the input string + // and the operation. + pub fn apply(self: &Self, input: &str) -> Result { + let strlen = input.len(); + + if strlen != self.base_length { + return Err(String::from("The operation's base length must be equal to the string's length")) + } + + let mut res = String::new(); + let mut i = 0; + for op in &self.ops { + match op { + Op::Retain(n) => { + assert!(i + n <= strlen); + res.push_str(&input[i..i + n]); + i += n; + }, + Op::Insert(s) => { + res.push_str(&s); + }, + Op::Delete(n) => { + i += n; + }, + } + } + + Ok(res) + } + + // Transform takes two operations A and B that happened concurrently and produces two + // operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This + // function is the heart of OT. That is, given a state map + // + // * + // / \ + // op1 / \ op2 + // / \ + // * * + // + // this function "completes the diamond: + // + // * * + // \ / + // op2' \ / op1' + // \ / + // * + // + // such that applying op2' after op1 has the same effect as applying op1' after op2. This + // allows two different systems which have already applied op1 and op2, respectively, and thus + // reached different states, to return to the same state by applying op2' and op1', + // respectively. + pub fn transform(mut operation1: TextOperation, mut operation2: TextOperation) -> (TextOperation, TextOperation) { + // both must start at the same state, and thus have the same base length + assert_eq!(operation1.base_length, operation2.base_length); + + let (mut operation1p, mut operation2p) = (TextOperation::new(), TextOperation::new()); + let (mut ops1, mut ops2) = (operation1.ops.iter_mut(), operation2.ops.iter_mut()); + let (mut op1, mut op2) = (ops1.next(), ops2.next()); + + loop { + match (&mut op1, &mut op2) { + // end condition: both ops1 and ops2 have been processed + (None, None) => break, + + // handle inserts, preferring op1 if both are inserts + (Some(&mut Op::Insert(ref s)), _) => { + operation1p = operation1p.insert(s.clone()); + operation2p = operation2p.retain(s.len()); + op1 = ops1.next(); + continue; + }, + + (_, Some(&mut Op::Insert(ref s))) => { + operation1p = operation1p.retain(s.len()); + operation2p = operation2p.insert(s.clone()); + op2 = ops2.next(); + continue; + }, + + (None, _) | (_, None) => { + unreachable!(); + } + + (Some(&mut Op::Retain(ref mut n1)), Some(&mut Op::Retain(ref mut n2))) => { + // retain the minimum of the two, and rewrite the larger input op + // to only retain what's left + let min; + if n1 > n2 { + min = *n2; + *n1 = *n1 - *n2; + op2 = ops2.next(); + } else if n1 == n2 { + min = *n2; + op1 = ops1.next(); + op2 = ops2.next(); + } else { + min = *n1; + *n2 = *n2 - *n1; + op1 = ops1.next(); + } + operation1p = operation1p.retain(min); + operation2p = operation2p.retain(min); + }, + (Some(Op::Delete(n1)), Some(Op::Delete(n2))) => { + // Both operations delete the same string at the same position. We don't need + // to produce any operations, we just skip over the delete ops and handle the + // case that one operation deletes more than the other. + if n1 > n2 { + *n1 = *n2 - *n1; + op2 = ops2.next(); + } else if n1 == n2 { + op1 = ops1.next(); + op2 = ops2.next(); + } else { + *n2 = *n2 - *n1; + op1 = ops1.next(); + } + }, + (Some(Op::Delete(n1)), Some(Op::Retain(n2))) => { + let min; + if n1 > n2 { + min = *n2; + *n1 = *n1 - *n2; + op2 = ops2.next(); + } else if n1 == n2 { + min = *n1; + op1 = ops1.next(); + op2 = ops2.next(); + } else { + min = *n1; + *n2 = *n2 - *n1; + op1 = ops1.next(); + } + operation1p = operation1p.delete(min); + }, + (Some(Op::Retain(n1)), Some(Op::Delete(n2))) => { + let min; + if n1 > n2 { + min = *n2; + *n1 = *n1 - *n2; + op2 = ops2.next(); + } else if n1 == n2 { + min = *n1; + op1 = ops1.next(); + op2 = ops2.next(); + } else { + min = *n1; + *n2 = *n2 - *n1; + op1 = ops1.next(); + } + operation2p = operation2p.delete(min); + }, + } + } + + (operation1p, operation2p) + } +} + +#[cfg(test)] +mod test { + use super::{TextOperation, Op}; + + #[test] + fn test_build_retain() { + let op = TextOperation::new() + .retain(10); + assert_eq!(op.base_length, 10); + assert_eq!(op.target_length, 10); + assert_eq!(op.ops, vec![Op::Retain(10)]); + } + + #[test] + fn test_build_retain_null() { + let op = TextOperation::new() + .retain(0); + assert_eq!(op.base_length, 0); + assert_eq!(op.target_length, 0); + assert_eq!(op.ops, vec![]); + } + + #[test] + fn test_build_retain_merge() { + let op = TextOperation::new() + .retain(10) + .retain(20); + assert_eq!(op.base_length, 30); + assert_eq!(op.target_length, 30); + assert_eq!(op.ops, vec![Op::Retain(30)]); + } + + #[test] + fn test_build_retain_after_insert() { + let op = TextOperation::new() + .insert(String::from("hello")) + .retain(20); + assert_eq!(op.base_length, 20); + assert_eq!(op.target_length, 25); + assert_eq!(op.ops, vec![Op::Insert(String::from("hello")), Op::Retain(20)]); + } + + #[test] + fn test_build_retain_after_delete() { + let op = TextOperation::new() + .delete(10) + .retain(20); + assert_eq!(op.base_length, 30); + assert_eq!(op.target_length, 20); + assert_eq!(op.ops, vec![Op::Delete(10), Op::Retain(20)]); + } + + #[test] + fn test_build_insert() { + let op = TextOperation::new() + .insert(String::from("hello")); + assert_eq!(op.base_length, 0); + assert_eq!(op.target_length, 5); + assert_eq!(op.ops, vec![Op::Insert(String::from("hello"))]); + } + + #[test] + fn test_build_insert_null() { + let op = TextOperation::new() + .insert(String::from("")); + assert_eq!(op.base_length, 0); + assert_eq!(op.target_length,0); + assert_eq!(op.ops, vec![]); + } + + #[test] + fn test_build_insert_merge() { + let op = TextOperation::new() + .insert(String::from("hello")) + .insert(String::from(" world")); + assert_eq!(op.base_length, 0); + assert_eq!(op.target_length, 11); + assert_eq!(op.ops, vec![Op::Insert(String::from("hello world"))]); + } + + #[test] + fn test_build_insert_after_retain() { + let op = TextOperation::new() + .retain(10) + .insert(String::from("hello")); + assert_eq!(op.base_length, 10); + assert_eq!(op.target_length, 15); + assert_eq!(op.ops, vec![Op::Retain(10), Op::Insert(String::from("hello"))]); + } + + #[test] + fn test_build_insert_after_delete() { + let op = TextOperation::new() + .delete(10) + .insert(String::from("hello")); + assert_eq!(op.base_length, 10); + assert_eq!(op.target_length, 5); + assert_eq!(op.ops, vec![ + Op::Insert(String::from("hello")), + Op::Delete(10), + ]); + } + + #[test] + fn test_build_insert_after_delete_merge() { + let op = TextOperation::new() + .insert(String::from("hello")) + .delete(10) + .insert(String::from(" world")); + assert_eq!(op.base_length, 10); + assert_eq!(op.target_length, 11); + assert_eq!(op.ops, vec![ + Op::Insert(String::from("hello world")), + Op::Delete(10), + ]); + } + + #[test] + fn test_build_delete() { + let op = TextOperation::new() + .delete(17); + assert_eq!(op.base_length, 17); + assert_eq!(op.target_length, 0); + assert_eq!(op.ops, vec![Op::Delete(17)]); + } + + #[test] + fn test_build_delete_null() { + let op = TextOperation::new() + .delete(0); + assert_eq!(op.base_length, 0); + assert_eq!(op.target_length, 0); + assert_eq!(op.ops, vec![]); + } + + #[test] + fn test_build_delete_merge() { + let op = TextOperation::new() + .delete(5) + .delete(8); + assert_eq!(op.base_length, 13); + assert_eq!(op.target_length, 0); + assert_eq!(op.ops, vec![Op::Delete(13)]); + } + + #[test] + fn test_build_delete_after_insert() { + let op = TextOperation::new() + .insert(String::from("hello")) + .delete(8); + assert_eq!(op.base_length, 8); + assert_eq!(op.target_length, 5); + assert_eq!(op.ops, vec![Op::Insert(String::from("hello")), Op::Delete(8)]); + } + + #[test] + fn test_build_delete_after_retain() { + let op = TextOperation::new() + .retain(10) + .delete(8); + assert_eq!(op.base_length, 18); + assert_eq!(op.target_length, 10); + assert_eq!(op.ops, vec![Op::Retain(10), Op::Delete(8)]); + } + + #[test] + fn test_apply_retain() { + let op = TextOperation::new() + .retain(5); + assert_eq!( + op.apply("hello"), + Ok(String::from("hello"))); + } + + #[test] + fn test_apply_insert() { + let op = TextOperation::new() + .retain(5) + .insert(String::from(" cruel")) + .retain(6); + assert_eq!( + op.apply("hello world"), + Ok(String::from("hello cruel world"))); + } + + #[test] + fn test_apply_delete() { + let op = TextOperation::new() + .retain(5) + .delete(6) + .retain(6); + assert_eq!( + op.apply("hello cruel world"), + Ok(String::from("hello world"))); + } + + #[test] + fn test_apply_wrong_length() { + let op = TextOperation::new() + .retain(5) + .insert(String::from(" cruel")) + .retain(6); + assert_eq!( + op.apply("hello cruel world"), + Err(String::from("The operation's base length must be equal to the string's length"))); + } + + mod transform { + use super::{TextOperation}; + + const STARTING_STATE: &str ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + fn test_transform(o1: TextOperation, o2: TextOperation, exp1p: TextOperation, exp2p: TextOperation) { + // first check that the resulting operation is as expected.. + let (o1p, o2p) = TextOperation::transform(o1.clone(), o2.clone()); + assert_eq!((&o1p, &o2p), (&exp1p, &exp2p)); + + // then check that the definition of `transform` is satisfied, by applying to an + // arbitrary string + let input = &STARTING_STATE[0..o1.base_length]; + // B' composed with A + let first = o2p.apply(&o1.apply(&input).unwrap()).unwrap(); + // A' composed with B + let second = o1p.apply(&o2.apply(&input).unwrap()).unwrap(); + print!("{} -> {}\n", input, first); + assert_eq!(first, second); + } + + #[test] + fn test_transform_empty() { + test_transform( + TextOperation::new(), + TextOperation::new(), + TextOperation::new(), + TextOperation::new()); + } + + #[test] + fn test_transform_one_noop() { + test_transform( + TextOperation::new().retain(2).insert("123").retain(10), + TextOperation::new().retain(12), + TextOperation::new().retain(2).insert("123").retain(10), + TextOperation::new().retain(15)); + } + + #[test] + fn test_transform_two_inserts() { + test_transform( + TextOperation::new().insert("123"), + TextOperation::new().insert("567"), + TextOperation::new().insert("123").retain(3), + TextOperation::new().retain(3).insert("567")); + } + + #[test] + fn test_transform_two_retains() { + test_transform( + TextOperation::new().retain(10), + TextOperation::new().retain(10), + TextOperation::new().retain(10), + TextOperation::new().retain(10)); + } + + #[test] + fn test_transform_insert_two_different_spots() { + test_transform( + TextOperation::new().retain(5).insert("123").retain(10), + TextOperation::new().retain(10).insert("567").retain(5), + TextOperation::new().retain(5).insert("123").retain(13), + TextOperation::new().retain(13).insert("567").retain(5)); + } + + #[test] + fn test_transform_insert_two_different_spots_reversed() { + test_transform( + TextOperation::new().retain(10).insert("567").retain(5), + TextOperation::new().retain(5).insert("123").retain(10), + TextOperation::new().retain(13).insert("567").retain(5), + TextOperation::new().retain(5).insert("123").retain(13)); + } + + #[test] + fn test_transform_two_deletes() { + test_transform( + TextOperation::new().delete(10), + TextOperation::new().delete(10), + TextOperation::new(), + TextOperation::new()); + } + + #[test] + fn test_transform_delete_retain() { + test_transform( + TextOperation::new().retain(10), + TextOperation::new().delete(3).retain(7), + TextOperation::new().retain(7), + TextOperation::new().delete(3).retain(7)); + } + + #[test] + fn test_transform_retain_delete() { + test_transform( + TextOperation::new().delete(3).retain(7), + TextOperation::new().retain(10), + TextOperation::new().delete(3).retain(7), + TextOperation::new().retain(7)); + } + + #[test] + fn test_transform_delete_insert() { + test_transform( + TextOperation::new().delete(10), + TextOperation::new().retain(3).insert("123").retain(7), + TextOperation::new().delete(3).retain(3).delete(7), + TextOperation::new().insert("123")); + } + } +} From 10c7dd28b936e418c90c5aee9f9c448cacdaf7f9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 25 Dec 2019 17:41:28 -0500 Subject: [PATCH 013/548] add README --- README.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..2c9d3101f --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +Sketch for a Taskd Replacement +------------------------------ + +Goals: + + * Reasonable privacy: user's task details not visible on server + * Reliable concurrency - clients do not diverge + * Storage O(n) with n number of tasks + +# Data Model + +A client manages a single offline instance of a single user's task list. +The data model is only seen from the clients' perspective. + +## Task Database + +The task database is composed of an un-ordered collection of tasks, each keyed by a UUID. +Each task has an arbitrary-sized set of key/value properties, with JSON values. +A property with a `null` value is considered equivalent to that property not being set on the task. + +Tasks are only created, never deleted. +See below for details on how tasks can "expire" from the task database. + +## Operations + +Every change to the task database is captured as an operation. +Each operation has one of the forms + * `Create(uuid)` + * `Update(uuid, property, value, timestamp)` + +The former form creates a new task. +The latter form updates the given property of the given task. +It is invalid to update a task that does not exist. +The timestamp on updates serves as additional metadata and is used to resolve conflicts. + +Operations act as deltas between database states. + +## Versions and Synchronization + +Occasionally, database states are named with an integer, called a version. +The system as a whole (server and clients) constructs a monotonic sequence of versions and the operations that separate each version from the next. +No gaps are allowed in the verison numbering. +Version 0 is implicitly the empty database. + +The server stores the operations for each version, and provides them as needed to clients. +Clients use this information to update their local task databases, and to generate new versions to send to the server. + +Clients generate a new version to transmit changes made locally to the server. +The changes are represented as a sequence of operations with the final operation being tagged as the version. +In order to keep the gap-free monotonic numbering, the server will only accept a proposed version from a client if its number is one greater that the latest version on the server. +When this is not the case, the client must "rebase" the local changes onto the latest version from the server and try again. +This operation is performed using operational transformation (OT). +The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the client can apply to its local task database to "catch up" to the version on the server. + +## Snapshots + +As designed, storage required on the server would grow with time, as would the time required for new clients to update to the latest version. +As an optimization, the server also stores "snapshots" containing a full copy of the task database at a given version. +Based on configurable heuristics, it may delete older operations and snapshots, as long as enough data remains for active clients to synchronize and for new clients to initialize. + +Since snapshots must be computed by clients, the server may "request" a snapshot when providing the latest version to a client. +This request comes with a number indicating how much it 'wants" the snapshot. +Clients which can easily generate and transmit a snapshot should be generous to the server, while clients with more limited resources can wait until the server's requests are more desperate. +The intent is, where possible, to request snapshots created on well-connected desktop clients over mobile and low-power clients. + +## Encryption and Signing + +From the server's perspective, all data except for version numbers are opaque binary blobs. +Clients encrypt and sign these blobs using a symmetric key known only to the clients. +This secures the data at-rest on the server. +Note that privacy is not complete, as the server still has some information about users, including source and frequency of synchronization transactions and size of those transactions. + +## Expiration + +TBD + +.. conditions on flushing to allow consistent handling + +# Implementation Notes + +## Client / Server Protocol + +TBD + +.. using HTTP +.. user auth +.. user setup process + +## Batching Operations + +TBD + +## Recurrence + +TBD + From e5a92826f6cdf08cf9129fc1aa60977afb867fcb Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Dec 2019 11:20:35 -0500 Subject: [PATCH 014/548] add taskdb --- Cargo.lock | 323 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 + README.md | 2 + src/errors.rs | 7 ++ src/lib.rs | 3 + src/main.rs | 7 -- src/taskdb.rs | 142 ++++++++++++++++++++++ 7 files changed, 481 insertions(+), 7 deletions(-) create mode 100644 src/errors.rs create mode 100644 src/lib.rs delete mode 100644 src/main.rs create mode 100644 src/taskdb.rs diff --git a/Cargo.lock b/Cargo.lock index d7e8046f3..c839c94c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,329 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "c2-chacha" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getrandom" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.66" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num-integer" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ot" version = "0.1.0" +dependencies = [ + "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasi" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" +"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" +"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" +"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" +"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" +"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" +"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" +"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 93ba0d087..2862faab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +uuid = { version = "0.8.1", features = ["serde", "v4"] } +serde_json = "1.0" +chrono = "0.4.10" +failure = {version = "0.1.5", features = ["derive"] } diff --git a/README.md b/README.md index 2c9d3101f..bc4440d47 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Each operation has one of the forms * `Update(uuid, property, value, timestamp)` The former form creates a new task. +It is invalid to create a task that already exists. + The latter form updates the given property of the given task. It is invalid to update a task that does not exist. The timestamp on updates serves as additional metadata and is used to resolve conflicts. diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 000000000..08884376f --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,7 @@ +use failure::Fail; + +#[derive(Debug, Fail, Eq, PartialEq, Clone)] +pub enum Error { + #[fail(display = "Task Database Error: {}", _0)] + DBError(String), +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..e0be8b685 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +mod errors; +mod operation; +mod taskdb; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index d4a28f0a7..000000000 --- a/src/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod operation; - -// see https://raw.githubusercontent.com/Operational-Transformation/ot.js/master/dist/ot.js - -fn main() { - println!("Hello, world!"); -} diff --git a/src/taskdb.rs b/src/taskdb.rs new file mode 100644 index 000000000..02fd51da0 --- /dev/null +++ b/src/taskdb.rs @@ -0,0 +1,142 @@ +use crate::errors::Error; +use chrono::{DateTime, Utc}; +use serde_json::Value; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use uuid::Uuid; + +#[derive(PartialEq, Clone, Debug)] +enum Operation { + Create { + uuid: Uuid, + }, + Update { + uuid: Uuid, + property: String, + value: Value, + timestamp: DateTime, + }, +} + +struct DB { + // The current state, with all pending operations applied + tasks: HashMap>, + + // The version at which `operations` begins + base_version: u64, + + // Operations applied since `base_version`, in order. + // + // INVARIANT: Given a snapshot at `base_version`, applying these operations produces `tasks`. + operations: Vec, +} + +impl DB { + fn new() -> DB { + DB { + tasks: HashMap::new(), + base_version: 0, + operations: vec![], + } + } + + fn apply(&mut self, op: Operation) -> Result<(), Error> { + match op { + Operation::Create { uuid } => { + match self.tasks.entry(uuid) { + Entry::Occupied(_) => { + return Err(Error::DBError(format!("Task {} already exists", uuid))); + } + ent @ Entry::Vacant(_) => { + ent.or_insert(HashMap::new()); + } + }; + } + Operation::Update { + ref uuid, + ref property, + ref value, + timestamp: _, + } => { + match self.tasks.get_mut(uuid) { + Some(task) => { + task.insert(property.clone(), value.clone()); + } + None => { + return Err(Error::DBError(format!("Task {} does not exist", uuid))); + } + }; + } + }; + self.operations.push(op); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use uuid::Uuid; + + #[test] + fn test_apply_create() { + let mut db = DB::new(); + let uuid = Uuid::new_v4(); + let op = Operation::Create { uuid }; + db.apply(op.clone()).unwrap(); + + let mut exp = HashMap::new(); + exp.insert(uuid, HashMap::new()); + assert_eq!(db.tasks, exp); + assert_eq!(db.operations, vec![op]); + } + + #[test] + fn test_apply_create_exists() { + let mut db = DB::new(); + let uuid = Uuid::new_v4(); + db.apply(Operation::Create { uuid }).unwrap(); + assert_eq!( + db.apply(Operation::Create { uuid }), + Err(Error::DBError(format!("Task {} already exists", uuid))) + ); + } + + #[test] + fn test_apply_create_update() { + let mut db = DB::new(); + let uuid = Uuid::new_v4(); + let op1 = Operation::Create { uuid }; + db.apply(op1.clone()).unwrap(); + let op2 = Operation::Update { + uuid, + property: String::from("title"), + value: Value::from("\"my task\""), + timestamp: Utc::now(), + }; + db.apply(op2.clone()).unwrap(); + + let mut exp = HashMap::new(); + let mut task = HashMap::new(); + task.insert(String::from("title"), Value::from("\"my task\"")); + exp.insert(uuid, task); + assert_eq!(db.tasks, exp); + assert_eq!(db.operations, vec![op1, op2]); + } + + #[test] + fn test_apply_update_does_not_exist() { + let mut db = DB::new(); + let uuid = Uuid::new_v4(); + assert_eq!( + db.apply(Operation::Update { + uuid, + property: String::from("title"), + value: Value::from("\"my task\""), + timestamp: Utc::now(), + }), + Err(Error::DBError(format!("Task {} does not exist", uuid))) + ); + } + +} From 83b2318a0600b5ab4853461c5e77a9f08914f974 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Dec 2019 11:24:20 -0500 Subject: [PATCH 015/548] backup scheme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index bc4440d47..4c857eeb2 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,13 @@ Clients encrypt and sign these blobs using a symmetric key known only to the cli This secures the data at-rest on the server. Note that privacy is not complete, as the server still has some information about users, including source and frequency of synchronization transactions and size of those transactions. +## Backups + +In this design, the server is little more than an authenticated storage for encrypted blobs provided by the client. +To allow for failure or data loss on the server, clients are expected to cache these blobs locally for a short time (a week), along with a server-provided HMAC signature. +When data loss is detected -- such as when a client expects the server to have a version N or higher, and the server only has N-1, the client can send those blobs to the server. +The server can validate the HMAC and, if successful, add the blobs to its datastore. + ## Expiration TBD From 0c3e4d5c2e68d3c448bd99f8bd2cb51b56ed54d6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Dec 2019 11:44:45 -0500 Subject: [PATCH 016/548] add a tasks method to db --- src/lib.rs | 3 +++ src/taskdb.rs | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e0be8b685..3ef1ecf6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +// TODO: remove this eventually when there's an API +#![allow(dead_code)] + mod errors; mod operation; mod taskdb; diff --git a/src/taskdb.rs b/src/taskdb.rs index 02fd51da0..e5d8ee195 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -32,6 +32,7 @@ struct DB { } impl DB { + /// Create a new, empty database fn new() -> DB { DB { tasks: HashMap::new(), @@ -40,6 +41,8 @@ impl DB { } } + /// Apply an operation to the DB. Aside from synchronization operations, this + /// is the only way to modify the DB. fn apply(&mut self, op: Operation) -> Result<(), Error> { match op { Operation::Create { uuid } => { @@ -71,6 +74,13 @@ impl DB { self.operations.push(op); Ok(()) } + + /// Get a read-only reference to the underlying set of tasks. + /// + /// This API is temporary, but provides query access to the DB. + fn tasks(&self) -> &HashMap> { + &self.tasks + } } #[cfg(test)] @@ -87,7 +97,7 @@ mod tests { let mut exp = HashMap::new(); exp.insert(uuid, HashMap::new()); - assert_eq!(db.tasks, exp); + assert_eq!(db.tasks(), &exp); assert_eq!(db.operations, vec![op]); } @@ -120,7 +130,7 @@ mod tests { let mut task = HashMap::new(); task.insert(String::from("title"), Value::from("\"my task\"")); exp.insert(uuid, task); - assert_eq!(db.tasks, exp); + assert_eq!(db.tasks(), &exp); assert_eq!(db.operations, vec![op1, op2]); } @@ -138,5 +148,4 @@ mod tests { Err(Error::DBError(format!("Task {} does not exist", uuid))) ); } - } From 72a12c751edd42430037c5b31ea9e21162cac8c2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Dec 2019 11:45:26 -0500 Subject: [PATCH 017/548] remove text operations --- src/lib.rs | 1 - src/operation.rs | 576 ----------------------------------------------- 2 files changed, 577 deletions(-) delete mode 100644 src/operation.rs diff --git a/src/lib.rs b/src/lib.rs index 3ef1ecf6e..dd2163137 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,5 +2,4 @@ #![allow(dead_code)] mod errors; -mod operation; mod taskdb; diff --git a/src/operation.rs b/src/operation.rs deleted file mode 100644 index e643b228f..000000000 --- a/src/operation.rs +++ /dev/null @@ -1,576 +0,0 @@ -#[derive(PartialEq, Debug, Clone)] -enum Op { - Retain(usize), - Insert(String), - Delete(usize), -} - -#[derive(PartialEq, Debug, Clone)] -struct TextOperation { - // When an operation is applied to an input string, you can think of this as if an imaginary - // cursor runs over the entire string and skips over some parts, deletes some parts and inserts - // characters at some positions. These actions (skip/delete/insert) are stored as an array in - // the "ops" property. - ops: Vec, - - // An operation's base_length is the length of every string the operation can be applied to. - base_length: usize, - - // The target_length is the length of every string that results from applying the operation on - // a valid input string. - target_length: usize, -} - -impl TextOperation { - // Create a new, empty TextOperation - pub fn new() -> Self { - Self { - ops: vec![], - base_length: 0, - target_length: 0, - } - } - - // Add a retain op to this TextOperation (used in the builder pattern) - pub fn retain(mut self: Self, n: usize) -> Self { - if n == 0 { - return self; - } - - self.base_length += n; - self.target_length += n; - - if let Some(&mut Op::Retain(ref mut l)) = self.ops.last_mut() { - *l += n; - } else { - self.ops.push(Op::Retain(n)); - } - - self - } - - // Add an insert op to this TextOperation (used in the builder pattern) - pub fn insert(mut self: Self, s: impl Into) -> Self { - let s = s.into(); - let l = s.len(); - - if l == 0 { - return self; - } - - if let Some(&Op::Delete(_)) = self.ops.last() { - // maintain the invariant that inserts never follow a delete by popping the - // delete. adding the insert, and then re-adding the delete - let del = self.ops.pop().unwrap(); - self = self.insert(s); - self.ops.push(del); - return self; - } - - self.target_length += l; - - if let Some(&mut Op::Insert(ref mut l)) = self.ops.last_mut() { - l.push_str(&s) - } else { - self.ops.push(Op::Insert(s)); - } - - self - } - - // Add a delete op to this TextOperation (used in the builder pattern) - pub fn delete(mut self: Self, n: usize) -> Self { - if n == 0 { - return self; - } - - self.base_length += n; - - if let Some(&mut Op::Delete(ref mut l)) = self.ops.last_mut() { - *l += n; - } else { - self.ops.push(Op::Delete(n)); - } - - self - } - - // Apply an operation to a string. Returns an error if there's a mismatch between the input string - // and the operation. - pub fn apply(self: &Self, input: &str) -> Result { - let strlen = input.len(); - - if strlen != self.base_length { - return Err(String::from("The operation's base length must be equal to the string's length")) - } - - let mut res = String::new(); - let mut i = 0; - for op in &self.ops { - match op { - Op::Retain(n) => { - assert!(i + n <= strlen); - res.push_str(&input[i..i + n]); - i += n; - }, - Op::Insert(s) => { - res.push_str(&s); - }, - Op::Delete(n) => { - i += n; - }, - } - } - - Ok(res) - } - - // Transform takes two operations A and B that happened concurrently and produces two - // operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This - // function is the heart of OT. That is, given a state map - // - // * - // / \ - // op1 / \ op2 - // / \ - // * * - // - // this function "completes the diamond: - // - // * * - // \ / - // op2' \ / op1' - // \ / - // * - // - // such that applying op2' after op1 has the same effect as applying op1' after op2. This - // allows two different systems which have already applied op1 and op2, respectively, and thus - // reached different states, to return to the same state by applying op2' and op1', - // respectively. - pub fn transform(mut operation1: TextOperation, mut operation2: TextOperation) -> (TextOperation, TextOperation) { - // both must start at the same state, and thus have the same base length - assert_eq!(operation1.base_length, operation2.base_length); - - let (mut operation1p, mut operation2p) = (TextOperation::new(), TextOperation::new()); - let (mut ops1, mut ops2) = (operation1.ops.iter_mut(), operation2.ops.iter_mut()); - let (mut op1, mut op2) = (ops1.next(), ops2.next()); - - loop { - match (&mut op1, &mut op2) { - // end condition: both ops1 and ops2 have been processed - (None, None) => break, - - // handle inserts, preferring op1 if both are inserts - (Some(&mut Op::Insert(ref s)), _) => { - operation1p = operation1p.insert(s.clone()); - operation2p = operation2p.retain(s.len()); - op1 = ops1.next(); - continue; - }, - - (_, Some(&mut Op::Insert(ref s))) => { - operation1p = operation1p.retain(s.len()); - operation2p = operation2p.insert(s.clone()); - op2 = ops2.next(); - continue; - }, - - (None, _) | (_, None) => { - unreachable!(); - } - - (Some(&mut Op::Retain(ref mut n1)), Some(&mut Op::Retain(ref mut n2))) => { - // retain the minimum of the two, and rewrite the larger input op - // to only retain what's left - let min; - if n1 > n2 { - min = *n2; - *n1 = *n1 - *n2; - op2 = ops2.next(); - } else if n1 == n2 { - min = *n2; - op1 = ops1.next(); - op2 = ops2.next(); - } else { - min = *n1; - *n2 = *n2 - *n1; - op1 = ops1.next(); - } - operation1p = operation1p.retain(min); - operation2p = operation2p.retain(min); - }, - (Some(Op::Delete(n1)), Some(Op::Delete(n2))) => { - // Both operations delete the same string at the same position. We don't need - // to produce any operations, we just skip over the delete ops and handle the - // case that one operation deletes more than the other. - if n1 > n2 { - *n1 = *n2 - *n1; - op2 = ops2.next(); - } else if n1 == n2 { - op1 = ops1.next(); - op2 = ops2.next(); - } else { - *n2 = *n2 - *n1; - op1 = ops1.next(); - } - }, - (Some(Op::Delete(n1)), Some(Op::Retain(n2))) => { - let min; - if n1 > n2 { - min = *n2; - *n1 = *n1 - *n2; - op2 = ops2.next(); - } else if n1 == n2 { - min = *n1; - op1 = ops1.next(); - op2 = ops2.next(); - } else { - min = *n1; - *n2 = *n2 - *n1; - op1 = ops1.next(); - } - operation1p = operation1p.delete(min); - }, - (Some(Op::Retain(n1)), Some(Op::Delete(n2))) => { - let min; - if n1 > n2 { - min = *n2; - *n1 = *n1 - *n2; - op2 = ops2.next(); - } else if n1 == n2 { - min = *n1; - op1 = ops1.next(); - op2 = ops2.next(); - } else { - min = *n1; - *n2 = *n2 - *n1; - op1 = ops1.next(); - } - operation2p = operation2p.delete(min); - }, - } - } - - (operation1p, operation2p) - } -} - -#[cfg(test)] -mod test { - use super::{TextOperation, Op}; - - #[test] - fn test_build_retain() { - let op = TextOperation::new() - .retain(10); - assert_eq!(op.base_length, 10); - assert_eq!(op.target_length, 10); - assert_eq!(op.ops, vec![Op::Retain(10)]); - } - - #[test] - fn test_build_retain_null() { - let op = TextOperation::new() - .retain(0); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length, 0); - assert_eq!(op.ops, vec![]); - } - - #[test] - fn test_build_retain_merge() { - let op = TextOperation::new() - .retain(10) - .retain(20); - assert_eq!(op.base_length, 30); - assert_eq!(op.target_length, 30); - assert_eq!(op.ops, vec![Op::Retain(30)]); - } - - #[test] - fn test_build_retain_after_insert() { - let op = TextOperation::new() - .insert(String::from("hello")) - .retain(20); - assert_eq!(op.base_length, 20); - assert_eq!(op.target_length, 25); - assert_eq!(op.ops, vec![Op::Insert(String::from("hello")), Op::Retain(20)]); - } - - #[test] - fn test_build_retain_after_delete() { - let op = TextOperation::new() - .delete(10) - .retain(20); - assert_eq!(op.base_length, 30); - assert_eq!(op.target_length, 20); - assert_eq!(op.ops, vec![Op::Delete(10), Op::Retain(20)]); - } - - #[test] - fn test_build_insert() { - let op = TextOperation::new() - .insert(String::from("hello")); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length, 5); - assert_eq!(op.ops, vec![Op::Insert(String::from("hello"))]); - } - - #[test] - fn test_build_insert_null() { - let op = TextOperation::new() - .insert(String::from("")); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length,0); - assert_eq!(op.ops, vec![]); - } - - #[test] - fn test_build_insert_merge() { - let op = TextOperation::new() - .insert(String::from("hello")) - .insert(String::from(" world")); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length, 11); - assert_eq!(op.ops, vec![Op::Insert(String::from("hello world"))]); - } - - #[test] - fn test_build_insert_after_retain() { - let op = TextOperation::new() - .retain(10) - .insert(String::from("hello")); - assert_eq!(op.base_length, 10); - assert_eq!(op.target_length, 15); - assert_eq!(op.ops, vec![Op::Retain(10), Op::Insert(String::from("hello"))]); - } - - #[test] - fn test_build_insert_after_delete() { - let op = TextOperation::new() - .delete(10) - .insert(String::from("hello")); - assert_eq!(op.base_length, 10); - assert_eq!(op.target_length, 5); - assert_eq!(op.ops, vec![ - Op::Insert(String::from("hello")), - Op::Delete(10), - ]); - } - - #[test] - fn test_build_insert_after_delete_merge() { - let op = TextOperation::new() - .insert(String::from("hello")) - .delete(10) - .insert(String::from(" world")); - assert_eq!(op.base_length, 10); - assert_eq!(op.target_length, 11); - assert_eq!(op.ops, vec![ - Op::Insert(String::from("hello world")), - Op::Delete(10), - ]); - } - - #[test] - fn test_build_delete() { - let op = TextOperation::new() - .delete(17); - assert_eq!(op.base_length, 17); - assert_eq!(op.target_length, 0); - assert_eq!(op.ops, vec![Op::Delete(17)]); - } - - #[test] - fn test_build_delete_null() { - let op = TextOperation::new() - .delete(0); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length, 0); - assert_eq!(op.ops, vec![]); - } - - #[test] - fn test_build_delete_merge() { - let op = TextOperation::new() - .delete(5) - .delete(8); - assert_eq!(op.base_length, 13); - assert_eq!(op.target_length, 0); - assert_eq!(op.ops, vec![Op::Delete(13)]); - } - - #[test] - fn test_build_delete_after_insert() { - let op = TextOperation::new() - .insert(String::from("hello")) - .delete(8); - assert_eq!(op.base_length, 8); - assert_eq!(op.target_length, 5); - assert_eq!(op.ops, vec![Op::Insert(String::from("hello")), Op::Delete(8)]); - } - - #[test] - fn test_build_delete_after_retain() { - let op = TextOperation::new() - .retain(10) - .delete(8); - assert_eq!(op.base_length, 18); - assert_eq!(op.target_length, 10); - assert_eq!(op.ops, vec![Op::Retain(10), Op::Delete(8)]); - } - - #[test] - fn test_apply_retain() { - let op = TextOperation::new() - .retain(5); - assert_eq!( - op.apply("hello"), - Ok(String::from("hello"))); - } - - #[test] - fn test_apply_insert() { - let op = TextOperation::new() - .retain(5) - .insert(String::from(" cruel")) - .retain(6); - assert_eq!( - op.apply("hello world"), - Ok(String::from("hello cruel world"))); - } - - #[test] - fn test_apply_delete() { - let op = TextOperation::new() - .retain(5) - .delete(6) - .retain(6); - assert_eq!( - op.apply("hello cruel world"), - Ok(String::from("hello world"))); - } - - #[test] - fn test_apply_wrong_length() { - let op = TextOperation::new() - .retain(5) - .insert(String::from(" cruel")) - .retain(6); - assert_eq!( - op.apply("hello cruel world"), - Err(String::from("The operation's base length must be equal to the string's length"))); - } - - mod transform { - use super::{TextOperation}; - - const STARTING_STATE: &str ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - fn test_transform(o1: TextOperation, o2: TextOperation, exp1p: TextOperation, exp2p: TextOperation) { - // first check that the resulting operation is as expected.. - let (o1p, o2p) = TextOperation::transform(o1.clone(), o2.clone()); - assert_eq!((&o1p, &o2p), (&exp1p, &exp2p)); - - // then check that the definition of `transform` is satisfied, by applying to an - // arbitrary string - let input = &STARTING_STATE[0..o1.base_length]; - // B' composed with A - let first = o2p.apply(&o1.apply(&input).unwrap()).unwrap(); - // A' composed with B - let second = o1p.apply(&o2.apply(&input).unwrap()).unwrap(); - print!("{} -> {}\n", input, first); - assert_eq!(first, second); - } - - #[test] - fn test_transform_empty() { - test_transform( - TextOperation::new(), - TextOperation::new(), - TextOperation::new(), - TextOperation::new()); - } - - #[test] - fn test_transform_one_noop() { - test_transform( - TextOperation::new().retain(2).insert("123").retain(10), - TextOperation::new().retain(12), - TextOperation::new().retain(2).insert("123").retain(10), - TextOperation::new().retain(15)); - } - - #[test] - fn test_transform_two_inserts() { - test_transform( - TextOperation::new().insert("123"), - TextOperation::new().insert("567"), - TextOperation::new().insert("123").retain(3), - TextOperation::new().retain(3).insert("567")); - } - - #[test] - fn test_transform_two_retains() { - test_transform( - TextOperation::new().retain(10), - TextOperation::new().retain(10), - TextOperation::new().retain(10), - TextOperation::new().retain(10)); - } - - #[test] - fn test_transform_insert_two_different_spots() { - test_transform( - TextOperation::new().retain(5).insert("123").retain(10), - TextOperation::new().retain(10).insert("567").retain(5), - TextOperation::new().retain(5).insert("123").retain(13), - TextOperation::new().retain(13).insert("567").retain(5)); - } - - #[test] - fn test_transform_insert_two_different_spots_reversed() { - test_transform( - TextOperation::new().retain(10).insert("567").retain(5), - TextOperation::new().retain(5).insert("123").retain(10), - TextOperation::new().retain(13).insert("567").retain(5), - TextOperation::new().retain(5).insert("123").retain(13)); - } - - #[test] - fn test_transform_two_deletes() { - test_transform( - TextOperation::new().delete(10), - TextOperation::new().delete(10), - TextOperation::new(), - TextOperation::new()); - } - - #[test] - fn test_transform_delete_retain() { - test_transform( - TextOperation::new().retain(10), - TextOperation::new().delete(3).retain(7), - TextOperation::new().retain(7), - TextOperation::new().delete(3).retain(7)); - } - - #[test] - fn test_transform_retain_delete() { - test_transform( - TextOperation::new().delete(3).retain(7), - TextOperation::new().retain(10), - TextOperation::new().delete(3).retain(7), - TextOperation::new().retain(7)); - } - - #[test] - fn test_transform_delete_insert() { - test_transform( - TextOperation::new().delete(10), - TextOperation::new().retain(3).insert("123").retain(7), - TextOperation::new().delete(3).retain(3).delete(7), - TextOperation::new().insert("123")); - } - } -} From 8799636c1a4a5b0ae1acd55209433d3e75e37ee8 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Dec 2019 12:17:42 -0500 Subject: [PATCH 018/548] add a transform function for operations --- src/lib.rs | 1 + src/operation.rs | 129 +++++++++++++++++++++++++++++++++++++++++++++++ src/taskdb.rs | 25 +++------ 3 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 src/operation.rs diff --git a/src/lib.rs b/src/lib.rs index dd2163137..3ef1ecf6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,4 +2,5 @@ #![allow(dead_code)] mod errors; +mod operation; mod taskdb; diff --git a/src/operation.rs b/src/operation.rs new file mode 100644 index 000000000..3d8258b8d --- /dev/null +++ b/src/operation.rs @@ -0,0 +1,129 @@ +use chrono::{DateTime, Utc}; +use serde_json::Value; +use uuid::Uuid; + +#[derive(PartialEq, Clone, Debug)] +pub enum Operation { + Create { + uuid: Uuid, + }, + Update { + uuid: Uuid, + property: String, + value: Value, + timestamp: DateTime, + }, +} + +use Operation::*; + +impl Operation { + // Transform takes two operations A and B that happened concurrently and produces two + // operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This + // function is used to serialize operations in a process similar to a Git "rebase". + // + // * + // / \ + // op1 / \ op2 + // / \ + // * * + // + // this function "completes the diamond: + // + // * * + // \ / + // op2' \ / op1' + // \ / + // * + // + // such that applying op2' after op1 has the same effect as applying op1' after op2. This + // allows two different systems which have already applied op1 and op2, respectively, and thus + // reached different states, to return to the same state by applying op2' and op1', + // respectively. + pub fn transform( + operation1: Operation, + operation2: Operation, + ) -> (Option, Option) { + match (&operation1, &operation2) { + // Two creations of the same uuid reach the same state, so there's no need for any + // further operations to bring the state together. + (&Create { uuid: uuid1 }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => (None, None), + + // Two updates to the same property of the same task might conflict. + ( + &Update { + uuid: ref uuid1, + property: ref property1, + value: ref value1, + timestamp: ref timestamp1, + }, + &Update { + uuid: ref uuid2, + property: ref property2, + value: ref value2, + timestamp: ref timestamp2, + }, + ) if uuid1 == uuid2 && property1 == property2 => { + // if the value is the same, there's no conflict + if value1 == value2 { + (None, None) + } else if timestamp1 < timestamp2 { + // prefer the later modification + (None, Some(operation2)) + } else if timestamp1 > timestamp2 { + // prefer the later modification + (Some(operation1), None) + } else { + // arbitrarily resolve in favor of the first operation + (Some(operation1), None) + } + } + + // anything else is not a conflict of any sort, so return the operations unchanged + (_, _) => (Some(operation1), Some(operation2)), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::taskdb::DB; + + fn test_transform( + o1: Operation, + o2: Operation, + exp1p: Option, + exp2p: Option, + ) { + let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); + assert_eq!((&o1p, &o2p), (&exp1p, &exp2p)); + + // check that the two operation sequences have the same effect, enforcing the invariant of + // the transform function. + let mut db1 = DB::new(); + db1.apply(o1).unwrap(); + if let Some(o) = o2p { + db1.apply(o).unwrap(); + } + let mut db2 = DB::new(); + db2.apply(o2).unwrap(); + if let Some(o) = o1p { + db2.apply(o).unwrap(); + } + assert_eq!(db1.tasks(), db2.tasks()); + } + + #[test] + fn test_unrelated_create() { + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + test_transform( + Operation::Create { uuid: uuid1 }, + Operation::Create { uuid: uuid2 }, + Some(Operation::Create { uuid: uuid1 }), + Some(Operation::Create { uuid: uuid2 }), + ); + } +} diff --git a/src/taskdb.rs b/src/taskdb.rs index e5d8ee195..acc555c0b 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -1,24 +1,12 @@ use crate::errors::Error; -use chrono::{DateTime, Utc}; +use crate::operation::Operation; use serde_json::Value; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; -#[derive(PartialEq, Clone, Debug)] -enum Operation { - Create { - uuid: Uuid, - }, - Update { - uuid: Uuid, - property: String, - value: Value, - timestamp: DateTime, - }, -} - -struct DB { +#[derive(PartialEq, Debug)] +pub struct DB { // The current state, with all pending operations applied tasks: HashMap>, @@ -33,7 +21,7 @@ struct DB { impl DB { /// Create a new, empty database - fn new() -> DB { + pub fn new() -> DB { DB { tasks: HashMap::new(), base_version: 0, @@ -43,7 +31,7 @@ impl DB { /// Apply an operation to the DB. Aside from synchronization operations, this /// is the only way to modify the DB. - fn apply(&mut self, op: Operation) -> Result<(), Error> { + pub fn apply(&mut self, op: Operation) -> Result<(), Error> { match op { Operation::Create { uuid } => { match self.tasks.entry(uuid) { @@ -78,7 +66,7 @@ impl DB { /// Get a read-only reference to the underlying set of tasks. /// /// This API is temporary, but provides query access to the DB. - fn tasks(&self) -> &HashMap> { + pub fn tasks(&self) -> &HashMap> { &self.tasks } } @@ -86,6 +74,7 @@ impl DB { #[cfg(test)] mod tests { use super::*; + use chrono::Utc; use uuid::Uuid; #[test] From 0a2293a9c56721291f4c5153145f9a89946ab3a6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Dec 2019 14:31:37 -0500 Subject: [PATCH 019/548] use proptest to check invariants --- Cargo.lock | 246 +++++++++++++++++++++++++ Cargo.toml | 3 + src/lib.rs | 3 + src/operation.rs | 111 ++++++++++- src/taskdb.rs | 71 ++++--- tests/operation_transform_invariant.rs | 60 ++++++ 6 files changed, 448 insertions(+), 46 deletions(-) create mode 100644 tests/operation_transform_invariant.rs diff --git a/Cargo.lock b/Cargo.lock index c839c94c8..828a6238d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,29 @@ dependencies = [ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bit-set" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit-vec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "c2-chacha" version = "0.2.3" @@ -53,6 +76,14 @@ dependencies = [ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "failure" version = "0.1.6" @@ -73,6 +104,16 @@ dependencies = [ "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "getrandom" version = "0.1.13" @@ -88,6 +129,11 @@ name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.66" @@ -116,6 +162,7 @@ version = "0.1.0" dependencies = [ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -133,6 +180,30 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proptest" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "quote" version = "1.0.2" @@ -141,6 +212,24 @@ dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.7.2" @@ -153,6 +242,15 @@ dependencies = [ "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_chacha" version = "0.2.1" @@ -162,6 +260,19 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rand_core" version = "0.5.1" @@ -170,6 +281,14 @@ dependencies = [ "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -178,16 +297,96 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "regex-syntax" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rusty-fork" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ryu" version = "1.0.2" @@ -242,6 +441,19 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.42" @@ -266,6 +478,14 @@ dependencies = [ "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasi" version = "0.7.0" @@ -294,35 +514,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" +"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" "checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" +"checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" "checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" "checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" "checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/Cargo.toml b/Cargo.toml index 2862faab0..f5e337fa0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,6 @@ uuid = { version = "0.8.1", features = ["serde", "v4"] } serde_json = "1.0" chrono = "0.4.10" failure = {version = "0.1.5", features = ["derive"] } + +[dev-dependencies] +proptest = "0.9.4" diff --git a/src/lib.rs b/src/lib.rs index 3ef1ecf6e..98390d7c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,6 @@ mod errors; mod operation; mod taskdb; + +pub use operation::Operation; +pub use taskdb::DB; diff --git a/src/operation.rs b/src/operation.rs index 3d8258b8d..8aafbce5b 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -72,6 +72,7 @@ impl Operation { (None, Some(operation2)) } else if timestamp1 > timestamp2 { // prefer the later modification + //(Some(operation1), None) (Some(operation1), None) } else { // arbitrarily resolve in favor of the first operation @@ -89,6 +90,10 @@ impl Operation { mod test { use super::*; use crate::taskdb::DB; + use chrono::{Duration, Utc}; + + // note that `tests/operation_transform_invariant.rs` tests the transform function quite + // thoroughly, so this testing is light. fn test_transform( o1: Operation, @@ -102,14 +107,14 @@ mod test { // check that the two operation sequences have the same effect, enforcing the invariant of // the transform function. let mut db1 = DB::new(); - db1.apply(o1).unwrap(); + db1.apply(o1); if let Some(o) = o2p { - db1.apply(o).unwrap(); + db1.apply(o); } let mut db2 = DB::new(); - db2.apply(o2).unwrap(); + db2.apply(o2); if let Some(o) = o1p { - db2.apply(o).unwrap(); + db2.apply(o); } assert_eq!(db1.tasks(), db2.tasks()); } @@ -120,10 +125,100 @@ mod test { let uuid2 = Uuid::new_v4(); test_transform( - Operation::Create { uuid: uuid1 }, - Operation::Create { uuid: uuid2 }, - Some(Operation::Create { uuid: uuid1 }), - Some(Operation::Create { uuid: uuid2 }), + Create { uuid: uuid1 }, + Create { uuid: uuid2 }, + Some(Create { uuid: uuid1 }), + Some(Create { uuid: uuid2 }), + ); + } + + #[test] + fn test_related_updates_different_props() { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + test_transform( + Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp, + }, + Update { + uuid, + property: "def".into(), + value: false.into(), + timestamp, + }, + Some(Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp, + }), + Some(Update { + uuid, + property: "def".into(), + value: false.into(), + timestamp, + }), + ); + } + + #[test] + fn test_related_updates_same_prop() { + let uuid = Uuid::new_v4(); + let timestamp1 = Utc::now(); + let timestamp2 = timestamp1 + Duration::seconds(10); + + test_transform( + Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp: timestamp1, + }, + Update { + uuid, + property: "abc".into(), + value: false.into(), + timestamp: timestamp2, + }, + None, + Some(Update { + uuid, + property: "abc".into(), + value: false.into(), + timestamp: timestamp2, + }), + ); + } + + #[test] + fn test_related_updates_same_prop_same_time() { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + test_transform( + Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp, + }, + Update { + uuid, + property: "abc".into(), + value: false.into(), + timestamp, + }, + Some(Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp, + }), + None, ); } } diff --git a/src/taskdb.rs b/src/taskdb.rs index acc555c0b..3417aa020 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -1,11 +1,10 @@ -use crate::errors::Error; use crate::operation::Operation; use serde_json::Value; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub struct DB { // The current state, with all pending operations applied tasks: HashMap>, @@ -30,18 +29,15 @@ impl DB { } /// Apply an operation to the DB. Aside from synchronization operations, this - /// is the only way to modify the DB. - pub fn apply(&mut self, op: Operation) -> Result<(), Error> { + /// is the only way to modify the DB. In cases where an operation does not + /// make sense, this function will ignore the operation. + pub fn apply(&mut self, op: Operation) { match op { Operation::Create { uuid } => { - match self.tasks.entry(uuid) { - Entry::Occupied(_) => { - return Err(Error::DBError(format!("Task {} already exists", uuid))); - } - ent @ Entry::Vacant(_) => { - ent.or_insert(HashMap::new()); - } - }; + // insert if the task does not already exist + if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { + ent.or_insert(HashMap::new()); + } } Operation::Update { ref uuid, @@ -49,18 +45,13 @@ impl DB { ref value, timestamp: _, } => { - match self.tasks.get_mut(uuid) { - Some(task) => { - task.insert(property.clone(), value.clone()); - } - None => { - return Err(Error::DBError(format!("Task {} does not exist", uuid))); - } - }; + // update if this task exists, otherwise ignore + if let Some(task) = self.tasks.get_mut(uuid) { + task.insert(property.clone(), value.clone()); + } } }; self.operations.push(op); - Ok(()) } /// Get a read-only reference to the underlying set of tasks. @@ -82,7 +73,7 @@ mod tests { let mut db = DB::new(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; - db.apply(op.clone()).unwrap(); + db.apply(op.clone()); let mut exp = HashMap::new(); exp.insert(uuid, HashMap::new()); @@ -94,11 +85,14 @@ mod tests { fn test_apply_create_exists() { let mut db = DB::new(); let uuid = Uuid::new_v4(); - db.apply(Operation::Create { uuid }).unwrap(); - assert_eq!( - db.apply(Operation::Create { uuid }), - Err(Error::DBError(format!("Task {} already exists", uuid))) - ); + let op = Operation::Create { uuid }; + db.apply(op.clone()); + db.apply(op.clone()); + + let mut exp = HashMap::new(); + exp.insert(uuid, HashMap::new()); + assert_eq!(db.tasks(), &exp); + assert_eq!(db.operations, vec![op.clone(), op]); } #[test] @@ -106,14 +100,14 @@ mod tests { let mut db = DB::new(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; - db.apply(op1.clone()).unwrap(); + db.apply(op1.clone()); let op2 = Operation::Update { uuid, property: String::from("title"), value: Value::from("\"my task\""), timestamp: Utc::now(), }; - db.apply(op2.clone()).unwrap(); + db.apply(op2.clone()); let mut exp = HashMap::new(); let mut task = HashMap::new(); @@ -127,14 +121,15 @@ mod tests { fn test_apply_update_does_not_exist() { let mut db = DB::new(); let uuid = Uuid::new_v4(); - assert_eq!( - db.apply(Operation::Update { - uuid, - property: String::from("title"), - value: Value::from("\"my task\""), - timestamp: Utc::now(), - }), - Err(Error::DBError(format!("Task {} does not exist", uuid))) - ); + let op = Operation::Update { + uuid, + property: String::from("title"), + value: Value::from("\"my task\""), + timestamp: Utc::now(), + }; + db.apply(op.clone()); + + assert_eq!(db.tasks(), &HashMap::new()); + assert_eq!(db.operations, vec![op]); } } diff --git a/tests/operation_transform_invariant.rs b/tests/operation_transform_invariant.rs new file mode 100644 index 000000000..61b0d9816 --- /dev/null +++ b/tests/operation_transform_invariant.rs @@ -0,0 +1,60 @@ +use chrono::Utc; +use ot::Operation; +use ot::DB; +use proptest::prelude::*; +use uuid::Uuid; + +fn uuid_strategy() -> impl Strategy { + prop_oneof![ + Just(Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap()), + Just(Uuid::parse_str("56e0be07-c61f-494c-a54c-bdcfdd52d2a7").unwrap()), + Just(Uuid::parse_str("4b7ed904-f7b0-4293-8a10-ad452422c7b3").unwrap()), + Just(Uuid::parse_str("9bdd0546-07c8-4e1f-a9bc-9d6299f4773b").unwrap()), + ] +} + +fn operation_strategy() -> impl Strategy { + prop_oneof![ + uuid_strategy().prop_map(|uuid| Operation::Create { uuid }), + (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { + Operation::Update { + uuid, + property, + value: true.into(), + timestamp: Utc::now(), + } + }), + ] +} + +proptest! { + #[test] + fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { + let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); + + // check that the two operation sequences have the same effect, enforcing the invariant of + // the transform function. This needs some care as if either of the operations is + // an Update then we must ensure the task already exists in the DB. + let mut db1 = DB::new(); + + if let Operation::Update{ ref uuid, .. } = o1 { + db1.apply(Operation::Create{uuid: uuid.clone()}); + } + + if let Operation::Update{ ref uuid, .. } = o2 { + db1.apply(Operation::Create{uuid: uuid.clone()}); + } + + let mut db2 = db1.clone(); + + db1.apply(o1); + if let Some(o) = o2p { + db1.apply(o); + } + db2.apply(o2); + if let Some(o) = o1p { + db2.apply(o); + } + assert_eq!(db1.tasks(), db2.tasks()); + } +} From 59f4e6abd7ff4dfd7aa75e87826560c6e0569dc0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Dec 2019 22:46:10 -0500 Subject: [PATCH 020/548] actually support synchronization --- Cargo.lock | 2 ++ Cargo.toml | 3 +- src/lib.rs | 2 ++ src/operation.rs | 3 +- src/server.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ src/taskdb.rs | 65 ++++++++++++++++++++++++++++++++++++ tests/sync.rs | 59 ++++++++++++++++++++++++++++++++ 7 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/server.rs create mode 100644 tests/sync.rs diff --git a/Cargo.lock b/Cargo.lock index 828a6238d..7c27f39e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -163,6 +164,7 @@ dependencies = [ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index f5e337fa0..a50095725 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,9 @@ edition = "2018" [dependencies] uuid = { version = "0.8.1", features = ["serde", "v4"] } +serde = "1.0.104" serde_json = "1.0" -chrono = "0.4.10" +chrono = { version = "0.4.10", features = ["serde"] } failure = {version = "0.1.5", features = ["derive"] } [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 98390d7c2..c2c80cfef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,9 @@ mod errors; mod operation; +mod server; mod taskdb; pub use operation::Operation; +pub use server::Server; pub use taskdb::DB; diff --git a/src/operation.rs b/src/operation.rs index 8aafbce5b..29b42a51f 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -1,8 +1,9 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; use serde_json::Value; use uuid::Uuid; -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum Operation { Create { uuid: Uuid, diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 000000000..4a50d77a2 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,87 @@ +use std::collections::HashMap; + +type Blob = Vec; + +struct User { + // versions, indexed at v-1 + versions: Vec, + snapshots: HashMap, +} + +pub struct Server { + users: HashMap, +} + +pub enum VersionAdd { + // OK, version added + Ok, + // Rejected, must be based on the the given version + ExpectedVersion(u64), +} + +impl User { + fn new() -> User { + User { + versions: vec![], + snapshots: HashMap::new(), + } + } + + fn get_versions(&self, since_version: u64) -> Vec { + let last_version = self.versions.len(); + if last_version == since_version as usize { + return vec![]; + } + self.versions[since_version as usize..last_version] + .iter() + .map(|r| r.clone()) + .collect::>() + } + + fn add_version(&mut self, version: u64, blob: Blob) -> VersionAdd { + // of by one here: client wants to send version 1 first + let expected_version = self.versions.len() as u64 + 1; + if version != expected_version { + return VersionAdd::ExpectedVersion(expected_version); + } + self.versions.push(blob); + + VersionAdd::Ok + } + + fn add_snapshot(&mut self, version: u64, blob: Blob) { + self.snapshots.insert(version, blob); + } +} + +impl Server { + pub fn new() -> Server { + Server { + users: HashMap::new(), + } + } + + fn get_user_mut(&mut self, username: &str) -> &mut User { + self.users + .entry(username.to_string()) + .or_insert_with(User::new) + } + + /// Get a vector of all versions after `since_version` + pub fn get_versions(&self, username: &str, since_version: u64) -> Vec { + self.users + .get(username) + .map(|user| user.get_versions(since_version)) + .unwrap_or_else(|| vec![]) + } + + /// Add a new version. If the given version number is incorrect, this responds with the + /// appropriate version and expects the caller to try again. + pub fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> VersionAdd { + self.get_user_mut(username).add_version(version, blob) + } + + pub fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) { + self.get_user_mut(username).add_snapshot(version, blob); + } +} diff --git a/src/taskdb.rs b/src/taskdb.rs index 3417aa020..8af0bc823 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -1,7 +1,10 @@ use crate::operation::Operation; +use crate::server::{Server, VersionAdd}; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::hash_map::Entry; use std::collections::HashMap; +use std::str; use uuid::Uuid; #[derive(PartialEq, Debug, Clone)] @@ -18,6 +21,12 @@ pub struct DB { operations: Vec, } +#[derive(Serialize, Deserialize, Debug)] +struct Version { + version: u64, + operations: Vec, +} + impl DB { /// Create a new, empty database pub fn new() -> DB { @@ -60,6 +69,62 @@ impl DB { pub fn tasks(&self) -> &HashMap> { &self.tasks } + + /// Sync to the given server, pulling remote changes and pushing local changes. + pub fn sync(&mut self, username: &str, server: &mut Server) { + loop { + // first pull changes and "rebase" on top of them + let new_versions = server.get_versions(username, self.base_version); + for version_blob in new_versions { + let version_str = str::from_utf8(&version_blob).unwrap(); + let version: Version = serde_json::from_str(version_str).unwrap(); + assert_eq!(version.version, self.base_version + 1); + println!("applying version {:?} from server", version.version); + + self.apply_version(version); + } + + if self.operations.len() == 0 { + break; + } + + // now make a version of our local changes and push those + let new_version = Version { + version: self.base_version + 1, + operations: self.operations.clone(), + }; + let new_version_str = serde_json::to_string(&new_version).unwrap(); + println!("sending version {:?} to server", new_version.version); + if let VersionAdd::Ok = + server.add_version(username, new_version.version, new_version_str.into()) + { + break; + } + } + } + + fn apply_version(&mut self, mut version: Version) { + for server_op in version.operations.drain(..) { + let mut new_local_ops = Vec::with_capacity(self.operations.len()); + let mut svr_op = Some(server_op); + for local_op in self.operations.drain(..) { + if let Some(o) = svr_op { + let (new_server_op, new_local_op) = Operation::transform(o, local_op); + svr_op = new_server_op; + if let Some(o) = new_local_op { + new_local_ops.push(o); + } + } else { + new_local_ops.push(local_op); + } + } + if let Some(o) = svr_op { + self.apply(o); + } + self.operations = new_local_ops; + } + self.base_version = version.version; + } } #[cfg(test)] diff --git a/tests/sync.rs b/tests/sync.rs new file mode 100644 index 000000000..055f88779 --- /dev/null +++ b/tests/sync.rs @@ -0,0 +1,59 @@ +use chrono::Utc; +use ot::{Operation, Server, DB}; +use uuid::Uuid; + +#[test] +fn test_sync() { + let mut server = Server::new(); + + let mut db1 = DB::new(); + db1.sync("me", &mut server); + + let mut db2 = DB::new(); + db2.sync("me", &mut server); + + // make some changes in parallel to db1 and db2.. + let uuid1 = Uuid::new_v4(); + db1.apply(Operation::Create { uuid: uuid1 }); + db1.apply(Operation::Update { + uuid: uuid1, + property: "title".into(), + value: "my first task".into(), + timestamp: Utc::now(), + }); + + let uuid2 = Uuid::new_v4(); + db2.apply(Operation::Create { uuid: uuid2 }); + db2.apply(Operation::Update { + uuid: uuid2, + property: "title".into(), + value: "my second task".into(), + timestamp: Utc::now(), + }); + + // and synchronize those around + db1.sync("me", &mut server); + db2.sync("me", &mut server); + db1.sync("me", &mut server); + assert_eq!(db1.tasks(), db2.tasks()); + + // now make updates to the same task on both sides + db1.apply(Operation::Update { + uuid: uuid2, + property: "priority".into(), + value: "H".into(), + timestamp: Utc::now(), + }); + db2.apply(Operation::Update { + uuid: uuid2, + property: "project".into(), + value: "personal".into(), + timestamp: Utc::now(), + }); + + // and synchronize those around + db1.sync("me", &mut server); + db2.sync("me", &mut server); + db1.sync("me", &mut server); + assert_eq!(db1.tasks(), db2.tasks()); +} From 4a62413e218a745a63066361afe52c5ea081553b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Dec 2019 11:09:05 -0500 Subject: [PATCH 021/548] add TODO --- TODO.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 TODO.txt diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 000000000..7f07d5a70 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,18 @@ +* assign types to properties + - modifications to types don't commute the same way + - optimize this to simplify the transform function + - types: + - dependencies: set of uuids + - annotations: set of annotations (incl timestamps for uniqueness) + - tags: set of tags + - idea: Update takes a dotted path for property; store everything as a map + e.g., {uuid: true}, {timestamp: annotation}, {tag: true}; keep the + set-to-null-to-delete to remove +* add HTTP API +* implement snapshot requests +* implement backups +* implement client-side encryption +* design expiration + - need to be sure that create / delete operations don't get reversed +* cli tools +* prop testing for DB modifications From e5bd258e8412b564b6262e730c7bb4ea2269c2ed Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Dec 2019 11:09:37 -0500 Subject: [PATCH 022/548] comments about applying --- src/taskdb.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/taskdb.rs b/src/taskdb.rs index 8af0bc823..389f1d47c 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -104,6 +104,31 @@ impl DB { } fn apply_version(&mut self, mut version: Version) { + // The situation here is that the server has already applied all server operations, and we + // have already applied all local operations, so states have diverged by several + // operations. We need to figure out what operations to apply locally and on the server in + // order to return to the same state. + // + // Operational transforms provide this on an operation-by-operation basis. To break this + // down, we treat each server operation individually, in order. For each such operation, + // we start in this state: + // + // + // base state-* + // / \-server op + // * * + // local / \ / + // ops * * + // / \ / new + // * * local + // local / \ / ops + // state-* * + // new-\ / + // server op *-new local state + // + // This is slightly complicated by the fact that the transform function can return None, + // indicating no operation is required. If this happens for a local op, we can just omit + // it. If it happens for server op, then we must copy the remaining local ops. for server_op in version.operations.drain(..) { let mut new_local_ops = Vec::with_capacity(self.operations.len()); let mut svr_op = Some(server_op); From e83bdc28cdb0ec0c8d882920393cdb69ff31dbfa Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Dec 2019 11:50:05 -0500 Subject: [PATCH 023/548] use strings as values, with option to allow removing --- README.md | 6 +-- TODO.txt | 15 +++---- src/operation.rs | 36 ++++++++------- src/taskdb.rs | 61 +++++++++++++++++++++++--- tests/operation_transform_invariant.rs | 2 +- tests/sync.rs | 8 ++-- 6 files changed, 89 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 4c857eeb2..3ea1a229a 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,7 @@ The data model is only seen from the clients' perspective. ## Task Database The task database is composed of an un-ordered collection of tasks, each keyed by a UUID. -Each task has an arbitrary-sized set of key/value properties, with JSON values. -A property with a `null` value is considered equivalent to that property not being set on the task. +Each task has an arbitrary-sized set of key/value properties, with string values. Tasks are only created, never deleted. See below for details on how tasks can "expire" from the task database. @@ -31,7 +30,8 @@ Each operation has one of the forms The former form creates a new task. It is invalid to create a task that already exists. -The latter form updates the given property of the given task. +The latter form updates the given property of the given task, where property and value are both strings. +Value can also be `None` to indicate deletion of a property. It is invalid to update a task that does not exist. The timestamp on updates serves as additional metadata and is used to resolve conflicts. diff --git a/TODO.txt b/TODO.txt index 7f07d5a70..cdbf54749 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,13 +1,9 @@ * assign types to properties - - modifications to types don't commute the same way - - optimize this to simplify the transform function - - types: - - dependencies: set of uuids - - annotations: set of annotations (incl timestamps for uniqueness) - - tags: set of tags - - idea: Update takes a dotted path for property; store everything as a map - e.g., {uuid: true}, {timestamp: annotation}, {tag: true}; keep the - set-to-null-to-delete to remove + - db / operation model is just k/v, but formatted names can be used for + structure: + - dependencies: `dependency. = ""` + - annotations: `annotation. = "annotation"` + - tags: `tags. = ""` * add HTTP API * implement snapshot requests * implement backups @@ -16,3 +12,4 @@ - need to be sure that create / delete operations don't get reversed * cli tools * prop testing for DB modifications + - 'strict' mode to fail on application of any nonsense operations diff --git a/src/operation.rs b/src/operation.rs index 29b42a51f..78ac36058 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -1,17 +1,23 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use serde_json::Value; use uuid::Uuid; +/// An Operation defines a single change to the task database #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum Operation { - Create { - uuid: Uuid, - }, + /// Create a new task; if the task already exists in the DB. + /// + /// On application, if the task already exists, the operation does nothing. + Create { uuid: Uuid }, + + /// Update an existing task, setting the given property to the given value. If the value is + /// None, then the corresponding property is deleted. + /// + /// If the given task does not exist, the operation does nothing. Update { uuid: Uuid, property: String, - value: Value, + value: Option, timestamp: DateTime, }, } @@ -142,25 +148,25 @@ mod test { Update { uuid, property: "abc".into(), - value: true.into(), + value: Some("true".into()), timestamp, }, Update { uuid, property: "def".into(), - value: false.into(), + value: Some("false".into()), timestamp, }, Some(Update { uuid, property: "abc".into(), - value: true.into(), + value: Some("true".into()), timestamp, }), Some(Update { uuid, property: "def".into(), - value: false.into(), + value: Some("false".into()), timestamp, }), ); @@ -176,20 +182,20 @@ mod test { Update { uuid, property: "abc".into(), - value: true.into(), + value: Some("true".into()), timestamp: timestamp1, }, Update { uuid, property: "abc".into(), - value: false.into(), + value: Some("false".into()), timestamp: timestamp2, }, None, Some(Update { uuid, property: "abc".into(), - value: false.into(), + value: Some("false".into()), timestamp: timestamp2, }), ); @@ -204,19 +210,19 @@ mod test { Update { uuid, property: "abc".into(), - value: true.into(), + value: Some("true".into()), timestamp, }, Update { uuid, property: "abc".into(), - value: false.into(), + value: Some("false".into()), timestamp, }, Some(Update { uuid, property: "abc".into(), - value: true.into(), + value: Some("true".into()), timestamp, }), None, diff --git a/src/taskdb.rs b/src/taskdb.rs index 389f1d47c..1cd022a3d 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -1,16 +1,17 @@ use crate::operation::Operation; use crate::server::{Server, VersionAdd}; use serde::{Deserialize, Serialize}; -use serde_json::Value; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::str; use uuid::Uuid; +type TaskMap = HashMap; + #[derive(PartialEq, Debug, Clone)] pub struct DB { // The current state, with all pending operations applied - tasks: HashMap>, + tasks: HashMap, // The version at which `operations` begins base_version: u64, @@ -56,17 +57,24 @@ impl DB { } => { // update if this task exists, otherwise ignore if let Some(task) = self.tasks.get_mut(uuid) { - task.insert(property.clone(), value.clone()); + DB::apply_update(task, property, value); } } }; self.operations.push(op); } + fn apply_update(task: &mut TaskMap, property: &str, value: &Option) { + match value { + Some(ref val) => task.insert(property.to_string(), val.clone()), + None => task.remove(property), + }; + } + /// Get a read-only reference to the underlying set of tasks. /// /// This API is temporary, but provides query access to the DB. - pub fn tasks(&self) -> &HashMap> { + pub fn tasks(&self) -> &HashMap { &self.tasks } @@ -194,19 +202,58 @@ mod tests { let op2 = Operation::Update { uuid, property: String::from("title"), - value: Value::from("\"my task\""), + value: Some("my task".into()), timestamp: Utc::now(), }; db.apply(op2.clone()); let mut exp = HashMap::new(); let mut task = HashMap::new(); - task.insert(String::from("title"), Value::from("\"my task\"")); + task.insert(String::from("title"), String::from("my task")); exp.insert(uuid, task); assert_eq!(db.tasks(), &exp); assert_eq!(db.operations, vec![op1, op2]); } + #[test] + fn test_apply_create_update_delete_prop() { + let mut db = DB::new(); + let uuid = Uuid::new_v4(); + let op1 = Operation::Create { uuid }; + db.apply(op1.clone()); + + let op2 = Operation::Update { + uuid, + property: String::from("title"), + value: Some("my task".into()), + timestamp: Utc::now(), + }; + db.apply(op2.clone()); + + let op3 = Operation::Update { + uuid, + property: String::from("priority"), + value: Some("H".into()), + timestamp: Utc::now(), + }; + db.apply(op3.clone()); + + let op4 = Operation::Update { + uuid, + property: String::from("title"), + value: None, + timestamp: Utc::now(), + }; + db.apply(op4.clone()); + + let mut exp = HashMap::new(); + let mut task = HashMap::new(); + task.insert(String::from("priority"), String::from("H")); + exp.insert(uuid, task); + assert_eq!(db.tasks(), &exp); + assert_eq!(db.operations, vec![op1, op2, op3, op4]); + } + #[test] fn test_apply_update_does_not_exist() { let mut db = DB::new(); @@ -214,7 +261,7 @@ mod tests { let op = Operation::Update { uuid, property: String::from("title"), - value: Value::from("\"my task\""), + value: Some("my task".into()), timestamp: Utc::now(), }; db.apply(op.clone()); diff --git a/tests/operation_transform_invariant.rs b/tests/operation_transform_invariant.rs index 61b0d9816..a88fbeb79 100644 --- a/tests/operation_transform_invariant.rs +++ b/tests/operation_transform_invariant.rs @@ -20,7 +20,7 @@ fn operation_strategy() -> impl Strategy { Operation::Update { uuid, property, - value: true.into(), + value: Some("true".into()), timestamp: Utc::now(), } }), diff --git a/tests/sync.rs b/tests/sync.rs index 055f88779..ed71eb7c0 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -18,7 +18,7 @@ fn test_sync() { db1.apply(Operation::Update { uuid: uuid1, property: "title".into(), - value: "my first task".into(), + value: Some("my first task".into()), timestamp: Utc::now(), }); @@ -27,7 +27,7 @@ fn test_sync() { db2.apply(Operation::Update { uuid: uuid2, property: "title".into(), - value: "my second task".into(), + value: Some("my second task".into()), timestamp: Utc::now(), }); @@ -41,13 +41,13 @@ fn test_sync() { db1.apply(Operation::Update { uuid: uuid2, property: "priority".into(), - value: "H".into(), + value: Some("H".into()), timestamp: Utc::now(), }); db2.apply(Operation::Update { uuid: uuid2, property: "project".into(), - value: "personal".into(), + value: Some("personal".into()), timestamp: Utc::now(), }); From 41acb1fa1e7725d58f51508f3a121b31ff4be83d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Dec 2019 13:16:42 -0500 Subject: [PATCH 024/548] Add support for Delete operations --- README.md | 8 +- src/operation.rs | 61 +++++++++++++-- src/taskdb.rs | 102 +++++++++++++++++++------ tests/operation_transform_invariant.rs | 39 +++++++--- tests/sync.rs | 16 ++-- 5 files changed, 175 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 3ea1a229a..95d46455f 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,16 @@ See below for details on how tasks can "expire" from the task database. Every change to the task database is captured as an operation. Each operation has one of the forms * `Create(uuid)` + * `Delete(uuid)` * `Update(uuid, property, value, timestamp)` -The former form creates a new task. +The Create form creates a new task. It is invalid to create a task that already exists. -The latter form updates the given property of the given task, where property and value are both strings. +Similarly, the Delete form deletes an existing task. +It is invalid to delete a task that does not exist. + +The Update form updates the given property of the given task, where property and value are both strings. Value can also be `None` to indicate deletion of a property. It is invalid to update a task that does not exist. The timestamp on updates serves as additional metadata and is used to resolve conflicts. diff --git a/src/operation.rs b/src/operation.rs index 78ac36058..4fe44df21 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -5,11 +5,16 @@ use uuid::Uuid; /// An Operation defines a single change to the task database #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum Operation { - /// Create a new task; if the task already exists in the DB. + /// Create a new task. /// /// On application, if the task already exists, the operation does nothing. Create { uuid: Uuid }, + /// Delete an existing task. + /// + /// On application, if the task does not exist, the operation does nothing. + Delete { uuid: Uuid }, + /// Update an existing task, setting the given property to the given value. If the value is /// None, then the corresponding property is deleted. /// @@ -52,9 +57,36 @@ impl Operation { operation2: Operation, ) -> (Option, Option) { match (&operation1, &operation2) { - // Two creations of the same uuid reach the same state, so there's no need for any - // further operations to bring the state together. + // Two creations or deletions of the same uuid reach the same state, so there's no need + // for any further operations to bring the state together. (&Create { uuid: uuid1 }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => (None, None), + (&Delete { uuid: uuid1 }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => (None, None), + + // Given a create and a delete of the same task, one of the operations is invalid: the + // create implies the task does not exist, but the delete implies it exists. Somewhat + // arbitrarily, we prefer the Create + (&Create { uuid: uuid1 }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => { + (Some(operation1), None) + } + (&Delete { uuid: uuid1 }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => { + (None, Some(operation2)) + } + + // And again from an Update and a Create, prefer the Update + (&Update { uuid: uuid1, .. }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => { + (Some(operation1), None) + } + (&Create { uuid: uuid1 }, &Update { uuid: uuid2, .. }) if uuid1 == uuid2 => { + (None, Some(operation2)) + } + + // Given a delete and an update, prefer the delete + (&Update { uuid: uuid1, .. }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => { + (None, Some(operation2)) + } + (&Delete { uuid: uuid1 }, &Update { uuid: uuid2, .. }) if uuid1 == uuid2 => { + (Some(operation1), None) + } // Two updates to the same property of the same task might conflict. ( @@ -103,6 +135,7 @@ mod test { // thoroughly, so this testing is light. fn test_transform( + setup: Option, o1: Operation, o2: Operation, exp1p: Option, @@ -114,15 +147,23 @@ mod test { // check that the two operation sequences have the same effect, enforcing the invariant of // the transform function. let mut db1 = DB::new(); - db1.apply(o1); + if let Some(ref o) = setup { + db1.apply(o.clone()).unwrap(); + } + db1.apply(o1).unwrap(); if let Some(o) = o2p { - db1.apply(o); + db1.apply(o).unwrap(); } + let mut db2 = DB::new(); - db2.apply(o2); - if let Some(o) = o1p { - db2.apply(o); + if let Some(ref o) = setup { + db2.apply(o.clone()).unwrap(); } + db2.apply(o2).unwrap(); + if let Some(o) = o1p { + db2.apply(o).unwrap(); + } + assert_eq!(db1.tasks(), db2.tasks()); } @@ -132,6 +173,7 @@ mod test { let uuid2 = Uuid::new_v4(); test_transform( + None, Create { uuid: uuid1 }, Create { uuid: uuid2 }, Some(Create { uuid: uuid1 }), @@ -145,6 +187,7 @@ mod test { let timestamp = Utc::now(); test_transform( + Some(Create { uuid }), Update { uuid, property: "abc".into(), @@ -179,6 +222,7 @@ mod test { let timestamp2 = timestamp1 + Duration::seconds(10); test_transform( + Some(Create { uuid }), Update { uuid, property: "abc".into(), @@ -207,6 +251,7 @@ mod test { let timestamp = Utc::now(); test_transform( + Some(Create { uuid }), Update { uuid, property: "abc".into(), diff --git a/src/taskdb.rs b/src/taskdb.rs index 1cd022a3d..1c1cec9e9 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -1,3 +1,4 @@ +use crate::errors::Error; use crate::operation::Operation; use crate::server::{Server, VersionAdd}; use serde::{Deserialize, Serialize}; @@ -41,15 +42,30 @@ impl DB { /// Apply an operation to the DB. Aside from synchronization operations, this /// is the only way to modify the DB. In cases where an operation does not /// make sense, this function will ignore the operation. - pub fn apply(&mut self, op: Operation) { + pub fn apply(&mut self, op: Operation) -> Result<(), Error> { + if let err @ Err(_) = self.apply_op(&op) { + return err; + } + self.operations.push(op); + Ok(()) + } + + fn apply_op(&mut self, op: &Operation) -> Result<(), Error> { match op { - Operation::Create { uuid } => { + &Operation::Create { uuid } => { // insert if the task does not already exist if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { ent.or_insert(HashMap::new()); + } else { + return Err(Error::DBError(format!("Task {} already exists", uuid))); } } - Operation::Update { + &Operation::Delete { ref uuid } => { + if let None = self.tasks.remove(uuid) { + return Err(Error::DBError(format!("Task {} does not exist", uuid))); + } + } + &Operation::Update { ref uuid, ref property, ref value, @@ -57,18 +73,17 @@ impl DB { } => { // update if this task exists, otherwise ignore if let Some(task) = self.tasks.get_mut(uuid) { - DB::apply_update(task, property, value); + match value { + Some(ref val) => task.insert(property.to_string(), val.clone()), + None => task.remove(property), + }; + } else { + return Err(Error::DBError(format!("Task {} does not exist", uuid))); } } - }; - self.operations.push(op); - } + } - fn apply_update(task: &mut TaskMap, property: &str, value: &Option) { - match value { - Some(ref val) => task.insert(property.to_string(), val.clone()), - None => task.remove(property), - }; + Ok(()) } /// Get a read-only reference to the underlying set of tasks. @@ -152,7 +167,9 @@ impl DB { } } if let Some(o) = svr_op { - self.apply(o); + if let Err(e) = self.apply_op(&o) { + println!("Invalid operation when syncing: {} (ignored)", e); + } } self.operations = new_local_ops; } @@ -171,7 +188,7 @@ mod tests { let mut db = DB::new(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; - db.apply(op.clone()); + db.apply(op.clone()).unwrap(); let mut exp = HashMap::new(); exp.insert(uuid, HashMap::new()); @@ -184,13 +201,16 @@ mod tests { let mut db = DB::new(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; - db.apply(op.clone()); - db.apply(op.clone()); + db.apply(op.clone()).unwrap(); + assert_eq!( + db.apply(op.clone()).err().unwrap(), + Error::DBError(format!("Task {} already exists", uuid)) + ); let mut exp = HashMap::new(); exp.insert(uuid, HashMap::new()); assert_eq!(db.tasks(), &exp); - assert_eq!(db.operations, vec![op.clone(), op]); + assert_eq!(db.operations, vec![op]); } #[test] @@ -198,14 +218,14 @@ mod tests { let mut db = DB::new(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; - db.apply(op1.clone()); + db.apply(op1.clone()).unwrap(); let op2 = Operation::Update { uuid, property: String::from("title"), value: Some("my task".into()), timestamp: Utc::now(), }; - db.apply(op2.clone()); + db.apply(op2.clone()).unwrap(); let mut exp = HashMap::new(); let mut task = HashMap::new(); @@ -220,7 +240,7 @@ mod tests { let mut db = DB::new(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; - db.apply(op1.clone()); + db.apply(op1.clone()).unwrap(); let op2 = Operation::Update { uuid, @@ -228,7 +248,7 @@ mod tests { value: Some("my task".into()), timestamp: Utc::now(), }; - db.apply(op2.clone()); + db.apply(op2.clone()).unwrap(); let op3 = Operation::Update { uuid, @@ -236,7 +256,7 @@ mod tests { value: Some("H".into()), timestamp: Utc::now(), }; - db.apply(op3.clone()); + db.apply(op3.clone()).unwrap(); let op4 = Operation::Update { uuid, @@ -244,7 +264,7 @@ mod tests { value: None, timestamp: Utc::now(), }; - db.apply(op4.clone()); + db.apply(op4.clone()).unwrap(); let mut exp = HashMap::new(); let mut task = HashMap::new(); @@ -264,9 +284,41 @@ mod tests { value: Some("my task".into()), timestamp: Utc::now(), }; - db.apply(op.clone()); + assert_eq!( + db.apply(op).err().unwrap(), + Error::DBError(format!("Task {} does not exist", uuid)) + ); assert_eq!(db.tasks(), &HashMap::new()); - assert_eq!(db.operations, vec![op]); + assert_eq!(db.operations, vec![]); + } + + #[test] + fn test_apply_create_delete() { + let mut db = DB::new(); + let uuid = Uuid::new_v4(); + let op1 = Operation::Create { uuid }; + db.apply(op1.clone()).unwrap(); + + let op2 = Operation::Delete { uuid }; + db.apply(op2.clone()).unwrap(); + + assert_eq!(db.tasks(), &HashMap::new()); + assert_eq!(db.operations, vec![op1, op2]); + } + + #[test] + fn test_apply_delete_not_present() { + let mut db = DB::new(); + let uuid = Uuid::new_v4(); + + let op1 = Operation::Delete { uuid }; + assert_eq!( + db.apply(op1).err().unwrap(), + Error::DBError(format!("Task {} does not exist", uuid)) + ); + + assert_eq!(db.tasks(), &HashMap::new()); + assert_eq!(db.operations, vec![]); } } diff --git a/tests/operation_transform_invariant.rs b/tests/operation_transform_invariant.rs index a88fbeb79..2f73e3a30 100644 --- a/tests/operation_transform_invariant.rs +++ b/tests/operation_transform_invariant.rs @@ -16,6 +16,7 @@ fn uuid_strategy() -> impl Strategy { fn operation_strategy() -> impl Strategy { prop_oneof![ uuid_strategy().prop_map(|uuid| Operation::Create { uuid }), + uuid_strategy().prop_map(|uuid| Operation::Delete { uuid }), (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { Operation::Update { uuid, @@ -28,32 +29,50 @@ fn operation_strategy() -> impl Strategy { } proptest! { + #![proptest_config(ProptestConfig { + cases: 1024, .. ProptestConfig::default() + })] #[test] + // check that the two operation sequences have the same effect, enforcing the invariant of + // the transform function. fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); - // check that the two operation sequences have the same effect, enforcing the invariant of - // the transform function. This needs some care as if either of the operations is - // an Update then we must ensure the task already exists in the DB. let mut db1 = DB::new(); + // Ensure that any expected tasks already exist if let Operation::Update{ ref uuid, .. } = o1 { - db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); } if let Operation::Update{ ref uuid, .. } = o2 { - db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + } + + if let Operation::Delete{ ref uuid } = o1 { + let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + } + + if let Operation::Delete{ ref uuid } = o2 { + let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); } let mut db2 = db1.clone(); - db1.apply(o1); - if let Some(o) = o2p { - db1.apply(o); + // if applying the initial operations fail, that indicates the operation was invalid + // in the base state, so consider the case successful. + if let Err(_) = db1.apply(o1) { + return Ok(()); + } + if let Err(_) = db2.apply(o2) { + return Ok(()); + } + + if let Some(o) = o2p { + db1.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?; } - db2.apply(o2); if let Some(o) = o1p { - db2.apply(o); + db2.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?; } assert_eq!(db1.tasks(), db2.tasks()); } diff --git a/tests/sync.rs b/tests/sync.rs index ed71eb7c0..9f062b244 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -14,22 +14,24 @@ fn test_sync() { // make some changes in parallel to db1 and db2.. let uuid1 = Uuid::new_v4(); - db1.apply(Operation::Create { uuid: uuid1 }); + db1.apply(Operation::Create { uuid: uuid1 }).unwrap(); db1.apply(Operation::Update { uuid: uuid1, property: "title".into(), value: Some("my first task".into()), timestamp: Utc::now(), - }); + }) + .unwrap(); let uuid2 = Uuid::new_v4(); - db2.apply(Operation::Create { uuid: uuid2 }); + db2.apply(Operation::Create { uuid: uuid2 }).unwrap(); db2.apply(Operation::Update { uuid: uuid2, property: "title".into(), value: Some("my second task".into()), timestamp: Utc::now(), - }); + }) + .unwrap(); // and synchronize those around db1.sync("me", &mut server); @@ -43,13 +45,15 @@ fn test_sync() { property: "priority".into(), value: Some("H".into()), timestamp: Utc::now(), - }); + }) + .unwrap(); db2.apply(Operation::Update { uuid: uuid2, property: "project".into(), value: Some("personal".into()), timestamp: Utc::now(), - }); + }) + .unwrap(); // and synchronize those around db1.sync("me", &mut server); From 04f6b1d421b29c6849d6094532d161f0e35f7d7e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Dec 2019 13:37:50 -0500 Subject: [PATCH 025/548] add test for syncing deletes and creates --- TODO.txt | 2 +- tests/sync.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/TODO.txt b/TODO.txt index cdbf54749..056c26e24 100644 --- a/TODO.txt +++ b/TODO.txt @@ -12,4 +12,4 @@ - need to be sure that create / delete operations don't get reversed * cli tools * prop testing for DB modifications - - 'strict' mode to fail on application of any nonsense operations + - generate a non-failing sequence of operations with syncs interspersed diff --git a/tests/sync.rs b/tests/sync.rs index 9f062b244..733fc71ce 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -61,3 +61,56 @@ fn test_sync() { db1.sync("me", &mut server); assert_eq!(db1.tasks(), db2.tasks()); } + +#[test] +fn test_sync_create_delete() { + let mut server = Server::new(); + + let mut db1 = DB::new(); + db1.sync("me", &mut server); + + let mut db2 = DB::new(); + db2.sync("me", &mut server); + + // create and update a task.. + let uuid = Uuid::new_v4(); + db1.apply(Operation::Create { uuid }).unwrap(); + db1.apply(Operation::Update { + uuid: uuid, + property: "title".into(), + value: Some("my first task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and synchronize those around + db1.sync("me", &mut server); + db2.sync("me", &mut server); + db1.sync("me", &mut server); + assert_eq!(db1.tasks(), db2.tasks()); + + // delete and re-create the task on db1 + db1.apply(Operation::Delete { uuid }).unwrap(); + db1.apply(Operation::Create { uuid }).unwrap(); + db1.apply(Operation::Update { + uuid: uuid, + property: "title".into(), + value: Some("my second task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and on db2, update a property of the task + db2.apply(Operation::Update { + uuid: uuid, + property: "project".into(), + value: Some("personal".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + db1.sync("me", &mut server); + db2.sync("me", &mut server); + db1.sync("me", &mut server); + assert_eq!(db1.tasks(), db2.tasks()); +} From f6ffcc70392542556c6f625ff04c89c25ea0bb1a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 1 Jan 2020 17:45:43 -0500 Subject: [PATCH 026/548] add proptest for action sequences --- TODO.txt | 3 +- tests/sync_action_sequences.rs | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/sync_action_sequences.rs diff --git a/TODO.txt b/TODO.txt index 056c26e24..11597abd5 100644 --- a/TODO.txt +++ b/TODO.txt @@ -5,11 +5,10 @@ - annotations: `annotation. = "annotation"` - tags: `tags. = ""` * add HTTP API +* abstract server, storage, etc. into traits * implement snapshot requests * implement backups * implement client-side encryption * design expiration - need to be sure that create / delete operations don't get reversed * cli tools -* prop testing for DB modifications - - generate a non-failing sequence of operations with syncs interspersed diff --git a/tests/sync_action_sequences.rs b/tests/sync_action_sequences.rs new file mode 100644 index 000000000..dd4a7e364 --- /dev/null +++ b/tests/sync_action_sequences.rs @@ -0,0 +1,69 @@ +use chrono::Utc; +use ot::{Operation, Server, DB}; +use proptest::prelude::*; +use uuid::Uuid; + +#[derive(Debug)] +enum Action { + Op(Operation), + Sync, +} + +fn action_sequence_strategy() -> impl Strategy> { + // Create, Update, Delete, or Sync on client 1, 2, .., followed by a round of syncs + "([CUDS][123])*S1S2S3S1S2".prop_map(|seq| { + let uuid = Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap(); + seq.as_bytes() + .chunks(2) + .map(|action_on| { + let action = match action_on[0] { + b'C' => Action::Op(Operation::Create { uuid }), + b'U' => Action::Op(Operation::Update { + uuid, + property: "title".into(), + value: Some("foo".into()), + timestamp: Utc::now(), + }), + b'D' => Action::Op(Operation::Delete { uuid }), + b'S' => Action::Sync, + _ => unreachable!(), + }; + let acton = action_on[1] - b'1'; + (action, acton) + }) + .collect::>() + }) +} + +proptest! { + #[test] + // check that various sequences of operations on mulitple db's do not get the db's into an + // incompatible state. The main concern here is that there might be a sequence of create + // and delete operations that results in a task existing in one DB but not existing in + // another. So, the generated sequences focus on a single task UUID. + fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { + let mut server = Server::new(); + let mut dbs = [DB::new(), DB::new(), DB::new()]; + + for (action, db) in action_sequence { + println!("{:?} on db {}", action, db); + + let db = &mut dbs[db as usize]; + match action { + Action::Op(op) => { + if let Err(e) = db.apply(op) { + println!(" {:?} (ignored)", e); + } + }, + Action::Sync => db.sync("me", &mut server), + } + } + + println!("{:?}", dbs[0].tasks()); + println!("{:?}", dbs[1].tasks()); + println!("{:?}", dbs[2].tasks()); + + assert_eq!(dbs[0].tasks(), dbs[1].tasks()); + assert_eq!(dbs[1].tasks(), dbs[2].tasks()); + } +} From b898ec1fde3c616b73df80a3360672e17228f30c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 1 Jan 2020 18:22:01 -0500 Subject: [PATCH 027/548] add a Replica --- src/lib.rs | 1 + src/replica.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/taskdb.rs | 6 +-- 3 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 src/replica.rs diff --git a/src/lib.rs b/src/lib.rs index c2c80cfef..7ec4cd83c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod errors; mod operation; +mod replica; mod server; mod taskdb; diff --git a/src/replica.rs b/src/replica.rs new file mode 100644 index 000000000..c0b147997 --- /dev/null +++ b/src/replica.rs @@ -0,0 +1,99 @@ +use crate::errors::Error; +use crate::operation::Operation; +use crate::taskdb::DB; +use chrono::Utc; +use std::collections::HashMap; +use uuid::Uuid; + +/// A replica represents an instance of a user's task data. +struct Replica { + taskdb: Box, +} + +impl Replica { + pub fn new(taskdb: Box) -> Replica { + return Replica { taskdb }; + } + + /// Create a new task. The task must not already exist. + pub fn create_task(&mut self, uuid: Uuid) -> Result<(), Error> { + self.taskdb.apply(Operation::Create { uuid }) + } + + /// Delete a task. The task must exist. + pub fn delete_task(&mut self, uuid: Uuid) -> Result<(), Error> { + self.taskdb.apply(Operation::Delete { uuid }) + } + + /// Update an existing task. If the value is Some, the property is added or updated. If the + /// value is None, the property is deleted. It is not an error to delete a nonexistent + /// property. + pub fn update_task( + &mut self, + uuid: Uuid, + property: S1, + value: Option, + ) -> Result<(), Error> + where + S1: Into, + S2: Into, + { + self.taskdb.apply(Operation::Update { + uuid, + property: property.into(), + value: value.map(|v| v.into()), + timestamp: Utc::now(), + }) + } + + /// Get an existing task by its UUID + pub fn get_task(&self, uuid: &Uuid) -> Option<&HashMap> { + self.taskdb.tasks().get(&uuid) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::taskdb::DB; + use uuid::Uuid; + + #[test] + fn create() { + let mut rep = Replica::new(DB::new().into()); + let uuid = Uuid::new_v4(); + + rep.create_task(uuid.clone()).unwrap(); + assert_eq!(rep.get_task(&uuid), Some(&HashMap::new())); + } + + #[test] + fn delete() { + let mut rep = Replica::new(DB::new().into()); + let uuid = Uuid::new_v4(); + + rep.create_task(uuid.clone()).unwrap(); + rep.delete_task(uuid.clone()).unwrap(); + assert_eq!(rep.get_task(&uuid), None); + } + + #[test] + fn update() { + let mut rep = Replica::new(DB::new().into()); + let uuid = Uuid::new_v4(); + + rep.create_task(uuid.clone()).unwrap(); + rep.update_task(uuid.clone(), "title", Some("snarsblat")) + .unwrap(); + let mut task = HashMap::new(); + task.insert("title".into(), "snarsblat".into()); + assert_eq!(rep.get_task(&uuid), Some(&task)); + } + + #[test] + fn get_does_not_exist() { + let rep = Replica::new(DB::new().into()); + let uuid = Uuid::new_v4(); + assert_eq!(rep.get_task(&uuid), None); + } +} diff --git a/src/taskdb.rs b/src/taskdb.rs index 1c1cec9e9..34506450d 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -39,9 +39,9 @@ impl DB { } } - /// Apply an operation to the DB. Aside from synchronization operations, this - /// is the only way to modify the DB. In cases where an operation does not - /// make sense, this function will ignore the operation. + /// Apply an operation to the DB. Aside from synchronization operations, this is the only way + /// to modify the DB. In cases where an operation does not make sense, this function will do + /// nothing and return an error (but leave the DB in a consistent state). pub fn apply(&mut self, op: Operation) -> Result<(), Error> { if let err @ Err(_) = self.apply_op(&op) { return err; From e17943d878bf548a061b3221b638810c098850fb Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 1 Jan 2020 18:52:40 -0500 Subject: [PATCH 028/548] add a simple CLI --- Cargo.lock | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + TODO.txt | 3 ++- src/bin/task.rs | 39 +++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/replica.rs | 7 +++++- 6 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/bin/task.rs diff --git a/Cargo.lock b/Cargo.lock index 7c27f39e5..e2fd2d1fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,22 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "autocfg" version = "0.1.7" @@ -77,6 +94,20 @@ dependencies = [ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -162,6 +193,7 @@ name = "ot" version = "0.1.0" dependencies = [ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", @@ -422,6 +454,11 @@ dependencies = [ "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" version = "1.0.11" @@ -456,6 +493,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.42" @@ -466,6 +511,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.2.0" @@ -480,6 +530,11 @@ dependencies = [ "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -513,6 +568,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" @@ -524,6 +581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" "checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" @@ -564,12 +622,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" "checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" "checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" diff --git a/Cargo.toml b/Cargo.toml index a50095725..084a4ae26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ serde = "1.0.104" serde_json = "1.0" chrono = { version = "0.4.10", features = ["serde"] } failure = {version = "0.1.5", features = ["derive"] } +clap = "~2.33.0" [dev-dependencies] proptest = "0.9.4" diff --git a/TODO.txt b/TODO.txt index 11597abd5..8cf4eba45 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,10 +1,11 @@ -* assign types to properties +* assign types to properties in Replica - db / operation model is just k/v, but formatted names can be used for structure: - dependencies: `dependency. = ""` - annotations: `annotation. = "annotation"` - tags: `tags. = ""` * add HTTP API +* add pending-task indexing to Replica * abstract server, storage, etc. into traits * implement snapshot requests * implement backups diff --git a/src/bin/task.rs b/src/bin/task.rs new file mode 100644 index 000000000..f83509bbf --- /dev/null +++ b/src/bin/task.rs @@ -0,0 +1,39 @@ +extern crate clap; +use clap::{App, Arg, SubCommand}; +use ot::{Replica, DB}; +use uuid::Uuid; + +fn main() { + let matches = App::new("Rask") + .version("0.1") + .author("Dustin J. Mitchell ") + .about("Replacement for TaskWarrior") + .subcommand( + SubCommand::with_name("add") + .about("adds a task") + .arg(Arg::with_name("title").help("task title").required(true)), + ) + .subcommand(SubCommand::with_name("list").about("lists tasks")) + .get_matches(); + + let mut replica = Replica::new(DB::new().into()); + + match matches.subcommand() { + ("add", Some(matches)) => { + let uuid = Uuid::new_v4(); + replica.create_task(uuid).unwrap(); + replica + .update_task(uuid, "title", Some(matches.value_of("title").unwrap())) + .unwrap(); + } + ("list", _) => { + for task in replica.all_tasks() { + println!("{:?}", task); + } + } + ("", None) => { + unreachable!(); + } + _ => unreachable!(), + }; +} diff --git a/src/lib.rs b/src/lib.rs index 7ec4cd83c..474bf3acd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,5 +8,6 @@ mod server; mod taskdb; pub use operation::Operation; +pub use replica::Replica; pub use server::Server; pub use taskdb::DB; diff --git a/src/replica.rs b/src/replica.rs index c0b147997..44a05a984 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use uuid::Uuid; /// A replica represents an instance of a user's task data. -struct Replica { +pub struct Replica { taskdb: Box, } @@ -46,6 +46,11 @@ impl Replica { }) } + /// Get all tasks as an iterator of (&Uuid, &HashMap) + pub fn all_tasks(&self) -> impl Iterator)> { + self.taskdb.tasks().iter() + } + /// Get an existing task by its UUID pub fn get_task(&self, uuid: &Uuid) -> Option<&HashMap> { self.taskdb.tasks().get(&uuid) From 752d05e96bbff8d9be67fce802726c44e3dd06b7 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 1 Jan 2020 19:57:01 -0500 Subject: [PATCH 029/548] merge README --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 84d7028fa..313270718 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -<<<<<<< HEAD Sketch for a Taskd Replacement ------------------------------ @@ -107,10 +106,3 @@ TBD ## Recurrence TBD - -======= -At the moment, this is sort of an make-work project to re-implement Taskwarrior in Rust. - -There's no great reason to do so, and lots of reasons not to. -But it's a nice way to practice some "basic" Rust that does not exercise all of the language's more esoteric features. ->>>>>>> 93ce28ed15d11ba601765933f95756e7fb76d2e3 From 727db7f66910c27fd8a4f1b274a4fbbddb3c84f9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 2 Jan 2020 21:08:17 -0500 Subject: [PATCH 030/548] name taskwarrior-rest --- Cargo.lock | 26 +++++++++++++------------- Cargo.toml | 2 +- src/bin/task.rs | 2 +- src/tdb2/ff4.rs | 3 ++- tests/operation_transform_invariant.rs | 2 +- tests/parse.rs | 2 +- tests/sync.rs | 2 +- tests/sync_action_sequences.rs | 2 +- 8 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a14bb43c..6b40737c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,19 +366,6 @@ dependencies = [ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rask" -version = "0.1.0" -dependencies = [ - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "rdrand" version = "0.4.0" @@ -480,6 +467,19 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "taskwarrior-rust" +version = "0.1.0" +dependencies = [ + "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tempfile" version = "3.1.0" diff --git a/Cargo.toml b/Cargo.toml index 333de8110..f61df728c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rask" +name = "taskwarrior-rust" version = "0.1.0" authors = ["Dustin J. Mitchell "] edition = "2018" diff --git a/src/bin/task.rs b/src/bin/task.rs index 88cff9c1e..841a04e6c 100644 --- a/src/bin/task.rs +++ b/src/bin/task.rs @@ -1,6 +1,6 @@ extern crate clap; use clap::{App, Arg, SubCommand}; -use rask::{Replica, DB}; +use taskwarrior_rust::{Replica, DB}; use uuid::Uuid; fn main() { diff --git a/src/tdb2/ff4.rs b/src/tdb2/ff4.rs index 9f92c63c6..fd554ed6e 100644 --- a/src/tdb2/ff4.rs +++ b/src/tdb2/ff4.rs @@ -89,7 +89,8 @@ fn decode(value: String) -> String { /// Parse an "FF4" formatted task line. From Task::parse in Taskwarrior's src/Task.cpp. /// -/// While Taskwarrior supports additional formats, this is the only format supported by rask. +/// While Taskwarrior supports additional formats, this is the only format supported by +/// taskwarrior_rust. pub(super) fn parse_ff4(line: &str) -> Fallible { let mut pig = Pig::new(line.as_bytes()); let mut builder = TaskBuilder::new(); diff --git a/tests/operation_transform_invariant.rs b/tests/operation_transform_invariant.rs index e0f67b9de..8c7dd39af 100644 --- a/tests/operation_transform_invariant.rs +++ b/tests/operation_transform_invariant.rs @@ -1,6 +1,6 @@ use chrono::Utc; use proptest::prelude::*; -use rask::{Operation, DB}; +use taskwarrior_rust::{Operation, DB}; use uuid::Uuid; fn uuid_strategy() -> impl Strategy { diff --git a/tests/parse.rs b/tests/parse.rs index d054d5c6e..e84aa00d1 100644 --- a/tests/parse.rs +++ b/tests/parse.rs @@ -6,7 +6,7 @@ use std::io::BufReader; fn test_parse() { let filename = "tests/data/tdb2-test.data"; let file = File::open(filename).unwrap(); - let tasks = rask::parse(filename, BufReader::new(file)).unwrap(); + let tasks = taskwarrior_rust::parse(filename, BufReader::new(file)).unwrap(); assert_eq!( tasks[0].description, "https://phabricator.services.example.com/D7364 [taskgraph] Download debian packages" diff --git a/tests/sync.rs b/tests/sync.rs index 4055a46ee..76049aed0 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -1,5 +1,5 @@ use chrono::Utc; -use rask::{Operation, Server, DB}; +use taskwarrior_rust::{Operation, Server, DB}; use uuid::Uuid; #[test] diff --git a/tests/sync_action_sequences.rs b/tests/sync_action_sequences.rs index bba2a0631..ab02342ef 100644 --- a/tests/sync_action_sequences.rs +++ b/tests/sync_action_sequences.rs @@ -1,6 +1,6 @@ use chrono::Utc; use proptest::prelude::*; -use rask::{Operation, Server, DB}; +use taskwarrior_rust::{Operation, Server, DB}; use uuid::Uuid; #[derive(Debug)] From e228c99b8319bc4ee5d66eb0b36ac45faa1f735c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 5 Jan 2020 13:17:07 -0500 Subject: [PATCH 031/548] partial refactor to separate taskdb and storage --- src/lib.rs | 1 + src/operation.rs | 2 +- src/replica.rs | 21 ++-- src/taskdb.rs | 142 +++++++++++++++---------- src/taskstorage/mod.rs | 100 +++++++++++++++++ tests/operation_transform_invariant.rs | 2 +- tests/sync.rs | 8 +- tests/sync_action_sequences.rs | 10 +- 8 files changed, 208 insertions(+), 78 deletions(-) create mode 100644 src/taskstorage/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 8ead13f9d..e5374f4f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ mod replica; mod server; mod task; mod taskdb; +mod taskstorage; mod tdb2; pub use operation::Operation; diff --git a/src/operation.rs b/src/operation.rs index 4fe44df21..f21c5b465 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -164,7 +164,7 @@ mod test { db2.apply(o).unwrap(); } - assert_eq!(db1.tasks(), db2.tasks()); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); } #[test] diff --git a/src/replica.rs b/src/replica.rs index 419dbf2be..3c98c2db1 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -1,8 +1,8 @@ use crate::operation::Operation; use crate::taskdb::DB; +use crate::taskstorage::TaskMap; use chrono::Utc; use failure::Fallible; -use std::collections::HashMap; use uuid::Uuid; /// A replica represents an instance of a user's task data. @@ -47,13 +47,18 @@ impl Replica { } /// Get all tasks as an iterator of (&Uuid, &HashMap) - pub fn all_tasks(&self) -> impl Iterator)> { - self.taskdb.tasks().iter() + pub fn all_tasks<'a>(&'a self) -> impl Iterator + 'a { + self.taskdb.all_tasks() + } + + /// Get the UUIDs of all tasks + pub fn all_task_uuids<'a>(&'a self) -> impl Iterator + 'a { + self.taskdb.all_task_uuids() } /// Get an existing task by its UUID - pub fn get_task(&self, uuid: &Uuid) -> Option<&HashMap> { - self.taskdb.tasks().get(&uuid) + pub fn get_task(&self, uuid: &Uuid) -> Option { + self.taskdb.get_task(&uuid) } } @@ -69,7 +74,7 @@ mod tests { let uuid = Uuid::new_v4(); rep.create_task(uuid.clone()).unwrap(); - assert_eq!(rep.get_task(&uuid), Some(&HashMap::new())); + assert_eq!(rep.get_task(&uuid), Some(TaskMap::new())); } #[test] @@ -90,9 +95,9 @@ mod tests { rep.create_task(uuid.clone()).unwrap(); rep.update_task(uuid.clone(), "title", Some("snarsblat")) .unwrap(); - let mut task = HashMap::new(); + let mut task = TaskMap::new(); task.insert("title".into(), "snarsblat".into()); - assert_eq!(rep.get_task(&uuid), Some(&task)); + assert_eq!(rep.get_task(&uuid), Some(task)); } #[test] diff --git a/src/taskdb.rs b/src/taskdb.rs index 103b9f4e6..8f77e5fed 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -1,27 +1,16 @@ use crate::errors::Error; use crate::operation::Operation; use crate::server::{Server, VersionAdd}; +use crate::taskstorage::{InMemoryStorage, TaskMap}; use failure::Fallible; use serde::{Deserialize, Serialize}; -use std::collections::hash_map::Entry; use std::collections::HashMap; use std::str; use uuid::Uuid; -type TaskMap = HashMap; - -#[derive(PartialEq, Debug, Clone)] +#[derive(Debug, Clone)] pub struct DB { - // The current state, with all pending operations applied - tasks: HashMap, - - // The version at which `operations` begins - base_version: u64, - - // Operations applied since `base_version`, in order. - // - // INVARIANT: Given a snapshot at `base_version`, applying these operations produces `tasks`. - operations: Vec, + storage: InMemoryStorage, } #[derive(Serialize, Deserialize, Debug)] @@ -34,9 +23,7 @@ impl DB { /// Create a new, empty database pub fn new() -> DB { DB { - tasks: HashMap::new(), - base_version: 0, - operations: vec![], + storage: InMemoryStorage::new(), } } @@ -47,7 +34,7 @@ impl DB { if let err @ Err(_) = self.apply_op(&op) { return err; } - self.operations.push(op); + self.storage.add_operation(op); Ok(()) } @@ -55,14 +42,12 @@ impl DB { match op { &Operation::Create { uuid } => { // insert if the task does not already exist - if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { - ent.or_insert(HashMap::new()); - } else { + if !self.storage.create_task(uuid, HashMap::new()) { return Err(Error::DBError(format!("Task {} already exists", uuid)).into()); } } &Operation::Delete { ref uuid } => { - if let None = self.tasks.remove(uuid) { + if !self.storage.delete_task(uuid) { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } } @@ -73,11 +58,13 @@ impl DB { timestamp: _, } => { // update if this task exists, otherwise ignore - if let Some(task) = self.tasks.get_mut(uuid) { + if let Some(task) = self.storage.get_task(uuid) { + let mut task = task.clone(); match value { Some(ref val) => task.insert(property.to_string(), val.clone()), None => task.remove(property), }; + self.storage.set_task(uuid.clone(), task); } else { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } @@ -87,41 +74,55 @@ impl DB { Ok(()) } - /// Get a read-only reference to the underlying set of tasks. - /// - /// This API is temporary, but provides query access to the DB. - pub fn tasks(&self) -> &HashMap { - &self.tasks + /// Get all tasks. This is not a terribly efficient operation. + pub fn all_tasks<'a>(&'a self) -> impl Iterator + 'a { + self.all_task_uuids() + .map(move |u| (u, self.get_task(&u).unwrap())) + } + + /// Get the UUIDs of all tasks + pub fn all_task_uuids<'a>(&'a self) -> impl Iterator + 'a { + self.storage.get_task_uuids() + } + + /// Get a single task, by uuid. + pub fn get_task(&self, uuid: &Uuid) -> Option { + self.storage.get_task(uuid) } /// Sync to the given server, pulling remote changes and pushing local changes. pub fn sync(&mut self, username: &str, server: &mut Server) { + // retry synchronizing until the server accepts our version (this allows for races between + // replicas trying to sync to the same server) loop { // first pull changes and "rebase" on top of them - let new_versions = server.get_versions(username, self.base_version); + let new_versions = server.get_versions(username, self.storage.base_version()); for version_blob in new_versions { let version_str = str::from_utf8(&version_blob).unwrap(); let version: Version = serde_json::from_str(version_str).unwrap(); - assert_eq!(version.version, self.base_version + 1); + assert_eq!(version.version, self.storage.base_version() + 1); println!("applying version {:?} from server", version.version); self.apply_version(version); } - if self.operations.len() == 0 { + let operations: Vec = self.storage.operations().map(|o| o.clone()).collect(); + if operations.len() == 0 { + // nothing to sync back to the server.. break; } // now make a version of our local changes and push those let new_version = Version { - version: self.base_version + 1, - operations: self.operations.clone(), + version: self.storage.base_version() + 1, + operations: operations, }; let new_version_str = serde_json::to_string(&new_version).unwrap(); println!("sending version {:?} to server", new_version.version); if let VersionAdd::Ok = server.add_version(username, new_version.version, new_version_str.into()) { + self.storage.local_operations_synced(new_version.version); break; } } @@ -153,10 +154,12 @@ impl DB { // This is slightly complicated by the fact that the transform function can return None, // indicating no operation is required. If this happens for a local op, we can just omit // it. If it happens for server op, then we must copy the remaining local ops. + let mut local_operations: Vec = + self.storage.operations().map(|o| o.clone()).collect(); for server_op in version.operations.drain(..) { - let mut new_local_ops = Vec::with_capacity(self.operations.len()); + let mut new_local_ops = Vec::with_capacity(local_operations.len()); let mut svr_op = Some(server_op); - for local_op in self.operations.drain(..) { + for local_op in local_operations.drain(..) { if let Some(o) = svr_op { let (new_server_op, new_local_op) = Operation::transform(o, local_op); svr_op = new_server_op; @@ -172,9 +175,32 @@ impl DB { println!("Invalid operation when syncing: {} (ignored)", e); } } - self.operations = new_local_ops; + local_operations = new_local_ops; } - self.base_version = version.version; + self.storage + .update_version(version.version, local_operations); + } + + // functions for supporting tests + + pub fn sorted_tasks(&self) -> Vec<(Uuid, Vec<(String, String)>)> { + let mut res: Vec<(Uuid, Vec<(String, String)>)> = self + .all_tasks() + .map(|(u, t)| { + let mut t = t + .iter() + .map(|(p, v)| (p.clone(), v.clone())) + .collect::>(); + t.sort(); + (u, t) + }) + .collect(); + res.sort(); + res + } + + pub fn operations(&self) -> Vec { + self.storage.operations().map(|o| o.clone()).collect() } } @@ -191,10 +217,8 @@ mod tests { let op = Operation::Create { uuid }; db.apply(op.clone()).unwrap(); - let mut exp = HashMap::new(); - exp.insert(uuid, HashMap::new()); - assert_eq!(db.tasks(), &exp); - assert_eq!(db.operations, vec![op]); + assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]); + assert_eq!(db.operations(), vec![op]); } #[test] @@ -208,10 +232,8 @@ mod tests { format!("Task Database Error: Task {} already exists", uuid) ); - let mut exp = HashMap::new(); - exp.insert(uuid, HashMap::new()); - assert_eq!(db.tasks(), &exp); - assert_eq!(db.operations, vec![op]); + assert_eq!(db.sorted_tasks(), vec![(uuid, vec![])]); + assert_eq!(db.operations(), vec![op]); } #[test] @@ -228,12 +250,11 @@ mod tests { }; db.apply(op2.clone()).unwrap(); - let mut exp = HashMap::new(); - let mut task = HashMap::new(); - task.insert(String::from("title"), String::from("my task")); - exp.insert(uuid, task); - assert_eq!(db.tasks(), &exp); - assert_eq!(db.operations, vec![op1, op2]); + assert_eq!( + db.sorted_tasks(), + vec![(uuid, vec![("title".into(), "my task".into())])] + ); + assert_eq!(db.operations(), vec![op1, op2]); } #[test] @@ -271,8 +292,11 @@ mod tests { let mut task = HashMap::new(); task.insert(String::from("priority"), String::from("H")); exp.insert(uuid, task); - assert_eq!(db.tasks(), &exp); - assert_eq!(db.operations, vec![op1, op2, op3, op4]); + assert_eq!( + db.sorted_tasks(), + vec![(uuid, vec![("priority".into(), "H".into())])] + ); + assert_eq!(db.operations(), vec![op1, op2, op3, op4]); } #[test] @@ -290,8 +314,8 @@ mod tests { format!("Task Database Error: Task {} does not exist", uuid) ); - assert_eq!(db.tasks(), &HashMap::new()); - assert_eq!(db.operations, vec![]); + assert_eq!(db.sorted_tasks(), vec![]); + assert_eq!(db.operations(), vec![]); } #[test] @@ -304,8 +328,8 @@ mod tests { let op2 = Operation::Delete { uuid }; db.apply(op2.clone()).unwrap(); - assert_eq!(db.tasks(), &HashMap::new()); - assert_eq!(db.operations, vec![op1, op2]); + assert_eq!(db.sorted_tasks(), vec![]); + assert_eq!(db.operations(), vec![op1, op2]); } #[test] @@ -319,7 +343,7 @@ mod tests { format!("Task Database Error: Task {} does not exist", uuid) ); - assert_eq!(db.tasks(), &HashMap::new()); - assert_eq!(db.operations, vec![]); + assert_eq!(db.sorted_tasks(), vec![]); + assert_eq!(db.operations(), vec![]); } } diff --git a/src/taskstorage/mod.rs b/src/taskstorage/mod.rs new file mode 100644 index 000000000..bbc7707a2 --- /dev/null +++ b/src/taskstorage/mod.rs @@ -0,0 +1,100 @@ +use crate::operation::Operation; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use uuid::Uuid; + +pub type TaskMap = HashMap; + +#[derive(PartialEq, Debug, Clone)] +pub struct InMemoryStorage { + // The current state, with all pending operations applied + tasks: HashMap, + + // The version at which `operations` begins + base_version: u64, + + // Operations applied since `base_version`, in order. + // + // INVARIANT: Given a snapshot at `base_version`, applying these operations produces `tasks`. + operations: Vec, +} + +impl InMemoryStorage { + pub fn new() -> InMemoryStorage { + InMemoryStorage { + tasks: HashMap::new(), + base_version: 0, + operations: vec![], + } + } + + /// Get an (immutable) task, if it is in the storage + pub fn get_task(&self, uuid: &Uuid) -> Option { + match self.tasks.get(uuid) { + None => None, + Some(t) => Some(t.clone()), + } + } + + /// Create a task, only if it does not already exist. Returns true if + /// the task was created (did not already exist). + pub fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> bool { + if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { + ent.or_insert(task); + true + } else { + false + } + } + + /// Set a task, overwriting any existing task. + pub fn set_task(&mut self, uuid: Uuid, task: TaskMap) { + self.tasks.insert(uuid, task); + } + + /// Delete a task, if it exists. Returns true if the task was deleted (already existed) + pub fn delete_task(&mut self, uuid: &Uuid) -> bool { + if let Some(_) = self.tasks.remove(uuid) { + true + } else { + false + } + } + + pub fn get_task_uuids<'a>(&'a self) -> impl Iterator + 'a { + self.tasks.keys().map(|u| u.clone()) + } + + /// Add an operation to the list of operations in the storage. Note that this merely *stores* + /// the operation; it is up to the TaskDB to apply it. + pub fn add_operation(&mut self, op: Operation) { + self.operations.push(op); + } + + /// Get the current base_version for this storage -- the last version synced from the server. + pub fn base_version(&self) -> u64 { + return self.base_version; + } + + /// Get the current set of outstanding operations (operations that have not been sync'd to the + /// server yet) + pub fn operations(&self) -> impl Iterator { + self.operations.iter() + } + + /// Apply the next version from the server. This replaces the existing base_version and + /// operations. It's up to the caller (TaskDB) to ensure this is done consistently. + pub(crate) fn update_version(&mut self, version: u64, new_operations: Vec) { + // ensure that we are applying the versions in order.. + assert_eq!(version, self.base_version + 1); + self.base_version = version; + self.operations = new_operations; + } + + /// Record the outstanding operations as synced to the server in the given version. + pub(crate) fn local_operations_synced(&mut self, version: u64) { + assert_eq!(version, self.base_version + 1); + self.base_version = version; + self.operations = vec![]; + } +} diff --git a/tests/operation_transform_invariant.rs b/tests/operation_transform_invariant.rs index 8c7dd39af..f76850891 100644 --- a/tests/operation_transform_invariant.rs +++ b/tests/operation_transform_invariant.rs @@ -73,6 +73,6 @@ proptest! { if let Some(o) = o1p { db2.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?; } - assert_eq!(db1.tasks(), db2.tasks()); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); } } diff --git a/tests/sync.rs b/tests/sync.rs index 76049aed0..aed8bbc47 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -37,7 +37,7 @@ fn test_sync() { db1.sync("me", &mut server); db2.sync("me", &mut server); db1.sync("me", &mut server); - assert_eq!(db1.tasks(), db2.tasks()); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // now make updates to the same task on both sides db1.apply(Operation::Update { @@ -59,7 +59,7 @@ fn test_sync() { db1.sync("me", &mut server); db2.sync("me", &mut server); db1.sync("me", &mut server); - assert_eq!(db1.tasks(), db2.tasks()); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); } #[test] @@ -87,7 +87,7 @@ fn test_sync_create_delete() { db1.sync("me", &mut server); db2.sync("me", &mut server); db1.sync("me", &mut server); - assert_eq!(db1.tasks(), db2.tasks()); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // delete and re-create the task on db1 db1.apply(Operation::Delete { uuid }).unwrap(); @@ -112,5 +112,5 @@ fn test_sync_create_delete() { db1.sync("me", &mut server); db2.sync("me", &mut server); db1.sync("me", &mut server); - assert_eq!(db1.tasks(), db2.tasks()); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); } diff --git a/tests/sync_action_sequences.rs b/tests/sync_action_sequences.rs index ab02342ef..7a92d6e89 100644 --- a/tests/sync_action_sequences.rs +++ b/tests/sync_action_sequences.rs @@ -59,11 +59,11 @@ proptest! { } } - println!("{:?}", dbs[0].tasks()); - println!("{:?}", dbs[1].tasks()); - println!("{:?}", dbs[2].tasks()); + println!("{:?}", dbs[0]); + println!("{:?}", dbs[1]); + println!("{:?}", dbs[2]); - assert_eq!(dbs[0].tasks(), dbs[1].tasks()); - assert_eq!(dbs[1].tasks(), dbs[2].tasks()); + assert_eq!(dbs[0].sorted_tasks(), dbs[0].sorted_tasks()); + assert_eq!(dbs[1].sorted_tasks(), dbs[2].sorted_tasks()); } } From 611b1cd68f048d029eb820d2408100f776f92176 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 5 Jan 2020 14:58:24 -0500 Subject: [PATCH 032/548] factor storage out to a trait object --- TODO.txt | 2 +- src/bin/task.rs | 4 +- src/lib.rs | 2 +- src/operation.rs | 4 +- src/replica.rs | 8 +- src/taskdb.rs | 33 ++++---- src/taskstorage/inmemory.rs | 101 +++++++++++++++++++++++++ src/taskstorage/mod.rs | 94 ++++++----------------- tests/operation_transform_invariant.rs | 15 +++- tests/sync.rs | 14 ++-- tests/sync_action_sequences.rs | 8 +- 11 files changed, 177 insertions(+), 108 deletions(-) create mode 100644 src/taskstorage/inmemory.rs diff --git a/TODO.txt b/TODO.txt index 8cf4eba45..0d8c7da95 100644 --- a/TODO.txt +++ b/TODO.txt @@ -6,7 +6,7 @@ - tags: `tags. = ""` * add HTTP API * add pending-task indexing to Replica -* abstract server, storage, etc. into traits +* abstract server into trait * implement snapshot requests * implement backups * implement client-side encryption diff --git a/src/bin/task.rs b/src/bin/task.rs index 841a04e6c..e777fd317 100644 --- a/src/bin/task.rs +++ b/src/bin/task.rs @@ -1,6 +1,6 @@ extern crate clap; use clap::{App, Arg, SubCommand}; -use taskwarrior_rust::{Replica, DB}; +use taskwarrior_rust::{taskstorage, Replica, DB}; use uuid::Uuid; fn main() { @@ -16,7 +16,7 @@ fn main() { .subcommand(SubCommand::with_name("list").about("lists tasks")) .get_matches(); - let mut replica = Replica::new(DB::new().into()); + let mut replica = Replica::new(DB::new(Box::new(taskstorage::InMemoryStorage::new())).into()); match matches.subcommand() { ("add", Some(matches)) => { diff --git a/src/lib.rs b/src/lib.rs index e5374f4f3..5c5f3aba6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ mod replica; mod server; mod task; mod taskdb; -mod taskstorage; +pub mod taskstorage; mod tdb2; pub use operation::Operation; diff --git a/src/operation.rs b/src/operation.rs index f21c5b465..cba88c808 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -146,7 +146,7 @@ mod test { // check that the two operation sequences have the same effect, enforcing the invariant of // the transform function. - let mut db1 = DB::new(); + let mut db1 = DB::new_inmemory(); if let Some(ref o) = setup { db1.apply(o.clone()).unwrap(); } @@ -155,7 +155,7 @@ mod test { db1.apply(o).unwrap(); } - let mut db2 = DB::new(); + let mut db2 = DB::new_inmemory(); if let Some(ref o) = setup { db2.apply(o.clone()).unwrap(); } diff --git a/src/replica.rs b/src/replica.rs index 3c98c2db1..0d7a49ff0 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -70,7 +70,7 @@ mod tests { #[test] fn create() { - let mut rep = Replica::new(DB::new().into()); + let mut rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); rep.create_task(uuid.clone()).unwrap(); @@ -79,7 +79,7 @@ mod tests { #[test] fn delete() { - let mut rep = Replica::new(DB::new().into()); + let mut rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); rep.create_task(uuid.clone()).unwrap(); @@ -89,7 +89,7 @@ mod tests { #[test] fn update() { - let mut rep = Replica::new(DB::new().into()); + let mut rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); rep.create_task(uuid.clone()).unwrap(); @@ -102,7 +102,7 @@ mod tests { #[test] fn get_does_not_exist() { - let rep = Replica::new(DB::new().into()); + let rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); assert_eq!(rep.get_task(&uuid), None); } diff --git a/src/taskdb.rs b/src/taskdb.rs index 8f77e5fed..be5dc8388 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -1,16 +1,16 @@ use crate::errors::Error; use crate::operation::Operation; use crate::server::{Server, VersionAdd}; -use crate::taskstorage::{InMemoryStorage, TaskMap}; +use crate::taskstorage::{TaskMap, TaskStorage}; use failure::Fallible; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::str; use uuid::Uuid; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct DB { - storage: InMemoryStorage, + storage: Box, } #[derive(Serialize, Deserialize, Debug)] @@ -20,11 +20,14 @@ struct Version { } impl DB { - /// Create a new, empty database - pub fn new() -> DB { - DB { - storage: InMemoryStorage::new(), - } + /// Create a new DB with the given backend storage + pub fn new(storage: Box) -> DB { + DB { storage } + } + + #[cfg(test)] + pub fn new_inmemory() -> DB { + DB::new(Box::new(crate::taskstorage::InMemoryStorage::new())) } /// Apply an operation to the DB. Aside from synchronization operations, this is the only way @@ -212,7 +215,7 @@ mod tests { #[test] fn test_apply_create() { - let mut db = DB::new(); + let mut db = DB::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; db.apply(op.clone()).unwrap(); @@ -223,7 +226,7 @@ mod tests { #[test] fn test_apply_create_exists() { - let mut db = DB::new(); + let mut db = DB::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; db.apply(op.clone()).unwrap(); @@ -238,7 +241,7 @@ mod tests { #[test] fn test_apply_create_update() { - let mut db = DB::new(); + let mut db = DB::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; db.apply(op1.clone()).unwrap(); @@ -259,7 +262,7 @@ mod tests { #[test] fn test_apply_create_update_delete_prop() { - let mut db = DB::new(); + let mut db = DB::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; db.apply(op1.clone()).unwrap(); @@ -301,7 +304,7 @@ mod tests { #[test] fn test_apply_update_does_not_exist() { - let mut db = DB::new(); + let mut db = DB::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Update { uuid, @@ -320,7 +323,7 @@ mod tests { #[test] fn test_apply_create_delete() { - let mut db = DB::new(); + let mut db = DB::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; db.apply(op1.clone()).unwrap(); @@ -334,7 +337,7 @@ mod tests { #[test] fn test_apply_delete_not_present() { - let mut db = DB::new(); + let mut db = DB::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Delete { uuid }; diff --git a/src/taskstorage/inmemory.rs b/src/taskstorage/inmemory.rs new file mode 100644 index 000000000..798be6053 --- /dev/null +++ b/src/taskstorage/inmemory.rs @@ -0,0 +1,101 @@ +use crate::operation::Operation; +use crate::taskstorage::{TaskMap, TaskStorage}; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use uuid::Uuid; + +#[derive(PartialEq, Debug, Clone)] +pub struct InMemoryStorage { + // The current state, with all pending operations applied + tasks: HashMap, + + // The version at which `operations` begins + base_version: u64, + + // Operations applied since `base_version`, in order. + // + // INVARIANT: Given a snapshot at `base_version`, applying these operations produces `tasks`. + operations: Vec, +} + +impl InMemoryStorage { + pub fn new() -> InMemoryStorage { + InMemoryStorage { + tasks: HashMap::new(), + base_version: 0, + operations: vec![], + } + } +} + +impl TaskStorage for InMemoryStorage { + /// Get an (immutable) task, if it is in the storage + fn get_task(&self, uuid: &Uuid) -> Option { + match self.tasks.get(uuid) { + None => None, + Some(t) => Some(t.clone()), + } + } + + /// Create a task, only if it does not already exist. Returns true if + /// the task was created (did not already exist). + fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> bool { + if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { + ent.or_insert(task); + true + } else { + false + } + } + + /// Set a task, overwriting any existing task. + fn set_task(&mut self, uuid: Uuid, task: TaskMap) { + self.tasks.insert(uuid, task); + } + + /// Delete a task, if it exists. Returns true if the task was deleted (already existed) + fn delete_task(&mut self, uuid: &Uuid) -> bool { + if let Some(_) = self.tasks.remove(uuid) { + true + } else { + false + } + } + + fn get_task_uuids<'a>(&'a self) -> Box + 'a> { + Box::new(self.tasks.keys().map(|u| u.clone())) + } + + /// Add an operation to the list of operations in the storage. Note that this merely *stores* + /// the operation; it is up to the TaskDB to apply it. + fn add_operation(&mut self, op: Operation) { + self.operations.push(op); + } + + /// Get the current base_version for this storage -- the last version synced from the server. + fn base_version(&self) -> u64 { + return self.base_version; + } + + /// Get the current set of outstanding operations (operations that have not been sync'd to the + /// server yet) + fn operations<'a>(&'a self) -> Box + 'a> { + Box::new(self.operations.iter()) + } + + /// Apply the next version from the server. This replaces the existing base_version and + /// operations. It's up to the caller (TaskDB) to ensure this is done consistently. + fn update_version(&mut self, version: u64, new_operations: Vec) { + // ensure that we are applying the versions in order.. + assert_eq!(version, self.base_version + 1); + self.base_version = version; + self.operations = new_operations; + } + + /// Record the outstanding operations as synced to the server in the given version. + fn local_operations_synced(&mut self, version: u64) { + assert_eq!(version, self.base_version + 1); + self.base_version = version; + self.operations = vec![]; + } +} diff --git a/src/taskstorage/mod.rs b/src/taskstorage/mod.rs index bbc7707a2..dd8e233de 100644 --- a/src/taskstorage/mod.rs +++ b/src/taskstorage/mod.rs @@ -1,100 +1,50 @@ -use crate::operation::Operation; -use std::collections::hash_map::Entry; +use crate::Operation; use std::collections::HashMap; +use std::fmt; use uuid::Uuid; +mod inmemory; + +pub use inmemory::InMemoryStorage; + +/// An in-memory representation of a task as a simple hashmap pub type TaskMap = HashMap; -#[derive(PartialEq, Debug, Clone)] -pub struct InMemoryStorage { - // The current state, with all pending operations applied - tasks: HashMap, - - // The version at which `operations` begins - base_version: u64, - - // Operations applied since `base_version`, in order. - // - // INVARIANT: Given a snapshot at `base_version`, applying these operations produces `tasks`. - operations: Vec, -} - -impl InMemoryStorage { - pub fn new() -> InMemoryStorage { - InMemoryStorage { - tasks: HashMap::new(), - base_version: 0, - operations: vec![], - } - } - +/// A trait for objects able to act as backing storage for a TaskDB. This API is optimized to be +/// easy to implement, with all of the semantic meaning of the data located in the TaskDB +/// implementation, which is the sole consumer of this trait. +pub trait TaskStorage: fmt::Debug { /// Get an (immutable) task, if it is in the storage - pub fn get_task(&self, uuid: &Uuid) -> Option { - match self.tasks.get(uuid) { - None => None, - Some(t) => Some(t.clone()), - } - } + fn get_task(&self, uuid: &Uuid) -> Option; /// Create a task, only if it does not already exist. Returns true if /// the task was created (did not already exist). - pub fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> bool { - if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { - ent.or_insert(task); - true - } else { - false - } - } + fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> bool; /// Set a task, overwriting any existing task. - pub fn set_task(&mut self, uuid: Uuid, task: TaskMap) { - self.tasks.insert(uuid, task); - } + fn set_task(&mut self, uuid: Uuid, task: TaskMap); /// Delete a task, if it exists. Returns true if the task was deleted (already existed) - pub fn delete_task(&mut self, uuid: &Uuid) -> bool { - if let Some(_) = self.tasks.remove(uuid) { - true - } else { - false - } - } + fn delete_task(&mut self, uuid: &Uuid) -> bool; - pub fn get_task_uuids<'a>(&'a self) -> impl Iterator + 'a { - self.tasks.keys().map(|u| u.clone()) - } + /// Get the uuids of all tasks in the storage, in undefined order. + fn get_task_uuids<'a>(&'a self) -> Box + 'a>; /// Add an operation to the list of operations in the storage. Note that this merely *stores* /// the operation; it is up to the TaskDB to apply it. - pub fn add_operation(&mut self, op: Operation) { - self.operations.push(op); - } + fn add_operation(&mut self, op: Operation); /// Get the current base_version for this storage -- the last version synced from the server. - pub fn base_version(&self) -> u64 { - return self.base_version; - } + fn base_version(&self) -> u64; /// Get the current set of outstanding operations (operations that have not been sync'd to the /// server yet) - pub fn operations(&self) -> impl Iterator { - self.operations.iter() - } + fn operations<'a>(&'a self) -> Box + 'a>; /// Apply the next version from the server. This replaces the existing base_version and /// operations. It's up to the caller (TaskDB) to ensure this is done consistently. - pub(crate) fn update_version(&mut self, version: u64, new_operations: Vec) { - // ensure that we are applying the versions in order.. - assert_eq!(version, self.base_version + 1); - self.base_version = version; - self.operations = new_operations; - } + fn update_version(&mut self, version: u64, new_operations: Vec); /// Record the outstanding operations as synced to the server in the given version. - pub(crate) fn local_operations_synced(&mut self, version: u64) { - assert_eq!(version, self.base_version + 1); - self.base_version = version; - self.operations = vec![]; - } + fn local_operations_synced(&mut self, version: u64); } diff --git a/tests/operation_transform_invariant.rs b/tests/operation_transform_invariant.rs index f76850891..4a588d707 100644 --- a/tests/operation_transform_invariant.rs +++ b/tests/operation_transform_invariant.rs @@ -1,8 +1,12 @@ use chrono::Utc; use proptest::prelude::*; -use taskwarrior_rust::{Operation, DB}; +use taskwarrior_rust::{taskstorage, Operation, DB}; use uuid::Uuid; +fn newdb() -> DB { + DB::new(Box::new(taskstorage::InMemoryStorage::new())) +} + fn uuid_strategy() -> impl Strategy { prop_oneof![ Just(Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap()), @@ -37,27 +41,30 @@ proptest! { fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); - let mut db1 = DB::new(); + let mut db1 = newdb(); + let mut db2 = newdb(); // Ensure that any expected tasks already exist if let Operation::Update{ ref uuid, .. } = o1 { let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); } if let Operation::Update{ ref uuid, .. } = o2 { let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); } if let Operation::Delete{ ref uuid } = o1 { let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); } if let Operation::Delete{ ref uuid } = o2 { let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); } - let mut db2 = db1.clone(); - // if applying the initial operations fail, that indicates the operation was invalid // in the base state, so consider the case successful. if let Err(_) = db1.apply(o1) { diff --git a/tests/sync.rs b/tests/sync.rs index aed8bbc47..8c684b4f9 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -1,15 +1,19 @@ use chrono::Utc; -use taskwarrior_rust::{Operation, Server, DB}; +use taskwarrior_rust::{taskstorage, Operation, Server, DB}; use uuid::Uuid; +fn newdb() -> DB { + DB::new(Box::new(taskstorage::InMemoryStorage::new())) +} + #[test] fn test_sync() { let mut server = Server::new(); - let mut db1 = DB::new(); + let mut db1 = newdb(); db1.sync("me", &mut server); - let mut db2 = DB::new(); + let mut db2 = newdb(); db2.sync("me", &mut server); // make some changes in parallel to db1 and db2.. @@ -66,10 +70,10 @@ fn test_sync() { fn test_sync_create_delete() { let mut server = Server::new(); - let mut db1 = DB::new(); + let mut db1 = newdb(); db1.sync("me", &mut server); - let mut db2 = DB::new(); + let mut db2 = newdb(); db2.sync("me", &mut server); // create and update a task.. diff --git a/tests/sync_action_sequences.rs b/tests/sync_action_sequences.rs index 7a92d6e89..4f18976e2 100644 --- a/tests/sync_action_sequences.rs +++ b/tests/sync_action_sequences.rs @@ -1,8 +1,12 @@ use chrono::Utc; use proptest::prelude::*; -use taskwarrior_rust::{Operation, Server, DB}; +use taskwarrior_rust::{taskstorage, Operation, Server, DB}; use uuid::Uuid; +fn newdb() -> DB { + DB::new(Box::new(taskstorage::InMemoryStorage::new())) +} + #[derive(Debug)] enum Action { Op(Operation), @@ -43,7 +47,7 @@ proptest! { // another. So, the generated sequences focus on a single task UUID. fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { let mut server = Server::new(); - let mut dbs = [DB::new(), DB::new(), DB::new()]; + let mut dbs = [newdb(), newdb(), newdb()]; for (action, db) in action_sequence { println!("{:?} on db {}", action, db); From afd11d08a7d3146bfb920271696117f455e038b2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 5 Jan 2020 15:39:34 -0500 Subject: [PATCH 033/548] make the TaskStorage API fallible everywhere --- Cargo.lock | 51 ++++++++++++++++++ Cargo.toml | 1 + TODO.txt | 1 + src/bin/task.rs | 2 +- src/replica.rs | 14 ++--- src/taskdb.rs | 54 +++++++++++-------- src/taskstorage/inmemory.rs | 43 ++++++++------- src/taskstorage/lmdb.rs | 95 ++++++++++++++++++++++++++++++++++ src/taskstorage/mod.rs | 21 ++++---- tests/sync.rs | 32 ++++++------ tests/sync_action_sequences.rs | 2 +- 11 files changed, 241 insertions(+), 75 deletions(-) create mode 100644 src/taskstorage/lmdb.rs diff --git a/Cargo.lock b/Cargo.lock index 6b40737c3..d48a1eaeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,6 +161,17 @@ name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "kv" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -171,6 +182,27 @@ name = "libc" version = "0.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lmdb-rkv" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "lmdb-rkv-sys 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lmdb-rkv-sys" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" version = "0.1.41" @@ -188,6 +220,11 @@ dependencies = [ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ppv-lite86" version = "0.2.6" @@ -474,6 +511,7 @@ dependencies = [ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "kv 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", @@ -511,6 +549,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-width" version = "0.1.7" @@ -589,10 +635,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum kv 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "db74e838988c38867eac475ff9793b34ee520618c73cad9dc5a450caa4f5a5e6" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +"checksum lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "605061e5465304475be2041f19967a900175ea1b6d8f47fbab84a84fb8c48452" +"checksum lmdb-rkv-sys 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7982ba0460e939e26a52ee12c8075deab0ebd44ed21881f656841b70e021b7c8" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" +"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" "checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f" @@ -628,6 +678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" diff --git a/Cargo.toml b/Cargo.toml index f61df728c..c65c7e6fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ serde_json = "1.0" chrono = { version = "0.4.10", features = ["serde"] } failure = {version = "0.1.5", features = ["derive"] } clap = "~2.33.0" +kv = "0.9.3" [dev-dependencies] proptest = "0.9.4" diff --git a/TODO.txt b/TODO.txt index 0d8c7da95..045580c49 100644 --- a/TODO.txt +++ b/TODO.txt @@ -13,3 +13,4 @@ * design expiration - need to be sure that create / delete operations don't get reversed * cli tools +* move test-only tools somewhere else (helpers in tests/?) diff --git a/src/bin/task.rs b/src/bin/task.rs index e777fd317..d9ce502e2 100644 --- a/src/bin/task.rs +++ b/src/bin/task.rs @@ -27,7 +27,7 @@ fn main() { .unwrap(); } ("list", _) => { - for task in replica.all_tasks() { + for task in replica.all_tasks().unwrap() { println!("{:?}", task); } } diff --git a/src/replica.rs b/src/replica.rs index 0d7a49ff0..dc07b3290 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -47,17 +47,17 @@ impl Replica { } /// Get all tasks as an iterator of (&Uuid, &HashMap) - pub fn all_tasks<'a>(&'a self) -> impl Iterator + 'a { + pub fn all_tasks<'a>(&'a self) -> Fallible + 'a> { self.taskdb.all_tasks() } /// Get the UUIDs of all tasks - pub fn all_task_uuids<'a>(&'a self) -> impl Iterator + 'a { + pub fn all_task_uuids<'a>(&'a self) -> Fallible + 'a> { self.taskdb.all_task_uuids() } /// Get an existing task by its UUID - pub fn get_task(&self, uuid: &Uuid) -> Option { + pub fn get_task(&self, uuid: &Uuid) -> Fallible> { self.taskdb.get_task(&uuid) } } @@ -74,7 +74,7 @@ mod tests { let uuid = Uuid::new_v4(); rep.create_task(uuid.clone()).unwrap(); - assert_eq!(rep.get_task(&uuid), Some(TaskMap::new())); + assert_eq!(rep.get_task(&uuid).unwrap(), Some(TaskMap::new())); } #[test] @@ -84,7 +84,7 @@ mod tests { rep.create_task(uuid.clone()).unwrap(); rep.delete_task(uuid.clone()).unwrap(); - assert_eq!(rep.get_task(&uuid), None); + assert_eq!(rep.get_task(&uuid).unwrap(), None); } #[test] @@ -97,13 +97,13 @@ mod tests { .unwrap(); let mut task = TaskMap::new(); task.insert("title".into(), "snarsblat".into()); - assert_eq!(rep.get_task(&uuid), Some(task)); + assert_eq!(rep.get_task(&uuid).unwrap(), Some(task)); } #[test] fn get_does_not_exist() { let rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); - assert_eq!(rep.get_task(&uuid), None); + assert_eq!(rep.get_task(&uuid).unwrap(), None); } } diff --git a/src/taskdb.rs b/src/taskdb.rs index be5dc8388..ba448dafc 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -34,10 +34,11 @@ impl DB { /// to modify the DB. In cases where an operation does not make sense, this function will do /// nothing and return an error (but leave the DB in a consistent state). pub fn apply(&mut self, op: Operation) -> Fallible<()> { + // TODO: differentiate error types here? if let err @ Err(_) = self.apply_op(&op) { return err; } - self.storage.add_operation(op); + self.storage.add_operation(op)?; Ok(()) } @@ -45,12 +46,12 @@ impl DB { match op { &Operation::Create { uuid } => { // insert if the task does not already exist - if !self.storage.create_task(uuid, HashMap::new()) { + if !self.storage.create_task(uuid, HashMap::new())? { return Err(Error::DBError(format!("Task {} already exists", uuid)).into()); } } &Operation::Delete { ref uuid } => { - if !self.storage.delete_task(uuid) { + if !self.storage.delete_task(uuid)? { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } } @@ -61,13 +62,13 @@ impl DB { timestamp: _, } => { // update if this task exists, otherwise ignore - if let Some(task) = self.storage.get_task(uuid) { + if let Some(task) = self.storage.get_task(uuid)? { let mut task = task.clone(); match value { Some(ref val) => task.insert(property.to_string(), val.clone()), None => task.remove(property), }; - self.storage.set_task(uuid.clone(), task); + self.storage.set_task(uuid.clone(), task)?; } else { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } @@ -78,38 +79,41 @@ impl DB { } /// Get all tasks. This is not a terribly efficient operation. - pub fn all_tasks<'a>(&'a self) -> impl Iterator + 'a { - self.all_task_uuids() - .map(move |u| (u, self.get_task(&u).unwrap())) + pub fn all_tasks<'a>(&'a self) -> Fallible + 'a> { + Ok(self + .all_task_uuids()? + // TODO: don't unwrap result (just option) + .map(move |u| (u, self.get_task(&u).unwrap().unwrap()))) } /// Get the UUIDs of all tasks - pub fn all_task_uuids<'a>(&'a self) -> impl Iterator + 'a { + pub fn all_task_uuids<'a>(&'a self) -> Fallible + 'a> { self.storage.get_task_uuids() } /// Get a single task, by uuid. - pub fn get_task(&self, uuid: &Uuid) -> Option { + pub fn get_task(&self, uuid: &Uuid) -> Fallible> { self.storage.get_task(uuid) } /// Sync to the given server, pulling remote changes and pushing local changes. - pub fn sync(&mut self, username: &str, server: &mut Server) { + pub fn sync(&mut self, username: &str, server: &mut Server) -> Fallible<()> { // retry synchronizing until the server accepts our version (this allows for races between // replicas trying to sync to the same server) loop { // first pull changes and "rebase" on top of them - let new_versions = server.get_versions(username, self.storage.base_version()); + let new_versions = server.get_versions(username, self.storage.base_version()?); for version_blob in new_versions { let version_str = str::from_utf8(&version_blob).unwrap(); let version: Version = serde_json::from_str(version_str).unwrap(); - assert_eq!(version.version, self.storage.base_version() + 1); + assert_eq!(version.version, self.storage.base_version()? + 1); println!("applying version {:?} from server", version.version); - self.apply_version(version); + self.apply_version(version)?; } - let operations: Vec = self.storage.operations().map(|o| o.clone()).collect(); + let operations: Vec = + self.storage.operations()?.map(|o| o.clone()).collect(); if operations.len() == 0 { // nothing to sync back to the server.. break; @@ -117,7 +121,7 @@ impl DB { // now make a version of our local changes and push those let new_version = Version { - version: self.storage.base_version() + 1, + version: self.storage.base_version()? + 1, operations: operations, }; let new_version_str = serde_json::to_string(&new_version).unwrap(); @@ -125,13 +129,15 @@ impl DB { if let VersionAdd::Ok = server.add_version(username, new_version.version, new_version_str.into()) { - self.storage.local_operations_synced(new_version.version); + self.storage.local_operations_synced(new_version.version)?; break; } } + + Ok(()) } - fn apply_version(&mut self, mut version: Version) { + fn apply_version(&mut self, mut version: Version) -> Fallible<()> { // The situation here is that the server has already applied all server operations, and we // have already applied all local operations, so states have diverged by several // operations. We need to figure out what operations to apply locally and on the server in @@ -158,7 +164,7 @@ impl DB { // indicating no operation is required. If this happens for a local op, we can just omit // it. If it happens for server op, then we must copy the remaining local ops. let mut local_operations: Vec = - self.storage.operations().map(|o| o.clone()).collect(); + self.storage.operations()?.map(|o| o.clone()).collect(); for server_op in version.operations.drain(..) { let mut new_local_ops = Vec::with_capacity(local_operations.len()); let mut svr_op = Some(server_op); @@ -181,7 +187,8 @@ impl DB { local_operations = new_local_ops; } self.storage - .update_version(version.version, local_operations); + .update_version(version.version, local_operations)?; + Ok(()) } // functions for supporting tests @@ -189,6 +196,7 @@ impl DB { pub fn sorted_tasks(&self) -> Vec<(Uuid, Vec<(String, String)>)> { let mut res: Vec<(Uuid, Vec<(String, String)>)> = self .all_tasks() + .unwrap() .map(|(u, t)| { let mut t = t .iter() @@ -203,7 +211,11 @@ impl DB { } pub fn operations(&self) -> Vec { - self.storage.operations().map(|o| o.clone()).collect() + self.storage + .operations() + .unwrap() + .map(|o| o.clone()) + .collect() } } diff --git a/src/taskstorage/inmemory.rs b/src/taskstorage/inmemory.rs index 798be6053..790cd341d 100644 --- a/src/taskstorage/inmemory.rs +++ b/src/taskstorage/inmemory.rs @@ -1,5 +1,6 @@ use crate::operation::Operation; use crate::taskstorage::{TaskMap, TaskStorage}; +use failure::Fallible; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; @@ -30,72 +31,76 @@ impl InMemoryStorage { impl TaskStorage for InMemoryStorage { /// Get an (immutable) task, if it is in the storage - fn get_task(&self, uuid: &Uuid) -> Option { + fn get_task(&self, uuid: &Uuid) -> Fallible> { match self.tasks.get(uuid) { - None => None, - Some(t) => Some(t.clone()), + None => Ok(None), + Some(t) => Ok(Some(t.clone())), } } /// Create a task, only if it does not already exist. Returns true if /// the task was created (did not already exist). - fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> bool { + fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible { if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { ent.or_insert(task); - true + Ok(true) } else { - false + Ok(false) } } /// Set a task, overwriting any existing task. - fn set_task(&mut self, uuid: Uuid, task: TaskMap) { + fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible<()> { self.tasks.insert(uuid, task); + Ok(()) } /// Delete a task, if it exists. Returns true if the task was deleted (already existed) - fn delete_task(&mut self, uuid: &Uuid) -> bool { + fn delete_task(&mut self, uuid: &Uuid) -> Fallible { if let Some(_) = self.tasks.remove(uuid) { - true + Ok(true) } else { - false + Ok(false) } } - fn get_task_uuids<'a>(&'a self) -> Box + 'a> { - Box::new(self.tasks.keys().map(|u| u.clone())) + fn get_task_uuids<'a>(&'a self) -> Fallible + 'a>> { + Ok(Box::new(self.tasks.keys().map(|u| u.clone()))) } /// Add an operation to the list of operations in the storage. Note that this merely *stores* /// the operation; it is up to the TaskDB to apply it. - fn add_operation(&mut self, op: Operation) { + fn add_operation(&mut self, op: Operation) -> Fallible<()> { self.operations.push(op); + Ok(()) } /// Get the current base_version for this storage -- the last version synced from the server. - fn base_version(&self) -> u64 { - return self.base_version; + fn base_version(&self) -> Fallible { + Ok(self.base_version) } /// Get the current set of outstanding operations (operations that have not been sync'd to the /// server yet) - fn operations<'a>(&'a self) -> Box + 'a> { - Box::new(self.operations.iter()) + fn operations<'a>(&'a self) -> Fallible + 'a>> { + Ok(Box::new(self.operations.iter())) } /// Apply the next version from the server. This replaces the existing base_version and /// operations. It's up to the caller (TaskDB) to ensure this is done consistently. - fn update_version(&mut self, version: u64, new_operations: Vec) { + fn update_version(&mut self, version: u64, new_operations: Vec) -> Fallible<()> { // ensure that we are applying the versions in order.. assert_eq!(version, self.base_version + 1); self.base_version = version; self.operations = new_operations; + Ok(()) } /// Record the outstanding operations as synced to the server in the given version. - fn local_operations_synced(&mut self, version: u64) { + fn local_operations_synced(&mut self, version: u64) -> Fallible<()> { assert_eq!(version, self.base_version + 1); self.base_version = version; self.operations = vec![]; + Ok(()) } } diff --git a/src/taskstorage/lmdb.rs b/src/taskstorage/lmdb.rs new file mode 100644 index 000000000..1a7d75c7f --- /dev/null +++ b/src/taskstorage/lmdb.rs @@ -0,0 +1,95 @@ +use crate::operation::Operation; +use crate::taskstorage::{TaskMap, TaskStorage}; +use kv::{Config, Error, Manager, ValueRef}; +use uuid::Uuid; + +pub struct KVStorage { + // TODO: make the manager global with lazy-static + manager: Manager, + config: Config, +} + +impl KVStorage { + pub fn new(directory: &str) -> KVStorage { + let mut config = Config::default(directory); + config.bucket("base_version", None); + config.bucket("operations", None); + config.bucket("tasks", None); + KVStorage { + manager: Manager::new(), + config, + } + } +} + +impl TaskStorage for KVStorage { + /// Get an (immutable) task, if it is in the storage + fn get_task(&self, uuid: &Uuid) -> Option { + match self.tasks.get(uuid) { + None => None, + Some(t) => Some(t.clone()), + } + } + + /// Create a task, only if it does not already exist. Returns true if + /// the task was created (did not already exist). + fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> bool { + if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { + ent.or_insert(task); + true + } else { + false + } + } + + /// Set a task, overwriting any existing task. + fn set_task(&mut self, uuid: Uuid, task: TaskMap) { + self.tasks.insert(uuid, task); + } + + /// Delete a task, if it exists. Returns true if the task was deleted (already existed) + fn delete_task(&mut self, uuid: &Uuid) -> bool { + if let Some(_) = self.tasks.remove(uuid) { + true + } else { + false + } + } + + fn get_task_uuids<'a>(&'a self) -> Box + 'a> { + Box::new(self.tasks.keys().map(|u| u.clone())) + } + + /// Add an operation to the list of operations in the storage. Note that this merely *stores* + /// the operation; it is up to the TaskDB to apply it. + fn add_operation(&mut self, op: Operation) { + self.operations.push(op); + } + + /// Get the current base_version for this storage -- the last version synced from the server. + fn base_version(&self) -> u64 { + return self.base_version; + } + + /// Get the current set of outstanding operations (operations that have not been sync'd to the + /// server yet) + fn operations<'a>(&'a self) -> Box + 'a> { + Box::new(self.operations.iter()) + } + + /// Apply the next version from the server. This replaces the existing base_version and + /// operations. It's up to the caller (TaskDB) to ensure this is done consistently. + fn update_version(&mut self, version: u64, new_operations: Vec) { + // ensure that we are applying the versions in order.. + assert_eq!(version, self.base_version + 1); + self.base_version = version; + self.operations = new_operations; + } + + /// Record the outstanding operations as synced to the server in the given version. + fn local_operations_synced(&mut self, version: u64) { + assert_eq!(version, self.base_version + 1); + self.base_version = version; + self.operations = vec![]; + } +} diff --git a/src/taskstorage/mod.rs b/src/taskstorage/mod.rs index dd8e233de..3f339ac29 100644 --- a/src/taskstorage/mod.rs +++ b/src/taskstorage/mod.rs @@ -1,4 +1,5 @@ use crate::Operation; +use failure::Fallible; use std::collections::HashMap; use std::fmt; use uuid::Uuid; @@ -15,36 +16,36 @@ pub type TaskMap = HashMap; /// implementation, which is the sole consumer of this trait. pub trait TaskStorage: fmt::Debug { /// Get an (immutable) task, if it is in the storage - fn get_task(&self, uuid: &Uuid) -> Option; + fn get_task(&self, uuid: &Uuid) -> Fallible>; /// Create a task, only if it does not already exist. Returns true if /// the task was created (did not already exist). - fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> bool; + fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible; /// Set a task, overwriting any existing task. - fn set_task(&mut self, uuid: Uuid, task: TaskMap); + fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible<()>; /// Delete a task, if it exists. Returns true if the task was deleted (already existed) - fn delete_task(&mut self, uuid: &Uuid) -> bool; + fn delete_task(&mut self, uuid: &Uuid) -> Fallible; /// Get the uuids of all tasks in the storage, in undefined order. - fn get_task_uuids<'a>(&'a self) -> Box + 'a>; + fn get_task_uuids<'a>(&'a self) -> Fallible + 'a>>; /// Add an operation to the list of operations in the storage. Note that this merely *stores* /// the operation; it is up to the TaskDB to apply it. - fn add_operation(&mut self, op: Operation); + fn add_operation(&mut self, op: Operation) -> Fallible<()>; /// Get the current base_version for this storage -- the last version synced from the server. - fn base_version(&self) -> u64; + fn base_version(&self) -> Fallible; /// Get the current set of outstanding operations (operations that have not been sync'd to the /// server yet) - fn operations<'a>(&'a self) -> Box + 'a>; + fn operations<'a>(&'a self) -> Fallible + 'a>>; /// Apply the next version from the server. This replaces the existing base_version and /// operations. It's up to the caller (TaskDB) to ensure this is done consistently. - fn update_version(&mut self, version: u64, new_operations: Vec); + fn update_version(&mut self, version: u64, new_operations: Vec) -> Fallible<()>; /// Record the outstanding operations as synced to the server in the given version. - fn local_operations_synced(&mut self, version: u64); + fn local_operations_synced(&mut self, version: u64) -> Fallible<()>; } diff --git a/tests/sync.rs b/tests/sync.rs index 8c684b4f9..b3f73f6de 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -11,10 +11,10 @@ fn test_sync() { let mut server = Server::new(); let mut db1 = newdb(); - db1.sync("me", &mut server); + db1.sync("me", &mut server).unwrap(); let mut db2 = newdb(); - db2.sync("me", &mut server); + db2.sync("me", &mut server).unwrap(); // make some changes in parallel to db1 and db2.. let uuid1 = Uuid::new_v4(); @@ -38,9 +38,9 @@ fn test_sync() { .unwrap(); // and synchronize those around - db1.sync("me", &mut server); - db2.sync("me", &mut server); - db1.sync("me", &mut server); + db1.sync("me", &mut server).unwrap(); + db2.sync("me", &mut server).unwrap(); + db1.sync("me", &mut server).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // now make updates to the same task on both sides @@ -60,9 +60,9 @@ fn test_sync() { .unwrap(); // and synchronize those around - db1.sync("me", &mut server); - db2.sync("me", &mut server); - db1.sync("me", &mut server); + db1.sync("me", &mut server).unwrap(); + db2.sync("me", &mut server).unwrap(); + db1.sync("me", &mut server).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); } @@ -71,10 +71,10 @@ fn test_sync_create_delete() { let mut server = Server::new(); let mut db1 = newdb(); - db1.sync("me", &mut server); + db1.sync("me", &mut server).unwrap(); let mut db2 = newdb(); - db2.sync("me", &mut server); + db2.sync("me", &mut server).unwrap(); // create and update a task.. let uuid = Uuid::new_v4(); @@ -88,9 +88,9 @@ fn test_sync_create_delete() { .unwrap(); // and synchronize those around - db1.sync("me", &mut server); - db2.sync("me", &mut server); - db1.sync("me", &mut server); + db1.sync("me", &mut server).unwrap(); + db2.sync("me", &mut server).unwrap(); + db1.sync("me", &mut server).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // delete and re-create the task on db1 @@ -113,8 +113,8 @@ fn test_sync_create_delete() { }) .unwrap(); - db1.sync("me", &mut server); - db2.sync("me", &mut server); - db1.sync("me", &mut server); + db1.sync("me", &mut server).unwrap(); + db2.sync("me", &mut server).unwrap(); + db1.sync("me", &mut server).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); } diff --git a/tests/sync_action_sequences.rs b/tests/sync_action_sequences.rs index 4f18976e2..58ed9ba77 100644 --- a/tests/sync_action_sequences.rs +++ b/tests/sync_action_sequences.rs @@ -59,7 +59,7 @@ proptest! { println!(" {:?} (ignored)", e); } }, - Action::Sync => db.sync("me", &mut server), + Action::Sync => db.sync("me", &mut server).unwrap(), } } From 2f973d3e62c8c303f81a0e7241e56f89f344ac70 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 5 Jan 2020 18:28:43 -0500 Subject: [PATCH 034/548] task storge implementation based on kv / LMDB --- Cargo.lock | 75 +++- Cargo.toml | 4 +- src/lib.rs | 1 + src/replica.rs | 8 +- src/taskdb.rs | 79 +++-- src/taskstorage/inmemory.rs | 137 +++++--- src/taskstorage/kv.rs | 621 +++++++++++++++++++++++++++++++++ src/taskstorage/lmdb.rs | 95 ----- src/taskstorage/mod.rs | 70 +++- tests/sync_action_sequences.rs | 4 - 10 files changed, 884 insertions(+), 210 deletions(-) create mode 100644 src/taskstorage/kv.rs delete mode 100644 src/taskstorage/lmdb.rs diff --git a/Cargo.lock b/Cargo.lock index d48a1eaeb..2aa865cc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,12 +163,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "kv" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rmp-serde 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -270,6 +271,18 @@ dependencies = [ "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.6.5" @@ -429,6 +442,25 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rmp" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rmp-serde" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rmp 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.16" @@ -511,13 +543,24 @@ dependencies = [ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "kv 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "kv 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tempfile" version = "3.1.0" @@ -539,6 +582,24 @@ dependencies = [ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thiserror" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.42" @@ -635,7 +696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" -"checksum kv 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "db74e838988c38867eac475ff9793b34ee520618c73cad9dc5a450caa4f5a5e6" +"checksum kv 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" "checksum lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "605061e5465304475be2041f19967a900175ea1b6d8f47fbab84a84fb8c48452" @@ -648,6 +709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" @@ -666,6 +728,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +"checksum rmp 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" +"checksum rmp-serde 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4a31c0798045f039ace94e0166f76478b3ba83116ec7c9d4bc934c5b13b8df21" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" @@ -675,8 +739,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ddc157159e2a7df58cd67b1cace10b8ed256a404fb0070593f137d8ba6bef4de" "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" +"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" diff --git a/Cargo.toml b/Cargo.toml index c65c7e6fa..9cc649f10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,9 @@ serde_json = "1.0" chrono = { version = "0.4.10", features = ["serde"] } failure = {version = "0.1.5", features = ["derive"] } clap = "~2.33.0" -kv = "0.9.3" +kv = {version = "0.10.0", features = ["msgpack-value"]} +lmdb-rkv = {version = "0.12.3"} [dev-dependencies] proptest = "0.9.4" +tempdir = "0.3.7" diff --git a/src/lib.rs b/src/lib.rs index 5c5f3aba6..8566989cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ // TODO: remove this eventually when there's an API #![allow(dead_code)] +#![allow(unused_variables)] #[macro_use] extern crate failure; diff --git a/src/replica.rs b/src/replica.rs index dc07b3290..c2df51fc8 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -47,17 +47,17 @@ impl Replica { } /// Get all tasks as an iterator of (&Uuid, &HashMap) - pub fn all_tasks<'a>(&'a self) -> Fallible + 'a> { + pub fn all_tasks<'a>(&'a mut self) -> Fallible> { self.taskdb.all_tasks() } /// Get the UUIDs of all tasks - pub fn all_task_uuids<'a>(&'a self) -> Fallible + 'a> { + pub fn all_task_uuids<'a>(&'a mut self) -> Fallible> { self.taskdb.all_task_uuids() } /// Get an existing task by its UUID - pub fn get_task(&self, uuid: &Uuid) -> Fallible> { + pub fn get_task(&mut self, uuid: &Uuid) -> Fallible> { self.taskdb.get_task(&uuid) } } @@ -102,7 +102,7 @@ mod tests { #[test] fn get_does_not_exist() { - let rep = Replica::new(DB::new_inmemory().into()); + let mut rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); assert_eq!(rep.get_task(&uuid).unwrap(), None); } diff --git a/src/taskdb.rs b/src/taskdb.rs index ba448dafc..20a8b74c3 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -1,14 +1,12 @@ use crate::errors::Error; use crate::operation::Operation; use crate::server::{Server, VersionAdd}; -use crate::taskstorage::{TaskMap, TaskStorage}; +use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn}; use failure::Fallible; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::str; use uuid::Uuid; -#[derive(Debug)] pub struct DB { storage: Box, } @@ -35,23 +33,25 @@ impl DB { /// nothing and return an error (but leave the DB in a consistent state). pub fn apply(&mut self, op: Operation) -> Fallible<()> { // TODO: differentiate error types here? - if let err @ Err(_) = self.apply_op(&op) { + let mut txn = self.storage.txn()?; + if let err @ Err(_) = DB::apply_op(txn.as_mut(), &op) { return err; } - self.storage.add_operation(op)?; + txn.add_operation(op)?; + txn.commit()?; Ok(()) } - fn apply_op(&mut self, op: &Operation) -> Fallible<()> { + fn apply_op(txn: &mut dyn TaskStorageTxn, op: &Operation) -> Fallible<()> { match op { &Operation::Create { uuid } => { // insert if the task does not already exist - if !self.storage.create_task(uuid, HashMap::new())? { + if !txn.create_task(uuid)? { return Err(Error::DBError(format!("Task {} already exists", uuid)).into()); } } &Operation::Delete { ref uuid } => { - if !self.storage.delete_task(uuid)? { + if !txn.delete_task(uuid)? { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } } @@ -62,13 +62,13 @@ impl DB { timestamp: _, } => { // update if this task exists, otherwise ignore - if let Some(task) = self.storage.get_task(uuid)? { + if let Some(task) = txn.get_task(uuid)? { let mut task = task.clone(); match value { Some(ref val) => task.insert(property.to_string(), val.clone()), None => task.remove(property), }; - self.storage.set_task(uuid.clone(), task)?; + txn.set_task(uuid.clone(), task)?; } else { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } @@ -78,42 +78,43 @@ impl DB { Ok(()) } - /// Get all tasks. This is not a terribly efficient operation. - pub fn all_tasks<'a>(&'a self) -> Fallible + 'a> { - Ok(self - .all_task_uuids()? - // TODO: don't unwrap result (just option) - .map(move |u| (u, self.get_task(&u).unwrap().unwrap()))) + /// Get all tasks. + pub fn all_tasks<'a>(&'a mut self) -> Fallible> { + let mut txn = self.storage.txn()?; + txn.all_tasks() } /// Get the UUIDs of all tasks - pub fn all_task_uuids<'a>(&'a self) -> Fallible + 'a> { - self.storage.get_task_uuids() + pub fn all_task_uuids<'a>(&'a mut self) -> Fallible> { + let mut txn = self.storage.txn()?; + txn.all_task_uuids() } /// Get a single task, by uuid. - pub fn get_task(&self, uuid: &Uuid) -> Fallible> { - self.storage.get_task(uuid) + pub fn get_task(&mut self, uuid: &Uuid) -> Fallible> { + let mut txn = self.storage.txn()?; + txn.get_task(uuid) } /// Sync to the given server, pulling remote changes and pushing local changes. pub fn sync(&mut self, username: &str, server: &mut Server) -> Fallible<()> { + let mut txn = self.storage.txn()?; + // retry synchronizing until the server accepts our version (this allows for races between // replicas trying to sync to the same server) loop { // first pull changes and "rebase" on top of them - let new_versions = server.get_versions(username, self.storage.base_version()?); + let new_versions = server.get_versions(username, txn.base_version()?); for version_blob in new_versions { let version_str = str::from_utf8(&version_blob).unwrap(); let version: Version = serde_json::from_str(version_str).unwrap(); - assert_eq!(version.version, self.storage.base_version()? + 1); + assert_eq!(version.version, txn.base_version()? + 1); println!("applying version {:?} from server", version.version); - self.apply_version(version)?; + DB::apply_version(txn.as_mut(), version)?; } - let operations: Vec = - self.storage.operations()?.map(|o| o.clone()).collect(); + let operations: Vec = txn.operations()?.iter().map(|o| o.clone()).collect(); if operations.len() == 0 { // nothing to sync back to the server.. break; @@ -121,7 +122,7 @@ impl DB { // now make a version of our local changes and push those let new_version = Version { - version: self.storage.base_version()? + 1, + version: txn.base_version()? + 1, operations: operations, }; let new_version_str = serde_json::to_string(&new_version).unwrap(); @@ -129,15 +130,16 @@ impl DB { if let VersionAdd::Ok = server.add_version(username, new_version.version, new_version_str.into()) { - self.storage.local_operations_synced(new_version.version)?; + txn.local_operations_synced(new_version.version)?; break; } } + txn.commit()?; Ok(()) } - fn apply_version(&mut self, mut version: Version) -> Fallible<()> { + fn apply_version(txn: &mut dyn TaskStorageTxn, mut version: Version) -> Fallible<()> { // The situation here is that the server has already applied all server operations, and we // have already applied all local operations, so states have diverged by several // operations. We need to figure out what operations to apply locally and on the server in @@ -163,8 +165,7 @@ impl DB { // This is slightly complicated by the fact that the transform function can return None, // indicating no operation is required. If this happens for a local op, we can just omit // it. If it happens for server op, then we must copy the remaining local ops. - let mut local_operations: Vec = - self.storage.operations()?.map(|o| o.clone()).collect(); + let mut local_operations: Vec = txn.operations()?; for server_op in version.operations.drain(..) { let mut new_local_ops = Vec::with_capacity(local_operations.len()); let mut svr_op = Some(server_op); @@ -180,40 +181,41 @@ impl DB { } } if let Some(o) = svr_op { - if let Err(e) = self.apply_op(&o) { + if let Err(e) = DB::apply_op(txn, &o) { println!("Invalid operation when syncing: {} (ignored)", e); } } local_operations = new_local_ops; } - self.storage - .update_version(version.version, local_operations)?; + txn.update_version(version.version, local_operations)?; Ok(()) } // functions for supporting tests - pub fn sorted_tasks(&self) -> Vec<(Uuid, Vec<(String, String)>)> { + pub fn sorted_tasks(&mut self) -> Vec<(Uuid, Vec<(String, String)>)> { let mut res: Vec<(Uuid, Vec<(String, String)>)> = self .all_tasks() .unwrap() + .iter() .map(|(u, t)| { let mut t = t .iter() .map(|(p, v)| (p.clone(), v.clone())) .collect::>(); t.sort(); - (u, t) + (u.clone(), t) }) .collect(); res.sort(); res } - pub fn operations(&self) -> Vec { - self.storage - .operations() + pub fn operations(&mut self) -> Vec { + let mut txn = self.storage.txn().unwrap(); + txn.operations() .unwrap() + .iter() .map(|o| o.clone()) .collect() } @@ -223,6 +225,7 @@ impl DB { mod tests { use super::*; use chrono::Utc; + use std::collections::HashMap; use uuid::Uuid; #[test] diff --git a/src/taskstorage/inmemory.rs b/src/taskstorage/inmemory.rs index 790cd341d..e85dcd054 100644 --- a/src/taskstorage/inmemory.rs +++ b/src/taskstorage/inmemory.rs @@ -1,106 +1,145 @@ use crate::operation::Operation; -use crate::taskstorage::{TaskMap, TaskStorage}; +use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn}; use failure::Fallible; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; #[derive(PartialEq, Debug, Clone)] -pub struct InMemoryStorage { - // The current state, with all pending operations applied +struct Data { tasks: HashMap, - - // The version at which `operations` begins base_version: u64, - - // Operations applied since `base_version`, in order. - // - // INVARIANT: Given a snapshot at `base_version`, applying these operations produces `tasks`. operations: Vec, } -impl InMemoryStorage { - pub fn new() -> InMemoryStorage { - InMemoryStorage { - tasks: HashMap::new(), - base_version: 0, - operations: vec![], +struct Txn<'t> { + storage: &'t mut InMemoryStorage, + new_data: Option, +} + +impl<'t> Txn<'t> { + fn mut_data_ref(&mut self) -> &mut Data { + if self.new_data.is_none() { + self.new_data = Some(self.storage.data.clone()); + } + if let Some(ref mut data) = self.new_data { + data + } else { + unreachable!(); + } + } + + fn data_ref(&mut self) -> &Data { + if let Some(ref data) = self.new_data { + data + } else { + &self.storage.data } } } -impl TaskStorage for InMemoryStorage { - /// Get an (immutable) task, if it is in the storage - fn get_task(&self, uuid: &Uuid) -> Fallible> { - match self.tasks.get(uuid) { +impl<'t> TaskStorageTxn for Txn<'t> { + fn get_task(&mut self, uuid: &Uuid) -> Fallible> { + match self.data_ref().tasks.get(uuid) { None => Ok(None), Some(t) => Ok(Some(t.clone())), } } - /// Create a task, only if it does not already exist. Returns true if - /// the task was created (did not already exist). - fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible { - if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { - ent.or_insert(task); + fn create_task(&mut self, uuid: Uuid) -> Fallible { + if let ent @ Entry::Vacant(_) = self.mut_data_ref().tasks.entry(uuid) { + ent.or_insert(TaskMap::new()); Ok(true) } else { Ok(false) } } - /// Set a task, overwriting any existing task. fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible<()> { - self.tasks.insert(uuid, task); + self.mut_data_ref().tasks.insert(uuid, task); Ok(()) } - /// Delete a task, if it exists. Returns true if the task was deleted (already existed) fn delete_task(&mut self, uuid: &Uuid) -> Fallible { - if let Some(_) = self.tasks.remove(uuid) { + if let Some(_) = self.mut_data_ref().tasks.remove(uuid) { Ok(true) } else { Ok(false) } } - fn get_task_uuids<'a>(&'a self) -> Fallible + 'a>> { - Ok(Box::new(self.tasks.keys().map(|u| u.clone()))) + fn all_tasks<'a>(&mut self) -> Fallible> { + Ok(self + .data_ref() + .tasks + .iter() + .map(|(u, t)| (u.clone(), t.clone())) + .collect()) + } + + fn all_task_uuids<'a>(&mut self) -> Fallible> { + Ok(self.data_ref().tasks.keys().map(|u| u.clone()).collect()) } - /// Add an operation to the list of operations in the storage. Note that this merely *stores* - /// the operation; it is up to the TaskDB to apply it. fn add_operation(&mut self, op: Operation) -> Fallible<()> { - self.operations.push(op); + self.mut_data_ref().operations.push(op); Ok(()) } - /// Get the current base_version for this storage -- the last version synced from the server. - fn base_version(&self) -> Fallible { - Ok(self.base_version) + fn base_version(&mut self) -> Fallible { + Ok(self.data_ref().base_version) } - /// Get the current set of outstanding operations (operations that have not been sync'd to the - /// server yet) - fn operations<'a>(&'a self) -> Fallible + 'a>> { - Ok(Box::new(self.operations.iter())) + fn operations(&mut self) -> Fallible> { + Ok(self.data_ref().operations.clone()) } - /// Apply the next version from the server. This replaces the existing base_version and - /// operations. It's up to the caller (TaskDB) to ensure this is done consistently. fn update_version(&mut self, version: u64, new_operations: Vec) -> Fallible<()> { // ensure that we are applying the versions in order.. - assert_eq!(version, self.base_version + 1); - self.base_version = version; - self.operations = new_operations; + assert_eq!(version, self.data_ref().base_version + 1); + self.mut_data_ref().base_version = version; + self.mut_data_ref().operations = new_operations; Ok(()) } - /// Record the outstanding operations as synced to the server in the given version. fn local_operations_synced(&mut self, version: u64) -> Fallible<()> { - assert_eq!(version, self.base_version + 1); - self.base_version = version; - self.operations = vec![]; + assert_eq!(version, self.data_ref().base_version + 1); + self.mut_data_ref().base_version = version; + self.mut_data_ref().operations = vec![]; + Ok(()) + } + + fn commit(&mut self) -> Fallible<()> { + // copy the new_data back into storage to commit the transaction + if let Some(data) = self.new_data.take() { + self.storage.data = data; + } Ok(()) } } + +#[derive(PartialEq, Debug, Clone)] +pub struct InMemoryStorage { + data: Data, +} + +impl InMemoryStorage { + pub fn new() -> InMemoryStorage { + InMemoryStorage { + data: Data { + tasks: HashMap::new(), + base_version: 0, + operations: vec![], + }, + } + } +} + +impl TaskStorage for InMemoryStorage { + fn txn<'a>(&'a mut self) -> Fallible> { + Ok(Box::new(Txn { + storage: self, + new_data: None, + })) + } +} diff --git a/src/taskstorage/kv.rs b/src/taskstorage/kv.rs new file mode 100644 index 000000000..4e56f697d --- /dev/null +++ b/src/taskstorage/kv.rs @@ -0,0 +1,621 @@ +use crate::operation::Operation; +use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn}; +use failure::Fallible; +use kv::msgpack::Msgpack; +use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; +use std::convert::TryInto; +use std::path::Path; +use uuid::Uuid; + +/// A representation of a UUID as a key. This is just a newtype wrapping the 128-bit packed form +/// of a UUID. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +struct Key(uuid::Bytes); + +impl From<&[u8]> for Key { + fn from(bytes: &[u8]) -> Key { + let key = Key(bytes.try_into().unwrap()); + key + } +} + +impl From<&Uuid> for Key { + fn from(uuid: &Uuid) -> Key { + let key = Key(uuid.as_bytes().clone()); + key + } +} + +impl From for Key { + fn from(uuid: Uuid) -> Key { + let key = Key(uuid.as_bytes().clone()); + key + } +} + +impl From for Uuid { + fn from(key: Key) -> Uuid { + Uuid::from_bytes(key.0) + } +} + +impl AsRef<[u8]> for Key { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +/// KVStorage is an on-disk storage backend which uses LMDB via the `kv` crate. +pub struct KVStorage<'t> { + store: Store, + tasks_bucket: Bucket<'t, Key, ValueBuf>>, + numbers_bucket: Bucket<'t, Integer, ValueBuf>>, + operations_bucket: Bucket<'t, Integer, ValueBuf>>, +} + +const BASE_VERSION: u64 = 1; +const NEXT_OPERATION: u64 = 2; + +impl<'t> KVStorage<'t> { + pub fn new(directory: &Path) -> Fallible { + let mut config = Config::default(directory); + config.bucket("tasks", None); + config.bucket("numbers", None); + config.bucket("operations", None); + let store = Store::new(config)?; + + // tasks are stored indexed by uuid + let tasks_bucket = store.bucket::>>(Some("tasks"))?; + + // this bucket contains various u64s, indexed by constants above + let numbers_bucket = store.int_bucket::>>(Some("numbers"))?; + + // this bucket contains operations, numbered consecutively + let operations_bucket = + store.int_bucket::>>(Some("operations"))?; + + Ok(KVStorage { + store, + tasks_bucket, + numbers_bucket, + operations_bucket, + }) + } +} + +impl<'t> TaskStorage for KVStorage<'t> { + fn txn<'a>(&'a mut self) -> Fallible> { + Ok(Box::new(Txn { + storage: self, + txn: Some(self.store.write_txn()?), + })) + } +} + +struct Txn<'t> { + storage: &'t KVStorage<'t>, + txn: Option>, +} + +impl<'t> Txn<'t> { + // get the underlying kv Txn + fn kvtxn<'a>(&mut self) -> &mut kv::Txn<'t> { + if let Some(ref mut txn) = self.txn { + txn + } else { + panic!("cannot use transaction after commit"); + } + } + + // Access to buckets + fn tasks_bucket(&self) -> &'t Bucket<'t, Key, ValueBuf>> { + &self.storage.tasks_bucket + } + fn numbers_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { + &self.storage.numbers_bucket + } + fn operations_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { + &self.storage.operations_bucket + } +} + +impl<'t> TaskStorageTxn for Txn<'t> { + fn get_task(&mut self, uuid: &Uuid) -> Fallible> { + let bucket = self.tasks_bucket(); + let buf = match self.kvtxn().get(bucket, uuid.into()) { + Ok(buf) => buf, + Err(Error::NotFound) => return Ok(None), + Err(e) => return Err(e.into()), + }; + let value = buf.inner()?.to_serde(); + Ok(Some(value)) + } + + fn create_task(&mut self, uuid: Uuid) -> Fallible { + let bucket = self.tasks_bucket(); + let kvtxn = self.kvtxn(); + match kvtxn.get(bucket, uuid.into()) { + Err(Error::NotFound) => { + kvtxn.set(bucket, uuid.into(), Msgpack::to_value_buf(TaskMap::new())?)?; + Ok(true) + } + Err(e) => Err(e.into()), + Ok(_) => Ok(false), + } + } + + fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible<()> { + let bucket = self.tasks_bucket(); + let kvtxn = self.kvtxn(); + kvtxn.set(bucket, uuid.into(), Msgpack::to_value_buf(task)?)?; + Ok(()) + } + + fn delete_task(&mut self, uuid: &Uuid) -> Fallible { + let bucket = self.tasks_bucket(); + let kvtxn = self.kvtxn(); + match kvtxn.del(bucket, uuid.into()) { + Err(Error::NotFound) => Ok(false), + Err(e) => Err(e.into()), + Ok(_) => Ok(true), + } + } + + fn all_tasks(&mut self) -> Fallible> { + let bucket = self.tasks_bucket(); + let kvtxn = self.kvtxn(); + let curs = kvtxn.read_cursor(bucket)?; + let all_tasks: Result, Error> = kvtxn + .read_cursor(bucket)? + .iter() + .map(|(k, v)| Ok((k.into(), v.inner()?.to_serde()))) + .collect(); + Ok(all_tasks?) + } + + fn all_task_uuids(&mut self) -> Fallible> { + let bucket = self.tasks_bucket(); + let kvtxn = self.kvtxn(); + let curs = kvtxn.read_cursor(bucket)?; + Ok(kvtxn + .read_cursor(bucket)? + .iter() + .map(|(k, _)| k.into()) + .collect()) + } + + fn add_operation(&mut self, op: Operation) -> Fallible<()> { + let numbers_bucket = self.numbers_bucket(); + let operations_bucket = self.operations_bucket(); + let kvtxn = self.kvtxn(); + + let next_op = match kvtxn.get(numbers_bucket, NEXT_OPERATION.into()) { + Ok(buf) => buf.inner()?.to_serde(), + Err(Error::NotFound) => 0, + Err(e) => return Err(e.into()), + }; + + kvtxn.set( + operations_bucket, + next_op.into(), + Msgpack::to_value_buf(op)?, + )?; + kvtxn.set( + numbers_bucket, + NEXT_OPERATION.into(), + Msgpack::to_value_buf(next_op + 1)?, + )?; + Ok(()) + } + + fn base_version(&mut self) -> Fallible { + let bucket = self.numbers_bucket(); + let base_version = match self.kvtxn().get(bucket, BASE_VERSION.into()) { + Ok(buf) => buf, + Err(Error::NotFound) => return Ok(0), + Err(e) => return Err(e.into()), + } + .inner()? + .to_serde(); + Ok(base_version) + } + + fn operations(&mut self) -> Fallible> { + let bucket = self.operations_bucket(); + let kvtxn = self.kvtxn(); + let curs = kvtxn.read_cursor(bucket)?; + let all_ops: Result, Error> = kvtxn + .read_cursor(bucket)? + .iter() + .map(|(i, v)| Ok((i.into(), v.inner()?.to_serde()))) + .collect(); + let mut all_ops = all_ops?; + // sort by key.. + all_ops.sort_by(|a, b| a.0.cmp(&b.0)); + // and return the values.. + Ok(all_ops.iter().map(|(_, v)| v.clone()).collect()) + } + + fn update_version(&mut self, version: u64, new_operations: Vec) -> Fallible<()> { + let numbers_bucket = self.numbers_bucket(); + let operations_bucket = self.operations_bucket(); + let kvtxn = self.kvtxn(); + + kvtxn.clear_db(operations_bucket)?; + + let mut i = 0u64; + for op in new_operations { + kvtxn.set(operations_bucket, i.into(), Msgpack::to_value_buf(op)?)?; + i += 1; + } + + kvtxn.set( + numbers_bucket, + BASE_VERSION.into(), + Msgpack::to_value_buf(version)?, + )?; + + kvtxn.set( + numbers_bucket, + NEXT_OPERATION.into(), + Msgpack::to_value_buf(i)?, + )?; + + Ok(()) + } + + fn local_operations_synced(&mut self, version: u64) -> Fallible<()> { + let numbers_bucket = self.numbers_bucket(); + let operations_bucket = self.operations_bucket(); + let kvtxn = self.kvtxn(); + + kvtxn.clear_db(operations_bucket)?; + + kvtxn.set( + numbers_bucket, + BASE_VERSION.into(), + Msgpack::to_value_buf(version)?, + )?; + + kvtxn.set( + numbers_bucket, + NEXT_OPERATION.into(), + Msgpack::to_value_buf(0)?, + )?; + + Ok(()) + } + + fn commit(&mut self) -> Fallible<()> { + if let Some(kvtxn) = self.txn.take() { + kvtxn.commit()?; + } else { + panic!("transaction already committed"); + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::taskstorage::taskmap_with; + use failure::Fallible; + use tempdir::TempDir; + + #[test] + fn test_create() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid.clone())?); + txn.commit()?; + } + { + let mut txn = storage.txn()?; + let task = txn.get_task(&uuid)?; + assert_eq!(task, Some(taskmap_with(vec![]))); + } + Ok(()) + } + + #[test] + fn test_create_exists() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid.clone())?); + txn.commit()?; + } + { + let mut txn = storage.txn()?; + assert!(!txn.create_task(uuid.clone())?); + txn.commit()?; + } + Ok(()) + } + + #[test] + fn test_get_missing() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + let task = txn.get_task(&uuid)?; + assert_eq!(task, None); + } + Ok(()) + } + + #[test] + fn test_set_task() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + txn.set_task( + uuid.clone(), + taskmap_with(vec![("k".to_string(), "v".to_string())]), + )?; + txn.commit()?; + } + { + let mut txn = storage.txn()?; + let task = txn.get_task(&uuid)?; + assert_eq!( + task, + Some(taskmap_with(vec![("k".to_string(), "v".to_string())])) + ); + } + Ok(()) + } + + #[test] + fn test_delete_task_missing() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(!txn.delete_task(&uuid)?); + } + Ok(()) + } + + #[test] + fn test_delete_task_exists() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid.clone())?); + txn.commit()?; + } + { + let mut txn = storage.txn()?; + assert!(txn.delete_task(&uuid)?); + } + Ok(()) + } + + #[test] + fn test_all_tasks_empty() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + { + let mut txn = storage.txn()?; + let tasks = txn.all_tasks()?; + assert_eq!(tasks, vec![]); + } + Ok(()) + } + + #[test] + fn test_all_tasks_and_uuids() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid1.clone())?); + txn.set_task( + uuid1.clone(), + taskmap_with(vec![("num".to_string(), "1".to_string())]), + )?; + assert!(txn.create_task(uuid2.clone())?); + txn.set_task( + uuid2.clone(), + taskmap_with(vec![("num".to_string(), "2".to_string())]), + )?; + txn.commit()?; + } + { + let mut txn = storage.txn()?; + let mut tasks = txn.all_tasks()?; + + // order is nondeterministic, so sort by uuid + tasks.sort_by(|a, b| a.0.cmp(&b.0)); + + let mut exp = vec![ + ( + uuid1.clone(), + taskmap_with(vec![("num".to_string(), "1".to_string())]), + ), + ( + uuid2.clone(), + taskmap_with(vec![("num".to_string(), "2".to_string())]), + ), + ]; + exp.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!(tasks, exp); + } + { + let mut txn = storage.txn()?; + let mut uuids = txn.all_task_uuids()?; + uuids.sort(); + + let mut exp = vec![uuid1.clone(), uuid2.clone()]; + exp.sort(); + + assert_eq!(uuids, exp); + } + Ok(()) + } + + #[test] + fn test_base_version_default() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + { + let mut txn = storage.txn()?; + assert_eq!(txn.base_version()?, 0); + } + Ok(()) + } + + #[test] + fn test_operations() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + // create some operations + { + let mut txn = storage.txn()?; + txn.add_operation(Operation::Create { uuid: uuid1 })?; + txn.add_operation(Operation::Create { uuid: uuid2 })?; + txn.commit()?; + } + + // read them back + { + let mut txn = storage.txn()?; + let ops = txn.operations()?; + assert_eq!( + ops, + vec![ + Operation::Create { uuid: uuid1 }, + Operation::Create { uuid: uuid2 }, + ] + ); + } + + // report them sync'd to the server + { + let mut txn = storage.txn()?; + txn.local_operations_synced(1)?; + txn.commit()?; + } + + // check that the operations are gone and the base version is incremented + { + let mut txn = storage.txn()?; + let ops = txn.operations()?; + assert_eq!(ops, vec![]); + assert_eq!(txn.base_version()?, 1); + } + + // create some more operations (to test adding operations after clearing) + { + let mut txn = storage.txn()?; + txn.add_operation(Operation::Delete { uuid: uuid2 })?; + txn.add_operation(Operation::Delete { uuid: uuid1 })?; + txn.commit()?; + } + + // read them back + { + let mut txn = storage.txn()?; + let ops = txn.operations()?; + assert_eq!( + ops, + vec![ + Operation::Delete { uuid: uuid2 }, + Operation::Delete { uuid: uuid1 }, + ] + ); + } + Ok(()) + } + + #[test] + fn test_update_version() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + let uuid3 = Uuid::new_v4(); + let uuid4 = Uuid::new_v4(); + + // create some operations + { + let mut txn = storage.txn()?; + txn.add_operation(Operation::Create { uuid: uuid1 })?; + txn.add_operation(Operation::Create { uuid: uuid2 })?; + txn.add_operation(Operation::Create { uuid: uuid3 })?; + txn.add_operation(Operation::Delete { uuid: uuid2 })?; + txn.commit()?; + } + + // update version from the server.. + { + let mut txn = storage.txn()?; + txn.update_version( + 1, + vec![ + Operation::Create { uuid: uuid2 }, + Operation::Delete { uuid: uuid2 }, + ], + )?; + txn.commit()?; + } + + // check that the operations are updated and the base version is incremented + { + let mut txn = storage.txn()?; + let ops = txn.operations()?; + assert_eq!( + ops, + vec![ + Operation::Create { uuid: uuid2 }, + Operation::Delete { uuid: uuid2 }, + ] + ); + assert_eq!(txn.base_version()?, 1); + } + + // create some more operations (to test adding operations after updating) + { + let mut txn = storage.txn()?; + txn.add_operation(Operation::Create { uuid: uuid4 })?; + txn.add_operation(Operation::Delete { uuid: uuid4 })?; + txn.commit()?; + } + + // read them back + { + let mut txn = storage.txn()?; + let ops = txn.operations()?; + assert_eq!( + ops, + vec![ + Operation::Create { uuid: uuid2 }, + Operation::Delete { uuid: uuid2 }, + Operation::Create { uuid: uuid4 }, + Operation::Delete { uuid: uuid4 }, + ] + ); + } + Ok(()) + } +} diff --git a/src/taskstorage/lmdb.rs b/src/taskstorage/lmdb.rs deleted file mode 100644 index 1a7d75c7f..000000000 --- a/src/taskstorage/lmdb.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::operation::Operation; -use crate::taskstorage::{TaskMap, TaskStorage}; -use kv::{Config, Error, Manager, ValueRef}; -use uuid::Uuid; - -pub struct KVStorage { - // TODO: make the manager global with lazy-static - manager: Manager, - config: Config, -} - -impl KVStorage { - pub fn new(directory: &str) -> KVStorage { - let mut config = Config::default(directory); - config.bucket("base_version", None); - config.bucket("operations", None); - config.bucket("tasks", None); - KVStorage { - manager: Manager::new(), - config, - } - } -} - -impl TaskStorage for KVStorage { - /// Get an (immutable) task, if it is in the storage - fn get_task(&self, uuid: &Uuid) -> Option { - match self.tasks.get(uuid) { - None => None, - Some(t) => Some(t.clone()), - } - } - - /// Create a task, only if it does not already exist. Returns true if - /// the task was created (did not already exist). - fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> bool { - if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { - ent.or_insert(task); - true - } else { - false - } - } - - /// Set a task, overwriting any existing task. - fn set_task(&mut self, uuid: Uuid, task: TaskMap) { - self.tasks.insert(uuid, task); - } - - /// Delete a task, if it exists. Returns true if the task was deleted (already existed) - fn delete_task(&mut self, uuid: &Uuid) -> bool { - if let Some(_) = self.tasks.remove(uuid) { - true - } else { - false - } - } - - fn get_task_uuids<'a>(&'a self) -> Box + 'a> { - Box::new(self.tasks.keys().map(|u| u.clone())) - } - - /// Add an operation to the list of operations in the storage. Note that this merely *stores* - /// the operation; it is up to the TaskDB to apply it. - fn add_operation(&mut self, op: Operation) { - self.operations.push(op); - } - - /// Get the current base_version for this storage -- the last version synced from the server. - fn base_version(&self) -> u64 { - return self.base_version; - } - - /// Get the current set of outstanding operations (operations that have not been sync'd to the - /// server yet) - fn operations<'a>(&'a self) -> Box + 'a> { - Box::new(self.operations.iter()) - } - - /// Apply the next version from the server. This replaces the existing base_version and - /// operations. It's up to the caller (TaskDB) to ensure this is done consistently. - fn update_version(&mut self, version: u64, new_operations: Vec) { - // ensure that we are applying the versions in order.. - assert_eq!(version, self.base_version + 1); - self.base_version = version; - self.operations = new_operations; - } - - /// Record the outstanding operations as synced to the server in the given version. - fn local_operations_synced(&mut self, version: u64) { - assert_eq!(version, self.base_version + 1); - self.base_version = version; - self.operations = vec![]; - } -} diff --git a/src/taskstorage/mod.rs b/src/taskstorage/mod.rs index 3f339ac29..d7a175596 100644 --- a/src/taskstorage/mod.rs +++ b/src/taskstorage/mod.rs @@ -1,51 +1,91 @@ use crate::Operation; use failure::Fallible; use std::collections::HashMap; -use std::fmt; use uuid::Uuid; mod inmemory; +mod kv; pub use inmemory::InMemoryStorage; /// An in-memory representation of a task as a simple hashmap pub type TaskMap = HashMap; -/// A trait for objects able to act as backing storage for a TaskDB. This API is optimized to be -/// easy to implement, with all of the semantic meaning of the data located in the TaskDB -/// implementation, which is the sole consumer of this trait. -pub trait TaskStorage: fmt::Debug { +#[cfg(test)] +fn taskmap_with(mut properties: Vec<(String, String)>) -> TaskMap { + let mut rv = TaskMap::new(); + for (p, v) in properties.drain(..) { + rv.insert(p, v); + } + rv +} + +/// A TaskStorage transaction, in which storage operations are performed. +/// Serializable consistency is maintained, and implementations do not optimize +/// for concurrent access so some may simply apply a mutex to limit access to +/// one transaction at a time. Transactions are aborted if they are dropped. +/// It's safe to drop transactions that did not modify any data. +pub trait TaskStorageTxn { /// Get an (immutable) task, if it is in the storage - fn get_task(&self, uuid: &Uuid) -> Fallible>; + fn get_task(&mut self, uuid: &Uuid) -> Fallible>; - /// Create a task, only if it does not already exist. Returns true if + /// Create an (empty) task, only if it does not already exist. Returns true if /// the task was created (did not already exist). - fn create_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible; + fn create_task(&mut self, uuid: Uuid) -> Fallible; - /// Set a task, overwriting any existing task. + /// Set a task, overwriting any existing task. If the task does not exist, this implicitly + /// creates it (use `get_task` to check first, if necessary). fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible<()>; /// Delete a task, if it exists. Returns true if the task was deleted (already existed) fn delete_task(&mut self, uuid: &Uuid) -> Fallible; + /// Get the uuids and bodies of all tasks in the storage, in undefined order. + fn all_tasks<'a>(&mut self) -> Fallible>; + /// Get the uuids of all tasks in the storage, in undefined order. - fn get_task_uuids<'a>(&'a self) -> Fallible + 'a>>; + fn all_task_uuids<'a>(&mut self) -> Fallible>; /// Add an operation to the list of operations in the storage. Note that this merely *stores* - /// the operation; it is up to the TaskDB to apply it. + /// the operation; it is up to the DB to apply it. fn add_operation(&mut self, op: Operation) -> Fallible<()>; /// Get the current base_version for this storage -- the last version synced from the server. - fn base_version(&self) -> Fallible; + fn base_version(&mut self) -> Fallible; /// Get the current set of outstanding operations (operations that have not been sync'd to the /// server yet) - fn operations<'a>(&'a self) -> Fallible + 'a>>; + fn operations<'a>(&mut self) -> Fallible>; /// Apply the next version from the server. This replaces the existing base_version and - /// operations. It's up to the caller (TaskDB) to ensure this is done consistently. + /// operations. It's up to the caller (DB) to ensure this is done consistently. fn update_version(&mut self, version: u64, new_operations: Vec) -> Fallible<()>; - /// Record the outstanding operations as synced to the server in the given version. + /// Record the outstanding operations as synced to the server in the given version: set + /// the base_version to the given value, and empty the operations list. fn local_operations_synced(&mut self, version: u64) -> Fallible<()>; + + /// Commit any changes made in the transaction. It is an error to call this more than + /// once. + fn commit(&mut self) -> Fallible<()>; +} + +/// A trait for objects able to act as backing storage for a DB. This API is optimized to be +/// easy to implement, with all of the semantic meaning of the data located in the DB +/// implementation, which is the sole consumer of this trait. +/// +/// Conceptually, task storage contains the following: +/// +/// - tasks: a set of tasks indexed by uuid +/// - base_version: the number of the last version sync'd from the server +/// - operations: all operations performed since base_version +/// +/// The `operations` are already reflected in `tasks`, so the following invariant holds: +/// > Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical +/// > to `tasks`. +/// +/// It is up to the caller (DB) to maintain this invariant. +pub trait TaskStorage { + /// Begin a transaction + fn txn<'a>(&'a mut self) -> Fallible>; } diff --git a/tests/sync_action_sequences.rs b/tests/sync_action_sequences.rs index 58ed9ba77..ddc8d328c 100644 --- a/tests/sync_action_sequences.rs +++ b/tests/sync_action_sequences.rs @@ -63,10 +63,6 @@ proptest! { } } - println!("{:?}", dbs[0]); - println!("{:?}", dbs[1]); - println!("{:?}", dbs[2]); - assert_eq!(dbs[0].sorted_tasks(), dbs[0].sorted_tasks()); assert_eq!(dbs[1].sorted_tasks(), dbs[2].sorted_tasks()); } From 4172c7012cb3daeadeeba98a4ac074d236d44ec9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 12 Jan 2020 13:48:16 -0500 Subject: [PATCH 035/548] simplify the taskstorage methods --- src/taskdb.rs | 6 +- src/taskstorage/inmemory.rs | 23 ++-- src/taskstorage/kv.rs | 209 ++++++++++++------------------------ src/taskstorage/mod.rs | 18 ++-- 4 files changed, 89 insertions(+), 167 deletions(-) diff --git a/src/taskdb.rs b/src/taskdb.rs index 20a8b74c3..3e9fbe0c1 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -130,7 +130,8 @@ impl DB { if let VersionAdd::Ok = server.add_version(username, new_version.version, new_version_str.into()) { - txn.local_operations_synced(new_version.version)?; + txn.set_base_version(new_version.version)?; + txn.set_operations(vec![])?; break; } } @@ -187,7 +188,8 @@ impl DB { } local_operations = new_local_ops; } - txn.update_version(version.version, local_operations)?; + txn.set_base_version(version.version)?; + txn.set_operations(local_operations)?; Ok(()) } diff --git a/src/taskstorage/inmemory.rs b/src/taskstorage/inmemory.rs index e85dcd054..ef654b0f3 100644 --- a/src/taskstorage/inmemory.rs +++ b/src/taskstorage/inmemory.rs @@ -81,31 +81,26 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(self.data_ref().tasks.keys().map(|u| u.clone()).collect()) } - fn add_operation(&mut self, op: Operation) -> Fallible<()> { - self.mut_data_ref().operations.push(op); - Ok(()) - } - fn base_version(&mut self) -> Fallible { Ok(self.data_ref().base_version) } + fn set_base_version(&mut self, version: u64) -> Fallible<()> { + self.mut_data_ref().base_version = version; + Ok(()) + } + fn operations(&mut self) -> Fallible> { Ok(self.data_ref().operations.clone()) } - fn update_version(&mut self, version: u64, new_operations: Vec) -> Fallible<()> { - // ensure that we are applying the versions in order.. - assert_eq!(version, self.data_ref().base_version + 1); - self.mut_data_ref().base_version = version; - self.mut_data_ref().operations = new_operations; + fn add_operation(&mut self, op: Operation) -> Fallible<()> { + self.mut_data_ref().operations.push(op); Ok(()) } - fn local_operations_synced(&mut self, version: u64) -> Fallible<()> { - assert_eq!(version, self.data_ref().base_version + 1); - self.mut_data_ref().base_version = version; - self.mut_data_ref().operations = vec![]; + fn set_operations(&mut self, ops: Vec) -> Fallible<()> { + self.mut_data_ref().operations = ops; Ok(()) } diff --git a/src/taskstorage/kv.rs b/src/taskstorage/kv.rs index 4e56f697d..5506b7bd5 100644 --- a/src/taskstorage/kv.rs +++ b/src/taskstorage/kv.rs @@ -184,6 +184,46 @@ impl<'t> TaskStorageTxn for Txn<'t> { .collect()) } + fn base_version(&mut self) -> Fallible { + let bucket = self.numbers_bucket(); + let base_version = match self.kvtxn().get(bucket, BASE_VERSION.into()) { + Ok(buf) => buf, + Err(Error::NotFound) => return Ok(0), + Err(e) => return Err(e.into()), + } + .inner()? + .to_serde(); + Ok(base_version) + } + + fn set_base_version(&mut self, version: u64) -> Fallible<()> { + let numbers_bucket = self.numbers_bucket(); + let kvtxn = self.kvtxn(); + + kvtxn.set( + numbers_bucket, + BASE_VERSION.into(), + Msgpack::to_value_buf(version)?, + )?; + Ok(()) + } + + fn operations(&mut self) -> Fallible> { + let bucket = self.operations_bucket(); + let kvtxn = self.kvtxn(); + let curs = kvtxn.read_cursor(bucket)?; + let all_ops: Result, Error> = kvtxn + .read_cursor(bucket)? + .iter() + .map(|(i, v)| Ok((i.into(), v.inner()?.to_serde()))) + .collect(); + let mut all_ops = all_ops?; + // sort by key.. + all_ops.sort_by(|a, b| a.0.cmp(&b.0)); + // and return the values.. + Ok(all_ops.iter().map(|(_, v)| v.clone()).collect()) + } + fn add_operation(&mut self, op: Operation) -> Fallible<()> { let numbers_bucket = self.numbers_bucket(); let operations_bucket = self.operations_bucket(); @@ -208,35 +248,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(()) } - fn base_version(&mut self) -> Fallible { - let bucket = self.numbers_bucket(); - let base_version = match self.kvtxn().get(bucket, BASE_VERSION.into()) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(0), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(base_version) - } - - fn operations(&mut self) -> Fallible> { - let bucket = self.operations_bucket(); - let kvtxn = self.kvtxn(); - let curs = kvtxn.read_cursor(bucket)?; - let all_ops: Result, Error> = kvtxn - .read_cursor(bucket)? - .iter() - .map(|(i, v)| Ok((i.into(), v.inner()?.to_serde()))) - .collect(); - let mut all_ops = all_ops?; - // sort by key.. - all_ops.sort_by(|a, b| a.0.cmp(&b.0)); - // and return the values.. - Ok(all_ops.iter().map(|(_, v)| v.clone()).collect()) - } - - fn update_version(&mut self, version: u64, new_operations: Vec) -> Fallible<()> { + fn set_operations(&mut self, ops: Vec) -> Fallible<()> { let numbers_bucket = self.numbers_bucket(); let operations_bucket = self.operations_bucket(); let kvtxn = self.kvtxn(); @@ -244,17 +256,11 @@ impl<'t> TaskStorageTxn for Txn<'t> { kvtxn.clear_db(operations_bucket)?; let mut i = 0u64; - for op in new_operations { + for op in ops { kvtxn.set(operations_bucket, i.into(), Msgpack::to_value_buf(op)?)?; i += 1; } - kvtxn.set( - numbers_bucket, - BASE_VERSION.into(), - Msgpack::to_value_buf(version)?, - )?; - kvtxn.set( numbers_bucket, NEXT_OPERATION.into(), @@ -264,28 +270,6 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(()) } - fn local_operations_synced(&mut self, version: u64) -> Fallible<()> { - let numbers_bucket = self.numbers_bucket(); - let operations_bucket = self.operations_bucket(); - let kvtxn = self.kvtxn(); - - kvtxn.clear_db(operations_bucket)?; - - kvtxn.set( - numbers_bucket, - BASE_VERSION.into(), - Msgpack::to_value_buf(version)?, - )?; - - kvtxn.set( - numbers_bucket, - NEXT_OPERATION.into(), - Msgpack::to_value_buf(0)?, - )?; - - Ok(()) - } - fn commit(&mut self) -> Fallible<()> { if let Some(kvtxn) = self.txn.take() { kvtxn.commit()?; @@ -482,12 +466,29 @@ mod test { Ok(()) } + #[test] + fn test_base_version_setting() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + { + let mut txn = storage.txn()?; + txn.set_base_version(3)?; + txn.commit()?; + } + { + let mut txn = storage.txn()?; + assert_eq!(txn.base_version()?, 3); + } + Ok(()) + } + #[test] fn test_operations() -> Fallible<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); + let uuid3 = Uuid::new_v4(); // create some operations { @@ -510,26 +511,21 @@ mod test { ); } - // report them sync'd to the server + // set them to a different bunch { let mut txn = storage.txn()?; - txn.local_operations_synced(1)?; + txn.set_operations(vec![ + Operation::Delete { uuid: uuid2 }, + Operation::Delete { uuid: uuid1 }, + ])?; txn.commit()?; } - // check that the operations are gone and the base version is incremented - { - let mut txn = storage.txn()?; - let ops = txn.operations()?; - assert_eq!(ops, vec![]); - assert_eq!(txn.base_version()?, 1); - } - // create some more operations (to test adding operations after clearing) { let mut txn = storage.txn()?; - txn.add_operation(Operation::Delete { uuid: uuid2 })?; - txn.add_operation(Operation::Delete { uuid: uuid1 })?; + txn.add_operation(Operation::Create { uuid: uuid3 })?; + txn.add_operation(Operation::Delete { uuid: uuid3 })?; txn.commit()?; } @@ -542,77 +538,8 @@ mod test { vec![ Operation::Delete { uuid: uuid2 }, Operation::Delete { uuid: uuid1 }, - ] - ); - } - Ok(()) - } - - #[test] - fn test_update_version() -> Fallible<()> { - let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - let uuid3 = Uuid::new_v4(); - let uuid4 = Uuid::new_v4(); - - // create some operations - { - let mut txn = storage.txn()?; - txn.add_operation(Operation::Create { uuid: uuid1 })?; - txn.add_operation(Operation::Create { uuid: uuid2 })?; - txn.add_operation(Operation::Create { uuid: uuid3 })?; - txn.add_operation(Operation::Delete { uuid: uuid2 })?; - txn.commit()?; - } - - // update version from the server.. - { - let mut txn = storage.txn()?; - txn.update_version( - 1, - vec![ - Operation::Create { uuid: uuid2 }, - Operation::Delete { uuid: uuid2 }, - ], - )?; - txn.commit()?; - } - - // check that the operations are updated and the base version is incremented - { - let mut txn = storage.txn()?; - let ops = txn.operations()?; - assert_eq!( - ops, - vec![ - Operation::Create { uuid: uuid2 }, - Operation::Delete { uuid: uuid2 }, - ] - ); - assert_eq!(txn.base_version()?, 1); - } - - // create some more operations (to test adding operations after updating) - { - let mut txn = storage.txn()?; - txn.add_operation(Operation::Create { uuid: uuid4 })?; - txn.add_operation(Operation::Delete { uuid: uuid4 })?; - txn.commit()?; - } - - // read them back - { - let mut txn = storage.txn()?; - let ops = txn.operations()?; - assert_eq!( - ops, - vec![ - Operation::Create { uuid: uuid2 }, - Operation::Delete { uuid: uuid2 }, - Operation::Create { uuid: uuid4 }, - Operation::Delete { uuid: uuid4 }, + Operation::Create { uuid: uuid3 }, + Operation::Delete { uuid: uuid3 }, ] ); } diff --git a/src/taskstorage/mod.rs b/src/taskstorage/mod.rs index d7a175596..60541cfa8 100644 --- a/src/taskstorage/mod.rs +++ b/src/taskstorage/mod.rs @@ -46,24 +46,22 @@ pub trait TaskStorageTxn { /// Get the uuids of all tasks in the storage, in undefined order. fn all_task_uuids<'a>(&mut self) -> Fallible>; - /// Add an operation to the list of operations in the storage. Note that this merely *stores* - /// the operation; it is up to the DB to apply it. - fn add_operation(&mut self, op: Operation) -> Fallible<()>; - /// Get the current base_version for this storage -- the last version synced from the server. fn base_version(&mut self) -> Fallible; + /// Set the current base_version for this storage. + fn set_base_version(&mut self, version: u64) -> Fallible<()>; + /// Get the current set of outstanding operations (operations that have not been sync'd to the /// server yet) fn operations<'a>(&mut self) -> Fallible>; - /// Apply the next version from the server. This replaces the existing base_version and - /// operations. It's up to the caller (DB) to ensure this is done consistently. - fn update_version(&mut self, version: u64, new_operations: Vec) -> Fallible<()>; + /// Add an operation to the end of the list of operations in the storage. Note that this + /// merely *stores* the operation; it is up to the DB to apply it. + fn add_operation(&mut self, op: Operation) -> Fallible<()>; - /// Record the outstanding operations as synced to the server in the given version: set - /// the base_version to the given value, and empty the operations list. - fn local_operations_synced(&mut self, version: u64) -> Fallible<()>; + /// Replace the current list of operations with a new list. + fn set_operations(&mut self, ops: Vec) -> Fallible<()>; /// Commit any changes made in the transaction. It is an error to call this more than /// once. From f8977c9ce3da755e047a934ec3650c3023a21a46 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 12 Jan 2020 13:51:58 -0500 Subject: [PATCH 036/548] use kv in cli --- src/bin/task.rs | 8 +++++++- src/taskstorage/mod.rs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/bin/task.rs b/src/bin/task.rs index d9ce502e2..d692cf384 100644 --- a/src/bin/task.rs +++ b/src/bin/task.rs @@ -1,5 +1,6 @@ extern crate clap; use clap::{App, Arg, SubCommand}; +use std::path::Path; use taskwarrior_rust::{taskstorage, Replica, DB}; use uuid::Uuid; @@ -16,7 +17,12 @@ fn main() { .subcommand(SubCommand::with_name("list").about("lists tasks")) .get_matches(); - let mut replica = Replica::new(DB::new(Box::new(taskstorage::InMemoryStorage::new())).into()); + let mut replica = Replica::new( + DB::new(Box::new( + taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(), + )) + .into(), + ); match matches.subcommand() { ("add", Some(matches)) => { diff --git a/src/taskstorage/mod.rs b/src/taskstorage/mod.rs index 60541cfa8..092707225 100644 --- a/src/taskstorage/mod.rs +++ b/src/taskstorage/mod.rs @@ -6,6 +6,7 @@ use uuid::Uuid; mod inmemory; mod kv; +pub use self::kv::KVStorage; pub use inmemory::InMemoryStorage; /// An in-memory representation of a task as a simple hashmap From 4203f232ad92dae3e83032479a7c23f3fcf91578 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 12 Jan 2020 16:00:40 -0500 Subject: [PATCH 037/548] docs --- src/taskstorage/kv.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/taskstorage/kv.rs b/src/taskstorage/kv.rs index 5506b7bd5..20c4da301 100644 --- a/src/taskstorage/kv.rs +++ b/src/taskstorage/kv.rs @@ -70,7 +70,8 @@ impl<'t> KVStorage<'t> { // this bucket contains various u64s, indexed by constants above let numbers_bucket = store.int_bucket::>>(Some("numbers"))?; - // this bucket contains operations, numbered consecutively + // this bucket contains operations, numbered consecutively; the NEXT_OPERATION number gives + // the index of the next operation to insert let operations_bucket = store.int_bucket::>>(Some("operations"))?; From ad08991292fc51a2bd8f654c5768119902f1d5a3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 12 Jan 2020 17:52:00 -0500 Subject: [PATCH 038/548] Start on making replica higher-level, with some TODO's --- TODO.txt | 1 + src/bin/task.rs | 17 ++-- src/lib.rs | 2 + src/replica.rs | 186 +++++++++++++++++++++++++++++++++------- src/task/task.rs | 60 +++++++++++-- src/task/taskbuilder.rs | 35 ++------ 6 files changed, 228 insertions(+), 73 deletions(-) diff --git a/TODO.txt b/TODO.txt index 045580c49..23ad27b6f 100644 --- a/TODO.txt +++ b/TODO.txt @@ -7,6 +7,7 @@ * add HTTP API * add pending-task indexing to Replica * abstract server into trait +* fix TODO items in replica.rs * implement snapshot requests * implement backups * implement client-side encryption diff --git a/src/bin/task.rs b/src/bin/task.rs index d692cf384..13a3a749b 100644 --- a/src/bin/task.rs +++ b/src/bin/task.rs @@ -1,7 +1,7 @@ extern crate clap; use clap::{App, Arg, SubCommand}; use std::path::Path; -use taskwarrior_rust::{taskstorage, Replica, DB}; +use taskwarrior_rust::{taskstorage, Replica, Status, DB}; use uuid::Uuid; fn main() { @@ -10,9 +10,11 @@ fn main() { .author("Dustin J. Mitchell ") .about("Replacement for TaskWarrior") .subcommand( - SubCommand::with_name("add") - .about("adds a task") - .arg(Arg::with_name("title").help("task title").required(true)), + SubCommand::with_name("add").about("adds a task").arg( + Arg::with_name("descrpition") + .help("task descrpition") + .required(true), + ), ) .subcommand(SubCommand::with_name("list").about("lists tasks")) .get_matches(); @@ -27,9 +29,12 @@ fn main() { match matches.subcommand() { ("add", Some(matches)) => { let uuid = Uuid::new_v4(); - replica.create_task(uuid).unwrap(); replica - .update_task(uuid, "title", Some(matches.value_of("title").unwrap())) + .new_task( + uuid, + Status::Pending, + matches.value_of("descrpition").unwrap().into(), + ) .unwrap(); } ("list", _) => { diff --git a/src/lib.rs b/src/lib.rs index 8566989cc..475ea1d6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,8 @@ mod tdb2; pub use operation::Operation; pub use replica::Replica; pub use server::Server; +pub use task::Priority; +pub use task::Status; pub use task::Task; pub use taskdb::DB; diff --git a/src/replica.rs b/src/replica.rs index c2df51fc8..59a9dd89d 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -1,8 +1,10 @@ use crate::operation::Operation; +use crate::task::{Priority, Status, Task, TaskBuilder}; use crate::taskdb::DB; use crate::taskstorage::TaskMap; use chrono::Utc; use failure::Fallible; +use std::collections::HashMap; use uuid::Uuid; /// A replica represents an instance of a user's task data. @@ -15,25 +17,10 @@ impl Replica { return Replica { taskdb }; } - /// Create a new task. The task must not already exist. - pub fn create_task(&mut self, uuid: Uuid) -> Fallible<()> { - self.taskdb.apply(Operation::Create { uuid }) - } - - /// Delete a task. The task must exist. - pub fn delete_task(&mut self, uuid: Uuid) -> Fallible<()> { - self.taskdb.apply(Operation::Delete { uuid }) - } - /// Update an existing task. If the value is Some, the property is added or updated. If the /// value is None, the property is deleted. It is not an error to delete a nonexistent /// property. - pub fn update_task( - &mut self, - uuid: Uuid, - property: S1, - value: Option, - ) -> Fallible<()> + fn update_task(&mut self, uuid: Uuid, property: S1, value: Option) -> Fallible<()> where S1: Into, S2: Into, @@ -46,9 +33,14 @@ impl Replica { }) } - /// Get all tasks as an iterator of (&Uuid, &HashMap) - pub fn all_tasks<'a>(&'a mut self) -> Fallible> { - self.taskdb.all_tasks() + /// Get all tasks represented as a map keyed by UUID + pub fn all_tasks<'a>(&'a mut self) -> Fallible> { + Ok(self + .taskdb + .all_tasks()? + .iter() + .map(|(k, v)| (k.clone(), v.into())) + .collect()) } /// Get the UUIDs of all tasks @@ -57,9 +49,126 @@ impl Replica { } /// Get an existing task by its UUID - pub fn get_task(&mut self, uuid: &Uuid) -> Fallible> { - self.taskdb.get_task(&uuid) + pub fn get_task(&mut self, uuid: &Uuid) -> Fallible> { + Ok(self.taskdb.get_task(&uuid)?.map(|t| (&t).into())) } + + /// Create a new task. The task must not already exist. + pub fn new_task( + &mut self, + uuid: Uuid, + status: Status, + description: String, + ) -> Fallible { + // TODO: check that it doesn't exist + self.taskdb + .apply(Operation::Create { uuid: uuid.clone() })?; + self.update_task(uuid.clone(), "status", Some(String::from(status.as_ref())))?; + self.update_task(uuid.clone(), "description", Some(description))?; + let now = format!("{}", Utc::now().timestamp()); + self.update_task(uuid.clone(), "entry", Some(now.clone()))?; + self.update_task(uuid.clone(), "modified", Some(now))?; + Ok(TaskMut::new(self, uuid)) + } + + /// Delete a task. The task must exist. + pub fn delete_task(&mut self, uuid: Uuid) -> Fallible<()> { + // TODO: must verify task does exist + self.taskdb.apply(Operation::Delete { uuid }) + } + + /// Get an existing task by its UUID, suitable for modification + pub fn get_task_mut<'a>(&'a mut self, uuid: &Uuid) -> Fallible>> { + // the call to get_task is to ensure the task exists locally + Ok(self + .taskdb + .get_task(&uuid)? + .map(move |_| TaskMut::new(self, uuid.clone()))) + } +} + +impl From<&TaskMap> for Task { + fn from(taskmap: &TaskMap) -> Task { + let mut bldr = TaskBuilder::new(); + for (k, v) in taskmap.iter() { + bldr = bldr.set(k, v.into()); + } + bldr.finish() + } +} + +/// TaskMut allows changes to a task. It is intended for short-term use, such as changing a few +/// properties, and should not be held for long periods of wall-clock time. +pub struct TaskMut<'a> { + replica: &'a mut Replica, + uuid: Uuid, + // if true, then this TaskMut has already updated the `modified` property and need not do so + // again. + updated_modified: bool, +} + +impl<'a> TaskMut<'a> { + fn new(replica: &'a mut Replica, uuid: Uuid) -> TaskMut { + TaskMut { + replica, + uuid, + updated_modified: false, + } + } + + fn lastmod(&mut self) -> Fallible<()> { + if !self.updated_modified { + let now = format!("{}", Utc::now().timestamp()); + self.replica + .update_task(self.uuid.clone(), "modified", Some(now))?; + self.updated_modified = true; + } + Ok(()) + } + + /// Set the task's status + pub fn status(&mut self, status: Status) -> Fallible<()> { + self.lastmod()?; + self.replica.update_task( + self.uuid.clone(), + "status", + Some(String::from(status.as_ref())), + ) + } + + // TODO: description + // TODO: start + // TODO: end + // TODO: due + // TODO: until + // TODO: wait + // TODO: scheduled + // TODO: recur + // TODO: mask + // TODO: imask + // TODO: parent + + /// Set the task's project + pub fn project(&mut self, project: String) -> Fallible<()> { + self.lastmod()?; + self.replica + .update_task(self.uuid.clone(), "project", Some(project)) + } + + /// Set the task's priority + pub fn priority(&mut self, priority: Priority) -> Fallible<()> { + self.lastmod()?; + self.replica.update_task( + self.uuid.clone(), + "priority", + Some(String::from(priority.as_ref())), + ) + } + + // TODO: depends + // TODO: tags + // TODO: annotations + // TODO: udas } #[cfg(test)] @@ -69,35 +178,48 @@ mod tests { use uuid::Uuid; #[test] - fn create() { + fn new_task_and_modify() { let mut rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); - rep.create_task(uuid.clone()).unwrap(); - assert_eq!(rep.get_task(&uuid).unwrap(), Some(TaskMap::new())); + let mut tm = rep + .new_task(uuid.clone(), Status::Pending, "a task".into()) + .unwrap(); + tm.priority(Priority::L).unwrap(); + + let t = rep.get_task(&uuid).unwrap().unwrap(); + assert_eq!(t.description, String::from("a task")); + assert_eq!(t.status, Status::Pending); + assert_eq!(t.priority, Some(Priority::L)); } #[test] - fn delete() { + fn delete_task() { let mut rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); - rep.create_task(uuid.clone()).unwrap(); + rep.new_task(uuid.clone(), Status::Pending, "a task".into()) + .unwrap(); + rep.delete_task(uuid.clone()).unwrap(); assert_eq!(rep.get_task(&uuid).unwrap(), None); } #[test] - fn update() { + fn get_and_modify() { let mut rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); - rep.create_task(uuid.clone()).unwrap(); - rep.update_task(uuid.clone(), "title", Some("snarsblat")) + rep.new_task(uuid.clone(), Status::Pending, "another task".into()) .unwrap(); - let mut task = TaskMap::new(); - task.insert("title".into(), "snarsblat".into()); - assert_eq!(rep.get_task(&uuid).unwrap(), Some(task)); + + let mut tm = rep.get_task_mut(&uuid).unwrap().unwrap(); + tm.priority(Priority::L).unwrap(); + tm.project("work".into()).unwrap(); + + let t = rep.get_task(&uuid).unwrap().unwrap(); + assert_eq!(t.description, String::from("another task")); + assert_eq!(t.project, Some("work".into())); } #[test] diff --git a/src/task/task.rs b/src/task/task.rs index 29e8f4925..e6945f65a 100644 --- a/src/task/task.rs +++ b/src/task/task.rs @@ -1,6 +1,7 @@ -use std::collections::HashMap; -use uuid::Uuid; use chrono::prelude::*; +use std::collections::HashMap; +use std::convert::TryFrom; +use uuid::Uuid; pub type Timestamp = DateTime; @@ -11,6 +12,28 @@ pub enum Priority { H, } +impl TryFrom<&str> for Priority { + type Error = failure::Error; + + fn try_from(s: &str) -> Result { + match s { + "L" => Ok(Priority::L), + "M" => Ok(Priority::M), + "H" => Ok(Priority::H), + _ => Err(format_err!("invalid status {}", s)), + } + } +} + +impl AsRef for Priority { + fn as_ref(&self) -> &str { + match self { + Priority::L => "L", + Priority::M => "M", + Priority::H => "H", + } + } +} #[derive(Debug, PartialEq)] pub enum Status { Pending, @@ -20,6 +43,33 @@ pub enum Status { Waiting, } +impl TryFrom<&str> for Status { + type Error = failure::Error; + + fn try_from(s: &str) -> Result { + match s { + "pending" => Ok(Status::Pending), + "completed" => Ok(Status::Completed), + "deleted" => Ok(Status::Deleted), + "recurring" => Ok(Status::Recurring), + "waiting" => Ok(Status::Waiting), + _ => Err(format_err!("invalid status {}", s)), + } + } +} + +impl AsRef for Status { + fn as_ref(&self) -> &str { + match self { + Status::Pending => "pending", + Status::Completed => "completed", + Status::Deleted => "deleted", + Status::Recurring => "recurring", + Status::Waiting => "waiting", + } + } +} + #[derive(Debug, PartialEq)] pub struct Annotation { pub entry: Timestamp, @@ -28,11 +78,11 @@ pub struct Annotation { /// A task, the fundamental business object of this tool. /// -/// This structure is based on https://taskwarrior.org/docs/design/task.html -#[derive(Debug)] +/// This structure is based on https://taskwarrior.org/docs/design/task.html with the +/// exception that the uuid property is omitted. +#[derive(Debug, PartialEq)] pub struct Task { pub status: Status, - pub uuid: Uuid, pub entry: Timestamp, pub description: String, pub start: Option, diff --git a/src/task/taskbuilder.rs b/src/task/taskbuilder.rs index f1d03fc06..53d805cf0 100644 --- a/src/task/taskbuilder.rs +++ b/src/task/taskbuilder.rs @@ -1,14 +1,13 @@ use crate::task::{Annotation, Priority, Status, Task, Timestamp}; use chrono::prelude::*; -use failure::Fallible; use std::collections::HashMap; +use std::convert::TryFrom; use std::str; use uuid::Uuid; #[derive(Default)] pub struct TaskBuilder { status: Option, - uuid: Option, entry: Option, description: Option, start: Option, @@ -51,29 +50,6 @@ where value.parse() } -/// Parse a status into a Status enum value -fn parse_status(value: &str) -> Fallible { - match value { - "pending" => Ok(Status::Pending), - "completed" => Ok(Status::Completed), - "deleted" => Ok(Status::Deleted), - "recurring" => Ok(Status::Recurring), - "waiting" => Ok(Status::Waiting), - _ => Err(format_err!("invalid status {}", value)), - } -} - -/// Parse "L", "M", "H" into the Priority enum - -fn parse_priority(value: &str) -> Fallible { - match value { - "L" => Ok(Priority::L), - "M" => Ok(Priority::M), - "H" => Ok(Priority::H), - _ => Err(format_err!("invalid priority {}", value)), - } -} - /// Parse a UNIX timestamp into a UTC DateTime fn parse_timestamp(value: &str) -> Result::Err> { Ok(Utc.timestamp(parse_int::(value)?, 0)) @@ -94,6 +70,7 @@ impl TaskBuilder { Default::default() } + // TODO: fallible pub fn set(mut self, name: &str, value: String) -> Self { const ANNOTATION_PREFIX: &str = "annotation_"; if name.starts_with(ANNOTATION_PREFIX) { @@ -106,8 +83,7 @@ impl TaskBuilder { return self; } match name { - "status" => self.status = Some(parse_status(&value).unwrap()), - "uuid" => self.uuid = Some(Uuid::parse_str(&value).unwrap()), + "status" => self.status = Some(Status::try_from(value.as_ref()).unwrap()), "entry" => self.entry = Some(parse_timestamp(&value).unwrap()), "description" => self.description = Some(value), "start" => self.start = Some(parse_timestamp(&value).unwrap()), @@ -120,9 +96,9 @@ impl TaskBuilder { "recur" => self.recur = Some(value), "mask" => self.mask = Some(value), "imask" => self.imask = Some(parse_int::(&value).unwrap()), - "parent" => self.uuid = Some(Uuid::parse_str(&value).unwrap()), + "parent" => self.parent = Some(Uuid::parse_str(&value).unwrap()), "project" => self.project = Some(value), - "priority" => self.priority = Some(parse_priority(&value).unwrap()), + "priority" => self.priority = Some(Priority::try_from(value.as_ref()).unwrap()), "depends" => self.depends = parse_depends(&value).unwrap(), "tags" => self.tags = parse_tags(&value), _ => { @@ -135,7 +111,6 @@ impl TaskBuilder { pub fn finish(self) -> Task { Task { status: self.status.unwrap(), - uuid: self.uuid.unwrap(), description: self.description.unwrap(), entry: self.entry.unwrap(), start: self.start, From 12980da5fd2aa0190ae37b86c071d264198a10f8 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 19 Jan 2020 14:56:09 -0500 Subject: [PATCH 039/548] add working_set support to taskstorage --- src/taskstorage/inmemory.rs | 151 +++++++++++++++++++++++++ src/taskstorage/kv.rs | 217 ++++++++++++++++++++++++++++++++++++ src/taskstorage/mod.rs | 16 +++ 3 files changed, 384 insertions(+) diff --git a/src/taskstorage/inmemory.rs b/src/taskstorage/inmemory.rs index ef654b0f3..e89d31bc7 100644 --- a/src/taskstorage/inmemory.rs +++ b/src/taskstorage/inmemory.rs @@ -10,6 +10,7 @@ struct Data { tasks: HashMap, base_version: u64, operations: Vec, + working_set: Vec>, } struct Txn<'t> { @@ -104,6 +105,31 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(()) } + fn get_working_set(&mut self) -> Fallible>> { + Ok(self.data_ref().working_set.clone()) + } + + fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { + let working_set = &mut self.mut_data_ref().working_set; + working_set.push(Some(uuid)); + Ok(working_set.len() as u64) + } + + fn remove_from_working_set(&mut self, index: u64) -> Fallible<()> { + let index = index as usize; + let working_set = &mut self.mut_data_ref().working_set; + if index >= working_set.len() || working_set[index].is_none() { + return Err(format_err!("No task found with index {}", index)); + } + working_set[index] = None; + Ok(()) + } + + fn clear_working_set(&mut self) -> Fallible<()> { + self.mut_data_ref().working_set = vec![None]; + Ok(()) + } + fn commit(&mut self) -> Fallible<()> { // copy the new_data back into storage to commit the transaction if let Some(data) = self.new_data.take() { @@ -125,6 +151,7 @@ impl InMemoryStorage { tasks: HashMap::new(), base_version: 0, operations: vec![], + working_set: vec![None], }, } } @@ -138,3 +165,127 @@ impl TaskStorage for InMemoryStorage { })) } } + +#[cfg(test)] +mod test { + use super::*; + + // (note: this module is heavily used in tests so most of its functionality is well-tested + // elsewhere and not tested here) + + #[test] + fn get_working_set_empty() -> Fallible<()> { + let mut storage = InMemoryStorage::new(); + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None]); + } + + Ok(()) + } + + #[test] + fn add_to_working_set() -> Fallible<()> { + let mut storage = InMemoryStorage::new(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(uuid2.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, Some(uuid1), Some(uuid2)]); + } + + Ok(()) + } + + #[test] + fn add_and_remove_from_working_set_holes() -> Fallible<()> { + let mut storage = InMemoryStorage::new(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(uuid2.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + txn.remove_from_working_set(1)?; + txn.add_to_working_set(uuid1.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, None, Some(uuid2), Some(uuid1)]); + } + + Ok(()) + } + + #[test] + fn remove_working_set_doesnt_exist() -> Fallible<()> { + let mut storage = InMemoryStorage::new(); + let uuid1 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let res = txn.remove_from_working_set(0); + assert!(res.is_err()); + let res = txn.remove_from_working_set(2); + assert!(res.is_err()); + } + + Ok(()) + } + + #[test] + fn clear_working_set() -> Fallible<()> { + let mut storage = InMemoryStorage::new(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(uuid2.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + txn.clear_working_set()?; + txn.add_to_working_set(uuid2.clone())?; + txn.add_to_working_set(uuid1.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, Some(uuid2), Some(uuid1)]); + } + + Ok(()) + } +} diff --git a/src/taskstorage/kv.rs b/src/taskstorage/kv.rs index 20c4da301..c8a6d85d5 100644 --- a/src/taskstorage/kv.rs +++ b/src/taskstorage/kv.rs @@ -51,10 +51,12 @@ pub struct KVStorage<'t> { tasks_bucket: Bucket<'t, Key, ValueBuf>>, numbers_bucket: Bucket<'t, Integer, ValueBuf>>, operations_bucket: Bucket<'t, Integer, ValueBuf>>, + working_set_bucket: Bucket<'t, Integer, ValueBuf>>, } const BASE_VERSION: u64 = 1; const NEXT_OPERATION: u64 = 2; +const NEXT_WORKING_SET_INDEX: u64 = 3; impl<'t> KVStorage<'t> { pub fn new(directory: &Path) -> Fallible { @@ -62,6 +64,7 @@ impl<'t> KVStorage<'t> { config.bucket("tasks", None); config.bucket("numbers", None); config.bucket("operations", None); + config.bucket("working_set", None); let store = Store::new(config)?; // tasks are stored indexed by uuid @@ -75,11 +78,17 @@ impl<'t> KVStorage<'t> { let operations_bucket = store.int_bucket::>>(Some("operations"))?; + // this bucket contains operations, numbered consecutively; the NEXT_WORKING_SET_INDEX + // number gives the index of the next operation to insert + let working_set_bucket = + store.int_bucket::>>(Some("working_set"))?; + Ok(KVStorage { store, tasks_bucket, numbers_bucket, operations_bucket, + working_set_bucket, }) } } @@ -118,6 +127,9 @@ impl<'t> Txn<'t> { fn operations_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { &self.storage.operations_bucket } + fn working_set_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { + &self.storage.working_set_bucket + } } impl<'t> TaskStorageTxn for Txn<'t> { @@ -271,6 +283,90 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(()) } + fn get_working_set(&mut self) -> Fallible>> { + let working_set_bucket = self.working_set_bucket(); + let numbers_bucket = self.numbers_bucket(); + let kvtxn = self.kvtxn(); + + let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { + Ok(buf) => buf.inner()?.to_serde(), + Err(Error::NotFound) => 1, + Err(e) => return Err(e.into()), + }; + + let mut res = Vec::with_capacity(next_index as usize); + for _ in 0..next_index { + res.push(None) + } + + let curs = kvtxn.read_cursor(working_set_bucket)?; + for (i, u) in kvtxn.read_cursor(working_set_bucket)?.iter() { + let i: u64 = i.into(); + res[i as usize] = Some(u.inner()?.to_serde()); + } + Ok(res) + } + + fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { + let working_set_bucket = self.working_set_bucket(); + let numbers_bucket = self.numbers_bucket(); + let kvtxn = self.kvtxn(); + + let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { + Ok(buf) => buf.inner()?.to_serde(), + Err(Error::NotFound) => 1, + Err(e) => return Err(e.into()), + }; + + kvtxn.set( + working_set_bucket, + next_index.into(), + Msgpack::to_value_buf(uuid)?, + )?; + kvtxn.set( + numbers_bucket, + NEXT_WORKING_SET_INDEX.into(), + Msgpack::to_value_buf(next_index + 1)?, + )?; + Ok(next_index) + } + + fn remove_from_working_set(&mut self, index: u64) -> Fallible<()> { + let working_set_bucket = self.working_set_bucket(); + let numbers_bucket = self.numbers_bucket(); + let kvtxn = self.kvtxn(); + + let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { + Ok(buf) => buf.inner()?.to_serde(), + Err(Error::NotFound) => 1, + Err(e) => return Err(e.into()), + }; + if index == 0 || index >= next_index { + return Err(format_err!("No task found with index {}", index)); + } + + match kvtxn.del(working_set_bucket, index.into()) { + Err(Error::NotFound) => Err(format_err!("No task found with index {}", index)), + Err(e) => Err(e.into()), + Ok(_) => Ok(()), + } + } + + fn clear_working_set(&mut self) -> Fallible<()> { + let working_set_bucket = self.working_set_bucket(); + let numbers_bucket = self.numbers_bucket(); + let kvtxn = self.kvtxn(); + + kvtxn.clear_db(working_set_bucket)?; + kvtxn.set( + numbers_bucket, + NEXT_WORKING_SET_INDEX.into(), + Msgpack::to_value_buf(1)?, + )?; + + Ok(()) + } + fn commit(&mut self) -> Fallible<()> { if let Some(kvtxn) = self.txn.take() { kvtxn.commit()?; @@ -546,4 +642,125 @@ mod test { } Ok(()) } + + #[test] + fn get_working_set_empty() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None]); + } + + Ok(()) + } + + #[test] + fn add_to_working_set() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(uuid2.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, Some(uuid1), Some(uuid2)]); + } + + Ok(()) + } + + #[test] + fn add_and_remove_from_working_set_holes() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(uuid2.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + txn.remove_from_working_set(1)?; + txn.add_to_working_set(uuid1.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, None, Some(uuid2), Some(uuid1)]); + } + + Ok(()) + } + + #[test] + fn remove_working_set_doesnt_exist() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let res = txn.remove_from_working_set(0); + assert!(res.is_err()); + let res = txn.remove_from_working_set(2); + assert!(res.is_err()); + } + + Ok(()) + } + + #[test] + fn clear_working_set() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut storage = KVStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(uuid2.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + txn.clear_working_set()?; + txn.add_to_working_set(uuid2.clone())?; + txn.add_to_working_set(uuid1.clone())?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, Some(uuid2), Some(uuid1)]); + } + + Ok(()) + } } diff --git a/src/taskstorage/mod.rs b/src/taskstorage/mod.rs index 092707225..5c2d879bf 100644 --- a/src/taskstorage/mod.rs +++ b/src/taskstorage/mod.rs @@ -64,6 +64,20 @@ pub trait TaskStorageTxn { /// Replace the current list of operations with a new list. fn set_operations(&mut self, ops: Vec) -> Fallible<()>; + /// Get the entire working set, with each task UUID at its appropriate (1-based) index. + /// Element 0 is always None. + fn get_working_set(&mut self) -> Fallible>>; + + /// Add a task to the working set and return its (one-based) index. This index will be one greater + /// than the highest used index. + fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible; + + /// Remove a task from the working set. Other tasks' indexes are not affected. + fn remove_from_working_set(&mut self, index: u64) -> Fallible<()>; + + /// Clear all tasks from the working set in preparation for a garbage-collection operation. + fn clear_working_set(&mut self) -> Fallible<()>; + /// Commit any changes made in the transaction. It is an error to call this more than /// once. fn commit(&mut self) -> Fallible<()>; @@ -78,6 +92,8 @@ pub trait TaskStorageTxn { /// - tasks: a set of tasks indexed by uuid /// - base_version: the number of the last version sync'd from the server /// - operations: all operations performed since base_version +/// - working_set: a mapping from integer -> uuid, used to keep stable small-integer indexes +/// into the tasks. The replica maintains this list. It is not covered by operations. /// /// The `operations` are already reflected in `tasks`, so the following invariant holds: /// > Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical From 61b2de132ba00c1a4aae296a570618e9b01eb5ec Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 19 Jan 2020 15:55:15 -0500 Subject: [PATCH 040/548] support rebuilding the working set --- TODO.txt | 7 +-- src/bin/task.rs | 17 +++++++- src/replica.rs | 24 ++++++++++ src/taskdb.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index 23ad27b6f..0f210f2ce 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,12 +1,13 @@ -* assign types to properties in Replica +* move rebuild_working_set logic to replica.rs, also include states != + completed / deleted +* [WIP] assign types to properties in Replica - db / operation model is just k/v, but formatted names can be used for structure: - dependencies: `dependency. = ""` - annotations: `annotation. = "annotation"` - tags: `tags. = ""` -* add HTTP API -* add pending-task indexing to Replica * abstract server into trait +* add HTTP API * fix TODO items in replica.rs * implement snapshot requests * implement backups diff --git a/src/bin/task.rs b/src/bin/task.rs index 13a3a749b..8149374d5 100644 --- a/src/bin/task.rs +++ b/src/bin/task.rs @@ -17,6 +17,8 @@ fn main() { ), ) .subcommand(SubCommand::with_name("list").about("lists tasks")) + .subcommand(SubCommand::with_name("pending").about("lists pending tasks")) + .subcommand(SubCommand::with_name("gc").about("run garbage collection")) .get_matches(); let mut replica = Replica::new( @@ -38,10 +40,21 @@ fn main() { .unwrap(); } ("list", _) => { - for task in replica.all_tasks().unwrap() { - println!("{:?}", task); + for (uuid, task) in replica.all_tasks().unwrap() { + println!("{} - {:?}", uuid, task); } } + ("pending", _) => { + let working_set = replica.working_set().unwrap(); + for i in 1..working_set.len() { + if let Some((ref uuid, ref task)) = working_set[i] { + println!("{}: {} - {:?}", i, uuid, task); + } + } + } + ("gc", _) => { + replica.gc().unwrap(); + } ("", None) => { unreachable!(); } diff --git a/src/replica.rs b/src/replica.rs index 59a9dd89d..abfe2ab7e 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -48,6 +48,23 @@ impl Replica { self.taskdb.all_task_uuids() } + /// Get the "working set" for this replica -- the set of pending tasks, as indexed by small + /// integers + pub fn working_set(&mut self) -> Fallible>> { + let working_set = self.taskdb.working_set()?; + let mut res = Vec::with_capacity(working_set.len()); + for i in 0..working_set.len() { + res.push(match working_set[i] { + Some(u) => match self.taskdb.get_task(&u)? { + Some(task) => Some((u, (&task).into())), + None => None, + }, + None => None, + }) + } + Ok(res) + } + /// Get an existing task by its UUID pub fn get_task(&mut self, uuid: &Uuid) -> Fallible> { Ok(self.taskdb.get_task(&uuid)?.map(|t| (&t).into())) @@ -85,6 +102,13 @@ impl Replica { .get_task(&uuid)? .map(move |_| TaskMut::new(self, uuid.clone()))) } + + /// Perform "garbage collection" on this replica. In particular, this renumbers the working + /// set. + pub fn gc(&mut self) -> Fallible<()> { + self.taskdb.rebuild_working_set()?; + Ok(()) + } } impl From<&TaskMap> for Task { diff --git a/src/taskdb.rs b/src/taskdb.rs index 3e9fbe0c1..2613cc39e 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -4,6 +4,7 @@ use crate::server::{Server, VersionAdd}; use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn}; use failure::Fallible; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; use std::str; use uuid::Uuid; @@ -64,6 +65,7 @@ impl DB { // update if this task exists, otherwise ignore if let Some(task) = txn.get_task(uuid)? { let mut task = task.clone(); + // TODO: update working_set if this is changing state to or from pending match value { Some(ref val) => task.insert(property.to_string(), val.clone()), None => task.remove(property), @@ -90,12 +92,61 @@ impl DB { txn.all_task_uuids() } + /// Get the working set + pub fn working_set<'a>(&'a mut self) -> Fallible>> { + let mut txn = self.storage.txn()?; + txn.get_working_set() + } + /// Get a single task, by uuid. pub fn get_task(&mut self, uuid: &Uuid) -> Fallible> { let mut txn = self.storage.txn()?; txn.get_task(uuid) } + /// Rebuild the working set. This renumbers the pending tasks to eliminate gaps, and also + /// finds any tasks whose statuses changed without being noticed. + pub fn rebuild_working_set(&mut self) -> Fallible<()> { + // TODO: this logic belongs in Replica + // TODO: it's every status but Completed and Deleted, I think? + let mut txn = self.storage.txn()?; + + let mut new_ws = vec![]; + let mut seen = HashSet::new(); + let pending = String::from("pending"); + + // The goal here is for existing working-set items to be "compressed' down to index + // 1, so we begin by scanning the current working set and inserting any still-pending + // tasks into the new list + for elt in txn.get_working_set()? { + if let Some(uuid) = elt { + if let Some(task) = txn.get_task(&uuid)? { + if task.get("status") == Some(&pending) { + new_ws.push(uuid.clone()); + seen.insert(uuid); + } + } + } + } + + // Now go hunting for tasks that are pending and are not already in this list + for (uuid, task) in txn.all_tasks()? { + if !seen.contains(&uuid) { + if task.get("status") == Some(&pending) { + new_ws.push(uuid.clone()); + } + } + } + + txn.clear_working_set()?; + for uuid in new_ws.drain(0..new_ws.len()) { + txn.add_to_working_set(uuid)?; + } + + txn.commit()?; + Ok(()) + } + /// Sync to the given server, pulling remote changes and pushing local changes. pub fn sync(&mut self, username: &str, server: &mut Server) -> Fallible<()> { let mut txn = self.storage.txn()?; @@ -366,4 +417,67 @@ mod tests { assert_eq!(db.sorted_tasks(), vec![]); assert_eq!(db.operations(), vec![]); } + + #[test] + fn rebuild_working_set() -> Fallible<()> { + let mut db = DB::new_inmemory(); + let uuids = vec![ + Uuid::new_v4(), // 0: pending, not already in working set + Uuid::new_v4(), // 1: pending, already in working set + Uuid::new_v4(), // 2: not pending, not already in working set + Uuid::new_v4(), // 3: not pending, already in working set + Uuid::new_v4(), // 4: pending, already in working set + ]; + + // add everything to the DB + for uuid in &uuids { + db.apply(Operation::Create { uuid: uuid.clone() })?; + } + for i in &[0usize, 1, 4] { + db.apply(Operation::Update { + uuid: uuids[*i].clone(), + property: String::from("status"), + value: Some("pending".into()), + timestamp: Utc::now(), + })?; + } + + // set the existing working_set as we want it + { + let mut txn = db.storage.txn()?; + txn.clear_working_set()?; + + for i in &[1usize, 3, 4] { + txn.add_to_working_set(uuids[*i])?; + } + + txn.commit()?; + } + + assert_eq!( + db.working_set()?, + vec![ + None, + Some(uuids[1].clone()), + Some(uuids[3].clone()), + Some(uuids[4].clone()) + ] + ); + + db.rebuild_working_set()?; + + // uuids[1] and uuids[4] are already in the working set, so are compressed + // to the top, and then uuids[0] is added. + assert_eq!( + db.working_set()?, + vec![ + None, + Some(uuids[1].clone()), + Some(uuids[4].clone()), + Some(uuids[0].clone()) + ] + ); + + Ok(()) + } } From cfdb2668000edbab6459bb351acd152280d2fb92 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 2 Feb 2020 15:11:14 -0500 Subject: [PATCH 041/548] add some cli parsing stuff --- src/cli/lexer.rs | 3098 +++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 1 + src/lib.rs | 2 + src/tdb2/ff4.rs | 2 +- src/tdb2/mod.rs | 1 - src/util/datetime.rs | 39 + src/util/duration.rs | 44 + src/util/mod.rs | 3 + src/{tdb2 => util}/pig.rs | 2 +- 9 files changed, 3189 insertions(+), 3 deletions(-) create mode 100644 src/cli/lexer.rs create mode 100644 src/cli/mod.rs create mode 100644 src/util/datetime.rs create mode 100644 src/util/duration.rs create mode 100644 src/util/mod.rs rename src/{tdb2 => util}/pig.rs (99%) diff --git a/src/cli/lexer.rs b/src/cli/lexer.rs new file mode 100644 index 000000000..9cc9bb237 --- /dev/null +++ b/src/cli/lexer.rs @@ -0,0 +1,3098 @@ +use crate::util::datetime::DateTime; +use crate::util::duration::Duration; +use std::convert::TryFrom; + +// based on src/Lexer.{h,cpp} in the Taskwarrior code + +const UUID_PATTERN: &[u8] = b"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; +const UUID_MIN_LENGTH: usize = 8; +const MINIMUM_MATCH_LEN: usize = 3; +const DATE_SUBELEMENTS: &[&str] = &[ + "year", "month", "day", "week", "weekday", "julian", "hour", "minute", "second", +]; + +#[derive(PartialEq, Debug, Clone, Copy)] +enum Type { + Uuid, + Number, + Hex, + String, + URL, + Pair, + Set, + Separator, + Tag, + Path, + Substitution, + Pattern, + Op, + DOM, + Identifier, + Word, + Date, + Duration, +} + +struct Lexer { + text: String, + cursor: usize, + eos: usize, + attributes: Vec, +} + +// TaskWarrior uses some non-standard character definitions, so they are repeated verbatim here, +// rather than defaulting to the unicode functions available on the char type. + +/// Returns true if this character is whitespace, as defined in TaskWarrior's libshared. +fn unicode_whitespace(c: char) -> bool { + unicode_horizontal_whitespace(c) || unicode_vertical_whitespace(c) +} + +/// Returns true if this character is horizontal whitespace, as defined in TaskWarrior's libshared. +fn unicode_horizontal_whitespace(c: char) -> bool { + let c: u32 = c.into(); + return c == 0x0020 || // space Common Separator, space + c == 0x0009 || // Common Other, control HT, Horizontal Tab + c == 0x00A0 || // no-break space Common Separator, space + c == 0x1680 || // ogham space mark Ogham Separator, space + c == 0x180E || // mongolian vowel separator Mongolian Separator, space + c == 0x2000 || // en quad Common Separator, space + c == 0x2001 || // em quad Common Separator, space + c == 0x2002 || // en space Common Separator, space + c == 0x2003 || // em space Common Separator, space + c == 0x2004 || // three-per-em space Common Separator, space + c == 0x2005 || // four-per-em space Common Separator, space + c == 0x2006 || // six-per-em space Common Separator, space + c == 0x2007 || // figure space Common Separator, space + c == 0x2008 || // punctuation space Common Separator, space + c == 0x2009 || // thin space Common Separator, space + c == 0x200A || // hair space Common Separator, space + c == 0x200B || // zero width space + c == 0x200C || // zero width non-joiner + c == 0x200D || // zero width joiner + c == 0x202F || // narrow no-break space Common Separator, space + c == 0x205F || // medium mathematical space Common Separator, space + c == 0x2060 || // word joiner + c == 0x3000; // ideographic space Common Separator, space +} + +/// Returns true if this character is vertical whitespace, as defined in TaskWarrior's libshared. +fn unicode_vertical_whitespace(c: char) -> bool { + let c: u32 = c.into(); + return c == 0x000A || // Common Other, control LF, Line feed + c == 0x000B || // Common Other, control VT, Vertical Tab + c == 0x000C || // Common Other, control FF, Form feed + c == 0x000D || // Common Other, control CR, Carriage return + c == 0x0085 || // Common Other, control NEL, Next line + c == 0x2028 || // line separator Common Separator, line + c == 0x2029; // paragraph separator Common Separator, paragraph +} + +/// Returns true if the given character is an ascii digit +fn unicode_latin_digit(c: char) -> bool { + c.is_ascii_digit() +} + +/// Returns true if the given character is an ascii letter +fn unicode_latin_alpha(c: char) -> bool { + c.is_ascii_alphabetic() +} + +/// Replicates the C function of the same name, which only recognizes ASCII printable +fn isprint(c: char) -> bool { + c.is_ascii_graphic() +} + +/// Returns true if the given character is punctuation. +fn is_punctuation(c: char) -> bool { + isprint(c) + && c != ' ' + && c != '@' + && c != '#' + && c != '$' + && c != '_' + && !unicode_latin_digit(c) + && !unicode_latin_alpha(c) +} + +/// Returns true if this character is an operator +fn is_single_char_operator(c: char) -> bool { + match c { + '+' | '-' | '*' | '/' | '(' | ')' | '<' | '>' | '^' | '!' | '%' | '=' | '~' => true, + _ => false, + } +} + +/// Returns true if this character can start an identifier +fn is_identifier_start(c: char) -> bool { + !unicode_whitespace(c) + && !unicode_latin_digit(c) + && !is_single_char_operator(c) + && !is_punctuation(c) +} + +/// Returns true if this character can be in the middle of an identifier +fn is_identifier_next(c: char) -> bool { + c != ':' && c != '=' && !unicode_whitespace(c) && !is_single_char_operator(c) +} + +/// Returns true if the sequence `` represents a token boundary. +fn is_boundary(left: char, right: char) -> bool { + right == '\0' + || (unicode_latin_alpha(left) != unicode_latin_alpha(right)) + || (unicode_latin_digit(left) != unicode_latin_digit(right)) + || (unicode_whitespace(left) != unicode_whitespace(right)) + || is_punctuation(left) + || is_punctuation(right) +} + +/// Returns true if the sequence `` represents a hard token boundary. +fn is_hard_boundary(left: char, right: char) -> bool { + right == '\0' || left == '(' || left == ')' || right == '(' || right == ')' +} + +/// Returns true if the given string must have been shell-quoted +fn was_quoted(s: &str) -> bool { + s.contains(&[' ', '\t', '(', ')', '<', '>', '&', '~'][..]) +} + +fn is_unicode_hex_digit(c: char) -> bool { + match c { + '0'..='9' | 'a'..='f' | 'A'..='F' => true, + _ => false, + } +} + +fn hex_to_char(hex: &str) -> Option { + let mut num = 0u32; + for c in hex.chars() { + num <<= 4; + num += match c { + '0'..='9' => c as u32 - '0' as u32, + 'a'..='f' => 10 + (c as u32 - 'a' as u32), + 'A'..='F' => 10 + (c as u32 - 'A' as u32), + _ => return None, + } + } + + if let Ok(c) = char::try_from(num) { + Some(c) + } else { + None + } +} + +/// Strips matching quote symbols from the beginning and end of the given string +/// (removing all quotes if given a single quote `'`) +fn dequote<'a, 'b>(s: &'a str, quotes: &'b str) -> &'a str { + // note that this returns a new ref to the same string, rather + // than modifying its argument as the C++ version does. + if let Some(first_char) = s.chars().next() { + if let Some(last_char) = s.chars().rev().next() { + if first_char == last_char && quotes.contains(first_char) { + let quote_len = first_char.len_utf8(); + if s.len() > 2 * quote_len { + return &s[quote_len..s.len() - quote_len]; + } else { + return ""; + } + } + } + } + s +} + +fn read_word_quoted(text: &str, quotes: &str, cursor: usize) -> Option<(String, usize)> { + let mut pos = cursor; + let mut res = String::new(); + let mut skipchars = 0; + + let mut chars = text.get(cursor..)?.chars(); + let quote = chars.next(); + if quote.is_none() { + return None; + } + let quote = quote.unwrap(); + if !quotes.contains(quote) { + return None; + } + + res.push(quote); + pos += quote.len_utf8(); + + for c in chars { + if skipchars > 0 { + skipchars -= 1; + pos += c.len_utf8(); + continue; + } + if c == quote { + res.push(c); + pos += quote.len_utf8(); + return Some((res, pos)); + } + + if c == 'U' { + if let Some('+') = text.get(pos + 1..).unwrap().chars().next() { + if let Some(hex) = text.get(pos + 2..pos + 6) { + if let Some(c) = hex_to_char(hex) { + res.push(c); + skipchars += 5; + } else { + res.push('U'); + } + } else { + res.push('U'); + } + } else { + res.push('U'); + } + } else if c == '\\' { + match text.get(pos + 1..).unwrap().chars().next() { + None => res.push(c), + Some('b') => res.push('\x08'), + Some('f') => res.push('\x0c'), + Some('n') => res.push('\x0a'), + Some('r') => res.push('\x0d'), + Some('t') => res.push('\x09'), + Some('v') => res.push('\x0b'), + Some('u') => { + if let Some(hex) = text.get(pos + 2..pos + 6) { + if let Some(c) = hex_to_char(hex) { + res.push(c); + skipchars += 4; + } else { + res.push('u') + } + } else { + res.push('u') + } + } + Some(c @ _) => res.push(c), + } + skipchars += 1; + } else { + res.push(c); + } + + pos += c.len_utf8(); + } + + None +} + +fn read_word_unquoted(text: &str, cursor: usize) -> Option<(String, usize)> { + let mut pos = cursor; + let mut res = String::new(); + let mut prev = None; + let mut skipchars = 0; + + for c in text.get(cursor..)?.chars() { + if skipchars > 0 { + skipchars -= 1; + pos += c.len_utf8(); + prev = Some(c); + continue; + } + if unicode_whitespace(c) { + break; + } + if let Some(p) = prev { + if is_hard_boundary(p, c) { + break; + } + } + + if c == 'U' { + if let Some('+') = text.get(pos + 1..).unwrap().chars().next() { + if let Some(hex) = text.get(pos + 2..pos + 6) { + if let Some(c) = hex_to_char(hex) { + res.push(c); + skipchars += 5; + } else { + res.push('U'); + } + } else { + res.push('U'); + } + } else { + res.push('U'); + } + } else if c == '\\' { + match text.get(pos + 1..).unwrap().chars().next() { + None => res.push(c), + Some('b') => res.push('\x08'), + Some('f') => res.push('\x0c'), + Some('n') => res.push('\x0a'), + Some('r') => res.push('\x0d'), + Some('t') => res.push('\x09'), + Some('v') => res.push('\x0b'), + Some('u') => { + if let Some(hex) = text.get(pos + 2..pos + 6) { + if let Some(c) = hex_to_char(hex) { + res.push(c); + skipchars += 4; + } else { + res.push('u') + } + } else { + res.push('u') + } + } + Some(c @ _) => res.push(c), + } + skipchars += 1; + } else { + res.push(c); + } + + pos += c.len_utf8(); + prev = Some(c); + } + + if pos != cursor { + Some((res, pos)) + } else { + None + } +} + +fn common_length(s1: &str, s2: &str) -> usize { + s1.chars() + .zip(s2.chars()) + .take_while(|(c1, c2)| c1 == c2) + .collect::>() + .len() +} + +#[derive(Debug, PartialEq)] +pub struct DecomposedPair { + name: String, + modifier: String, + separator: String, + value: String, +} + +impl Lexer { + pub fn new>(text: S) -> Lexer { + let text = text.into(); + let eos = text.len(); + Lexer { + text, + cursor: 0, + eos, + attributes: vec![], + } + } + + pub fn add_attribute>(&mut self, attribute: S) { + self.attributes.push(attribute.into()); + } + + /// This static method tokenizes the input, but discards the type information. + pub fn split>(text: S) -> Vec { + Lexer::new(text).into_iter().map(|(tx, ty)| tx).collect() + } + + pub fn token(&mut self) -> Option<(String, Type)> { + // Eat whitespace + while let Some(c) = self.text[self.cursor..].chars().next() { + if unicode_whitespace(c) { + self.cursor += c.len_utf8(); + continue; + } + break; + } + + if self.cursor == self.eos { + return None; + } + + // The sequence is specific, and must follow these rules: + // - date < duration < uuid < identifier + // - dom < uuid + // - uuid < hex < number + // - url < pair < identifier + // - hex < number + // - separator < tag < operator + // - path < substitution < pattern + // - set < number + // - word last + if let Some(r) = self.is_string("\"'") { + return Some(r); + } + if let Some(r) = self.is_date() { + return Some(r); + } + if let Some(r) = self.is_duration() { + return Some(r); + } + if let Some(r) = self.is_url() { + return Some(r); + } + if let Some(r) = self.is_pair() { + return Some(r); + } + if let Some(r) = self.is_uuid(true) { + return Some(r); + } + if let Some(r) = self.is_set() { + return Some(r); + } + if let Some(r) = self.is_dom() { + return Some(r); + } + if let Some(r) = self.is_hexnumber() { + return Some(r); + } + if let Some(r) = self.is_number() { + return Some(r); + } + if let Some(r) = self.is_separator() { + return Some(r); + } + if let Some(r) = self.is_tag() { + return Some(r); + } + if let Some(r) = self.is_path() { + return Some(r); + } + if let Some(r) = self.is_substitution() { + return Some(r); + } + if let Some(r) = self.is_pattern() { + return Some(r); + } + if let Some(r) = self.is_operator() { + return Some(r); + } + if let Some(r) = self.is_identifier() { + return Some(r); + } + if let Some(r) = self.is_word() { + return Some(r); + } + None + } + + pub fn decompose_pair(text: &str) -> Option { + let npos = usize::max_value(); + // npos + let dot = text.find(".").unwrap_or(npos); + // npos + let sep_defer = text.find("::").unwrap_or(npos); + // npos + let sep_eval = text.find(":=").unwrap_or(npos); + // 4 + let sep_colon = text.find(":").unwrap_or(npos); + // npos + let sep_equal = text.find("=").unwrap_or(npos); + + let (sep, sep_end) = if sep_defer != npos + && sep_defer <= sep_eval + && sep_defer <= sep_colon + && sep_defer <= sep_equal + { + (sep_defer, sep_defer + 2) + } else if sep_eval != npos + && sep_eval <= sep_defer + && sep_eval <= sep_colon + && sep_eval <= sep_equal + { + (sep_eval, sep_eval + 2) + } else if sep_colon != npos + && sep_colon <= sep_defer + && sep_colon <= sep_eval + && sep_colon <= sep_equal + { + (sep_colon, sep_colon + 1) + } else if sep_equal != npos + && sep_equal <= sep_defer + && sep_equal <= sep_eval + && sep_equal <= sep_colon + { + (sep_equal, sep_equal + 1) + } else { + return None; + }; + + let (name, modifier) = if dot != npos && dot < sep { + ( + text.get(0..dot).unwrap().into(), + text.get(dot + 1..sep).unwrap().into(), + ) + } else { + (text.get(0..sep).unwrap().into(), "".into()) + }; + + let separator = text.get(sep..sep_end).unwrap().into(); + let value = text.get(sep_end..).unwrap().into(); + + Some(DecomposedPair { + name, + modifier, + separator, + value, + }) + } + + // recognizers for the `token` method + + fn is_string(&mut self, quotes: &str) -> Option<(String, Type)> { + if let Some((s, pos)) = read_word_quoted(&self.text, quotes, self.cursor) { + self.cursor = pos; + return Some((s, Type::String)); + } + None + } + + fn is_date(&mut self) -> Option<(String, Type)> { + let (_, read) = DateTime::parse(&self.text[self.cursor..], "")?; + let token = self.text[self.cursor..self.cursor + read].into(); + self.cursor += read; + Some((token, Type::Date)) + } + + fn is_duration(&mut self) -> Option<(String, Type)> { + let marker = self.cursor; + + if self.is_operator().is_some() { + self.cursor = marker; + return None; + } + + let (_, read) = Duration::parse(&self.text[self.cursor..], "")?; + let token = self.text[self.cursor..self.cursor + read].into(); + self.cursor += read; + Some((token, Type::Duration)) + } + + fn is_url(&mut self) -> Option<(String, Type)> { + let remainder = &self.text[self.cursor..]; + if remainder.starts_with("https://") || remainder.starts_with("http://") { + if let Some(i) = remainder.find(unicode_whitespace) { + let token = &remainder[..i]; + self.cursor += i; + return Some((token.into(), Type::URL)); + } else { + self.cursor = self.eos; + return Some((remainder.into(), Type::URL)); + } + } + None + } + + fn is_pair(&mut self) -> Option<(String, Type)> { + let marker = self.cursor; + if self.is_identifier().is_some() { + let separator = &self.text[self.cursor..]; + if separator.starts_with("::") || separator.starts_with(":=") { + self.cursor += 2; + } else if separator.starts_with(":") || separator.starts_with("=") { + self.cursor += 1; + } else { + self.cursor = marker; + return None; + } + + // String, word, or nothing are all valid + let marker2 = self.cursor; + if let Some((word, end)) = read_word_quoted(&self.text[..], "'\"", self.cursor) { + self.cursor = end; + return Some(( + format!("{}{}", &self.text[marker..marker2], word), + Type::Pair, + )); + } + if let Some((word, end)) = read_word_unquoted(&self.text[..], self.cursor) { + self.cursor = end; + return Some(( + format!("{}{}", &self.text[marker..marker2], word), + Type::Pair, + )); + } + if self.cursor == self.eos + || unicode_whitespace(self.text[self.cursor..].chars().next().unwrap()) + { + return Some((self.text[marker..self.cursor].into(), Type::Pair)); + } + } + self.cursor = marker; + None + } + + fn is_uuid(&mut self, end_boundary: bool) -> Option<(String, Type)> { + let mut i = 0; + for c in self.text[self.cursor..].chars() { + if UUID_PATTERN[i] == b'x' { + if !is_unicode_hex_digit(c) { + break; + } + } else { + if c != '-' { + break; + } + } + i += 1; + if i >= UUID_PATTERN.len() { + break; + } + } + + if i < UUID_MIN_LENGTH { + return None; + } + + if end_boundary { + let c = self.text[self.cursor + i..].chars().next(); + if let Some(c) = c { + if !unicode_whitespace(c) && !is_single_char_operator(c) { + return None; + } + } + } + + let token = self.text[self.cursor..self.cursor + i].into(); + self.cursor += i; + Some((token, Type::Uuid)) + } + + fn is_set(&mut self) -> Option<(String, Type)> { + let marker = self.cursor; + let mut count = 0; + loop { + if self.is_integer().is_some() { + count += 1; + if self.is_literal("-", false, false) { + if self.is_integer().is_some() { + count += 1; + } else { + self.cursor = marker; + return None; + } + } + } else { + self.cursor = marker; + return None; + } + if !self.is_literal(",", false, false) { + break; + } + } + + if count <= 1 { + self.cursor = marker; + return None; + } + + // -1 is OK here since integers are ASCII + let last_char = self.text[self.cursor - 1..].chars().next().unwrap(); + + // look ahead a bit + match self.text[self.cursor..].chars().next() { + Some(c) if !unicode_whitespace(c) && !is_hard_boundary(last_char, c) => { + self.cursor = marker; + return None; + } + _ => (), + } + + Some((self.text[marker..self.cursor].into(), Type::Set)) + } + + fn is_dom(&mut self) -> Option<(String, Type)> { + let marker = self.cursor; + + // rc. ... + if self.is_literal("rc.", false, false) && self.is_word().is_some() { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } else { + self.cursor = marker; + } + + // Literals + if self.is_one_of( + &vec![ + "tw.syncneeded", + "tw.program", + "tw.args", + "tw.width", + "tw.height", + "tw.version", + "context.program", + "context.args", + "context.width", + "context.height", + "system.version", + "system.os", + ], + false, + true, + ) { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } + + // Optional: + // . + // . + if self.is_uuid(false).is_some() || self.is_integer().is_some() { + if !self.is_literal(".", false, false) { + self.cursor = marker; + return None; + } + } + + // Any failure after this line should rollback to the checkpoint. + let checkpoint = self.cursor; + + // [prefix]tags. + if self.is_literal("tags", false, false) + && self.is_literal(".", false, false) + && self.is_word().is_some() + { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } else { + self.cursor = checkpoint; + } + + // [prefix]attribute (bounded) + // (have to clone here to avoid double-borrowing self + let attributes = self.attributes.clone(); + if self.is_one_of(&attributes, false, true) { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } + + // [prefix]attribute. (unbounded) + if self.is_one_of(&attributes, false, false) { + if self.is_literal(".", false, false) { + let attribute = &self.text[checkpoint..self.cursor - 1]; + // if attribute type is 'date', then it has sub-elements. + if attribute == "date" && self.is_one_of(&DATE_SUBELEMENTS, false, true) { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } + self.cursor = checkpoint; + } + // Lookahead: ! + else if !self.text[marker..] + .chars() + .next() + .map_or(false, |c| unicode_latin_alpha(c)) + { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } + self.cursor = checkpoint; + } + + // [prefix]annotations. + if self.is_literal("annotations", true, false) && self.is_literal(".", false, false) { + if self.is_literal("count", false, false) { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } + + if self.is_integer().is_some() { + if self.is_literal(".", false, false) { + if self.is_literal("description", false, true) { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } else if self.is_literal("entry", false, true) { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } else if self.is_literal("entry", false, false) + && self.is_literal(".", false, false) + && self.is_one_of(&DATE_SUBELEMENTS, false, true) + { + return Some((self.text[marker..self.cursor].into(), Type::DOM)); + } + } + } else { + self.cursor = checkpoint; + } + } + + self.cursor = marker; + None + } + + fn is_hexnumber(&mut self) -> Option<(String, Type)> { + let remainder = &self.text[self.cursor..]; + + if !remainder.starts_with("0x") { + return None; + } + let mut end = 2; + for (i, c) in remainder[2..].char_indices() { + if is_unicode_hex_digit(c) { + end = 2 + i + c.len_utf8(); + } else { + break; + } + } + if end > 2 { + self.cursor += end; + Some((remainder[..end].into(), Type::Hex)) + } else { + None + } + } + + fn is_number(&mut self) -> Option<(String, Type)> { + let remainder = &self.text[self.cursor..]; + let mut chars = remainder.char_indices().peekable(); + let mut marker = 0; + + // A hand-rolled regexp. States are as follows: + // \d \d* (. \d \d*)? ([eE] [+-]? \d \d* (. \d \d*)?)? + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + let mut state = 0; + + loop { + let c = match chars.peek() { + Some((i, c)) => { + marker = *i; + Some(*c) + } + None => None, + }; + match (state, c) { + (0, Some(c)) if unicode_latin_digit(c) => state = 1, + + (1, Some(c)) if unicode_latin_digit(c) => state = 2, + (1, Some(c)) if c == '.' => state = 3, + (1, Some(c)) if c == 'e' || c == 'E' => state = 6, + (1, _) => break, + + (2, Some(c)) if unicode_latin_digit(c) => state = 2, + (2, Some(c)) if c == '.' => state = 3, + (2, Some(c)) if c == 'e' || c == 'E' => state = 6, + (2, _) => break, + + (3, Some(c)) if unicode_latin_digit(c) => state = 4, + (3, Some(c)) if c == 'e' || c == 'E' => state = 6, + (3, _) => break, + + (4, Some(c)) if unicode_latin_digit(c) => state = 5, + (4, Some(c)) if c == 'e' || c == 'E' => state = 6, + (4, _) => break, + + (5, Some(c)) if unicode_latin_digit(c) => state = 5, + (5, Some(c)) if c == 'e' || c == 'E' => state = 6, + (5, _) => break, + + (6, Some(c)) if unicode_latin_digit(c) => state = 8, + (6, Some(c)) if c == '-' || c == '+' => state = 7, + (6, _) => break, + + (7, Some(c)) if unicode_latin_digit(c) => state = 8, + (7, _) => break, + + (8, Some(c)) if unicode_latin_digit(c) => state = 9, + (8, Some(c)) if c == '.' => state = 10, + (8, _) => break, + + (9, Some(c)) if unicode_latin_digit(c) => state = 9, + (9, Some(c)) if c == '.' => state = 10, + (9, _) => break, + + (10, Some(c)) if unicode_latin_digit(c) => state = 11, + (10, _) => break, + + (11, Some(c)) if unicode_latin_digit(c) => state = 11, + (11, _) => break, + + _ => return None, + }; + if let Some((i, c)) = chars.next() { + marker = i + c.len_utf8(); + } + } + // lookahead + if let Some((_, c)) = chars.peek() { + if !unicode_whitespace(*c) && !is_single_char_operator(*c) { + return None; + } + } + self.cursor += marker; + Some((remainder[..marker].into(), Type::Number)) + } + + fn is_separator(&mut self) -> Option<(String, Type)> { + let next_chars = self + .text + .get(self.cursor..self.cursor + 2)? + .chars() + .collect::>(); + if &next_chars[..] == &['-', '-'] { + self.cursor += 2; + return Some(("--".into(), Type::Separator)); + } + None + } + + fn is_tag(&mut self) -> Option<(String, Type)> { + let mut marker = self.cursor; + + // Lookbehind: Assert ^ or preceded by whitespace, (, or ). + if marker > 0 { + // if the previous byte is not a valid character, then it's + // not ( or ) + if let Some(lookbehind) = self.text.get(self.cursor - 1..) { + if let Some(c) = lookbehind.chars().next() { + if !unicode_whitespace(c) && c != '(' && c != ')' { + return None; + } + } + } else { + return None; + } + } + + let mut chars = self.text[marker..].chars(); + if let Some(c) = chars.next() { + if c == '+' || c == '-' { + marker += c.len_utf8(); + if let Some(c) = chars.next() { + if is_identifier_start(c) { + marker += c.len_utf8(); + while let Some(c) = chars.next() { + if !is_identifier_next(c) { + break; + } + marker += c.len_utf8(); + } + let token = self.text[self.cursor..marker].into(); + self.cursor = marker; + return Some((token, Type::Tag)); + } + } + } + } + + None + } + + fn is_path(&mut self) -> Option<(String, Type)> { + let mut marker = self.cursor; + let mut slash_count = 0; + let mut chars = self.text[self.cursor..].chars().peekable(); + + loop { + if let Some('/') = chars.next() { + marker += 1; + slash_count += 1; + } else { + break; + } + + if let Some(c) = chars.next() { + if !unicode_whitespace(c) && c != '/' { + marker += 1; + while let Some(c) = chars.peek() { + if !unicode_whitespace(*c) && *c != '/' { + marker += 1; + chars.next(); + } else { + break; + } + } + } else { + break; + } + } else { + break; + } + } + + if marker > self.cursor && slash_count > 3 { + let token = self.text[self.cursor..marker].into(); + self.cursor = marker; + return Some((token, Type::Path)); + } + + None + } + + fn is_substitution(&mut self) -> Option<(String, Type)> { + let marker = self.cursor; + + if let Some((_, end)) = read_word_quoted(&self.text, "/", self.cursor) { + // end-1 to step back over the middle `/` + if let Some((_, end)) = read_word_quoted(&self.text, "/", end - 1) { + let mut remainder = self.text[end..].chars(); + return match remainder.next() { + None => { + self.cursor = end; + Some((self.text[marker..self.cursor].into(), Type::Substitution)) + } + Some('g') => match remainder.next() { + None => { + self.cursor = end + 1; + Some((self.text[marker..self.cursor].into(), Type::Substitution)) + } + Some(c) if unicode_whitespace(c) => { + self.cursor = end + 1; + Some((self.text[marker..self.cursor].into(), Type::Substitution)) + } + _ => None, + }, + Some(c) if unicode_whitespace(c) => { + self.cursor = end; + Some((self.text[marker..self.cursor].into(), Type::Substitution)) + } + _ => None, + }; + } + } + + None + } + + fn is_pattern(&mut self) -> Option<(String, Type)> { + let marker = self.cursor; + if let Some((_, end)) = read_word_quoted(&self.text, "/", self.cursor) { + if end == self.eos || unicode_whitespace(self.text[end..].chars().next().unwrap()) { + self.cursor = end; + return Some((self.text[marker..self.cursor].into(), Type::Pattern)); + } + } + None + } + + fn is_operator(&mut self) -> Option<(String, Type)> { + let remainder = &self.text[self.cursor..]; + + // operators that do not require a boundary afterward + for strop in &[ + // custom stuff + "_hastag_", "_notag_", "_neg_", "_pos_", + // triple-char + "!==", // and, xor below + // double-char + "==", "!=", "<=", ">=", "||", "&&", "!~", // or below + // single-char + "+", "-", "*", "/", "(", ")", "<", ">", "^", "!", "%", "=", "~", + ] { + if remainder.starts_with(strop) { + self.cursor += strop.len(); + return Some((remainder[..strop.len()].into(), Type::Op)); + } + } + + // operators that require a boundary afterward + for strop in &["and", "xor", "!==", "or"] { + if remainder.starts_with(strop) { + if self.cursor + strop.len() == self.eos + || is_boundary( + remainder[strop.len() - 1..].chars().next().unwrap(), + remainder[strop.len()..].chars().next().unwrap(), + ) + { + self.cursor += strop.len(); + return Some((remainder[..strop.len()].into(), Type::Op)); + } + } + } + None + } + + fn is_identifier(&mut self) -> Option<(String, Type)> { + let mut chars = self.text.get(self.cursor..)?.chars(); + let start = self.cursor; + let mut len = 0; + + if let Some(c) = chars.next() { + if is_identifier_start(c) { + len += c.len_utf8(); + for c in chars { + if !is_identifier_next(c) { + break; + } + len += c.len_utf8(); + } + self.cursor += len; + return Some((self.text.get(start..self.cursor)?.into(), Type::Identifier)); + } + } + + None + } + + fn is_word(&mut self) -> Option<(String, Type)> { + let mut marker = self.cursor; + for c in self.text[self.cursor..].chars() { + if unicode_whitespace(c) || is_single_char_operator(c) { + break; + } + marker += c.len_utf8(); + } + + if marker > self.cursor { + let token = self.text[self.cursor..marker].into(); + self.cursor = marker; + return Some((token, Type::Word)); + } + + None + } + + // utilities that may modify self + + fn is_one_of>( + &mut self, + options: &[S], + allow_abbreviations: bool, + end_boundary: bool, + ) -> bool { + for option in options { + if self.is_literal(option.as_ref(), allow_abbreviations, end_boundary) { + return true; + } + } + false + } + + fn is_literal(&mut self, literal: &str, allow_abbreviations: bool, end_boundary: bool) -> bool { + // calculate the number of common characters between the literal and the string being + // parsed + let common = common_length(literal, &self.text[self.cursor..]); + + // Without abbreviations, common must equal literal length. + if !allow_abbreviations && common < literal.len() { + return false; + } + + if allow_abbreviations && common < MINIMUM_MATCH_LEN { + return false; + } + + if end_boundary { + let c = self.text[self.cursor + common..].chars().next(); + if let Some(c) = c { + if !unicode_whitespace(c) && !is_single_char_operator(c) { + return false; + } + } + } + + self.cursor += common; + + true + } + + fn is_integer(&mut self) -> Option<(String, Type)> { + let mut marker = self.cursor; + for c in self.text[self.cursor..].chars() { + if !unicode_latin_digit(c) { + break; + } + marker += c.len_utf8(); + } + + if marker > self.cursor { + let token = self.text[self.cursor..marker].into(); + self.cursor = marker; + return Some((token, Type::Number)); + } + + None + } +} + +struct LexerIterator(Lexer); + +impl Iterator for LexerIterator { + type Item = (String, Type); + + fn next(&mut self) -> Option { + self.0.token() + } +} + +impl IntoIterator for Lexer { + type Item = (String, Type); + type IntoIter = LexerIterator; + + fn into_iter(self) -> Self::IntoIter { + LexerIterator(self) + } +} + +#[cfg(test)] +mod test { + use super::*; + const NONE: Option<(String, Type)> = None; + + #[test] + fn test_is_punctuation_comma() { + assert!(is_punctuation(',')); + } + + #[test] + fn test_is_punctuation_slash() { + assert!(is_punctuation('/')); + } + + #[test] + fn test_is_punctuation_at() { + assert!(!is_punctuation('@')); + } + + #[test] + fn test_is_punctuation_hash() { + assert!(!is_punctuation('#')); + } + + #[test] + fn test_is_punctuation_dollar() { + assert!(!is_punctuation('$')); + } + + #[test] + fn test_is_punctuation_underscore() { + assert!(!is_punctuation('_')); + } + + #[test] + fn test_is_punctuation_space() { + assert!(!is_punctuation(' ')); + } + + #[test] + fn test_is_punctuation_a() { + assert!(!is_punctuation('a')); + } + + #[test] + fn test_is_punctuation_9() { + assert!(!is_punctuation('9')); + } + + #[test] + fn test_is_punctuation_latin() { + assert!(!is_punctuation('é')); + } + + #[test] + fn test_is_punctuation_euro() { + assert!(!is_punctuation('€')); + } + + #[test] + fn test_is_punctuation_smile() { + assert!(!is_punctuation('☺')); + } + + #[test] + fn test_is_punctuation_numeric() { + assert!(!is_punctuation('¾')); + } + + #[test] + fn test_is_boundary() { + assert!(is_boundary(' ', 'a')); + assert!(is_boundary('a', ' ')); + assert!(is_boundary(' ', '+')); + assert!(is_boundary(' ', ',')); + assert!(!is_boundary('3', '4')); + assert!(is_boundary('(', '(')); + assert!(!is_boundary('r', 'd')); + } + + #[test] + fn test_was_quoted() { + assert!(!was_quoted("")); + assert!(!was_quoted("foo")); + assert!(was_quoted("a b")); + assert!(was_quoted("(a)")); + } + + #[test] + fn test_dequote() { + assert_eq!(dequote("foo", "'\""), "foo"); + assert_eq!(dequote("'foo'", "'\""), "foo"); + assert_eq!(dequote("\"foo\"", "'\""), "foo"); + assert_eq!(dequote("'o\\'clock'", "'\""), "o\\'clock"); + // single quote char + assert_eq!(dequote("'", "'\""), ""); + // multibyte quote char + assert_eq!(dequote("éo\\'clocké", "é"), "o\\'clock"); + } + + #[test] + fn test_token_empty() { + let mut l = Lexer::new(""); + assert_eq!(l.token(), NONE); + } + + #[test] + fn test_token_tokens() { + let mut l = Lexer::new( + " one 'two \\'three\\''+456-(1.3*2 - 0x12) 1.2e-3.4 foo.bar and '\\u20ac'", + ); + assert_eq!(l.token(), Some((String::from("one"), Type::Identifier))); + assert_eq!( + l.token(), + Some((String::from("'two 'three''"), Type::String)) + ); + assert_eq!(l.token(), Some((String::from("+"), Type::Op))); + assert_eq!(l.token(), Some((String::from("456"), Type::Number))); + assert_eq!(l.token(), Some((String::from("-"), Type::Op))); + assert_eq!(l.token(), Some((String::from("("), Type::Op))); + assert_eq!(l.token(), Some((String::from("1.3"), Type::Number))); + assert_eq!(l.token(), Some((String::from("*"), Type::Op))); + assert_eq!(l.token(), Some((String::from("2"), Type::Number))); + assert_eq!(l.token(), Some((String::from("-"), Type::Op))); + assert_eq!(l.token(), Some((String::from("0x12"), Type::Hex))); + assert_eq!(l.token(), Some((String::from(")"), Type::Op))); + assert_eq!(l.token(), Some((String::from("1.2e-3.4"), Type::Number))); + assert_eq!(l.token(), Some((String::from("foo.bar"), Type::Identifier))); + assert_eq!(l.token(), Some((String::from("and"), Type::Op))); + assert_eq!(l.token(), Some((String::from("'€'"), Type::String))); + assert_eq!(l.token(), None); + } + + #[test] + fn test_token_short_numbers() { + let mut l = Lexer::new("1 12 123 1234 12345 123456 1234567 123.45e 12.34e+"); + assert_eq!(l.token(), Some((String::from("1"), Type::Number))); + assert_eq!(l.token(), Some((String::from("12"), Type::Number))); + assert_eq!(l.token(), Some((String::from("123"), Type::Number))); + assert_eq!(l.token(), Some((String::from("1234"), Type::Number))); + assert_eq!(l.token(), Some((String::from("12345"), Type::Number))); + assert_eq!(l.token(), Some((String::from("123456"), Type::Number))); + assert_eq!(l.token(), Some((String::from("1234567"), Type::Number))); + assert_eq!(l.token(), Some((String::from("123.45e"), Type::Number))); + assert_eq!(l.token(), Some((String::from("12.34e+"), Type::Number))); + assert_eq!(l.token(), None); + } + + #[test] + fn test_read_word_quoted_simple() { + assert_eq!( + read_word_quoted("'one two'", "'\"", 0), + Some((String::from("'one two'"), 9)) + ); + } + + #[test] + fn test_read_word_quoted_unterminated() { + assert_eq!( + read_word_quoted("'one two", "'\"", 0), + None as Option<(String, usize)> + ); + } + + #[test] + fn test_read_word_quoted_backslash_u() { + assert_eq!( + read_word_quoted("'pay \\u20a43'", "'\"", 0), + Some((String::from("'pay ₤3'"), 13)) + ); + } + + #[test] + fn test_read_word_quoted_u_plus() { + assert_eq!( + read_word_quoted("\"pay U+20AC5\"", "'\"", 0), + Some((String::from("\"pay €5\""), 13)) + ); + } + + #[test] + fn test_read_word_unquoted_simple() { + assert_eq!( + read_word_unquoted("input", 0), + Some((String::from("input"), 5)) + ); + } + + #[test] + fn test_read_word_unquoted_escaped_space() { + assert_eq!( + read_word_unquoted("one\\ two", 0), + Some((String::from("one two"), 8)) + ); + } + + #[test] + fn test_read_word_unquoted_escaped_quote() { + assert_eq!( + read_word_unquoted("one\\\"two", 0), + Some((String::from("one\"two"), 8)) + ); + } + + #[test] + fn test_read_word_unquoted_escaped_newline() { + assert_eq!( + read_word_unquoted("one\\ntwo", 0), + Some((String::from("one\x0atwo"), 8)) + ); + } + + #[test] + fn test_read_word_unquoted_escaped_backslash_u() { + assert_eq!( + read_word_unquoted("pay\\u20a43", 0), + Some((String::from("pay₤3"), 10)) + ); + } + + #[test] + fn test_read_word_unquoted_incomplete_escaped_backslash_u() { + assert_eq!( + read_word_unquoted("\\u203", 0), + Some((String::from("u203"), 5)) + ); + } + + #[test] + fn test_read_word_unquoted_nonhex_escaped_backslash_u() { + assert_eq!( + read_word_unquoted("\\u2fghk", 0), + Some((String::from("u2fghk"), 7)) + ); + } + + #[test] + fn test_read_word_unquoted_escaped_u_plus() { + assert_eq!( + read_word_unquoted("payU+20AC4", 0), + Some((String::from("pay€4"), 10)) + ); + } + + #[test] + fn test_read_word_unquoted_incomplete_u_plus() { + assert_eq!( + read_word_unquoted("U+20A", 0), + Some((String::from("U+20A"), 5)) + ); + } + + #[test] + fn test_read_word_trailing_whitespace() { + assert_eq!( + read_word_unquoted("one ", 0), + Some((String::from("one"), 3)) + ); + } + + #[test] + fn test_read_word_unquoted_several_words() { + let text = "one 'two' three\\ four"; + assert_eq!(read_word_unquoted(text, 0), Some((String::from("one"), 3))); + assert_eq!( + read_word_unquoted(text, 4), + Some((String::from("'two'"), 9)) + ); + assert_eq!( + read_word_unquoted(text, 10), + Some((String::from("three four"), 21)) + ); + } + + #[test] + fn test_common_length_empty() { + assert_eq!(common_length("", ""), 0); + } + + #[test] + fn test_common_length_match_one() { + assert_eq!(common_length("a", "a"), 1); + } + + #[test] + fn test_common_length_match_longer() { + assert_eq!(common_length("abcde", "abcde"), 5); + } + + #[test] + fn test_common_length_match_s2_short() { + assert_eq!(common_length("abc", ""), 0); + } + + #[test] + fn test_common_length_match_differ() { + assert_eq!(common_length("abc", "def"), 0); + } + + #[test] + fn test_common_length_match_s2_prefix() { + assert_eq!(common_length("foobar", "foo"), 3); + } + + #[test] + fn test_common_length_match_s1_prefix() { + assert_eq!(common_length("foo", "foobar"), 3); + } + + #[test] + fn test_is_string() { + let mut l = Lexer::new("'one'"); + assert_eq!(l.is_string("'\""), Some(("'one'".into(), Type::String))); + assert_eq!(l.cursor, 5); + } + + #[test] + fn test_is_string_negative() { + let mut l = Lexer::new("one"); + assert_eq!(l.is_string("'\""), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_string_empty() { + let mut l = Lexer::new("''"); + assert_eq!(l.is_string("'\""), Some(("''".into(), Type::String))); + assert_eq!(l.cursor, 2); + } + + #[test] + fn test_is_string_escape() { + let mut l = Lexer::new("'one\ttwo'"); + assert_eq!( + l.is_string("'\""), + Some(("'one\ttwo'".into(), Type::String)) + ); + assert_eq!(l.cursor, 9); + } + + #[test] + fn test_is_date_year_eos() { + let mut l = Lexer::new("2015"); + assert_eq!(l.is_date(), Some(("2015".into(), Type::Date))); + assert_eq!(l.cursor, 4); + } + + #[test] + fn test_is_date_epoch() { + let mut l = Lexer::new("315532800"); + assert_eq!(l.is_date(), Some(("315532800".into(), Type::Date))); + assert_eq!(l.cursor, 9); + } + + #[test] + fn test_is_date_year_ws() { + let mut l = Lexer::new("2015 "); + assert_eq!(l.is_date(), Some(("2015".into(), Type::Date))); + assert_eq!(l.cursor, 4); + } + + #[test] + fn test_is_date_year_ident() { + let mut l = Lexer::new("2015abc"); + assert_eq!(l.is_date(), Some(("2015".into(), Type::Date))); + assert_eq!(l.cursor, 4); + } + + #[test] + fn test_is_date_year_plus() { + let mut l = Lexer::new("2015+"); + assert_eq!(l.is_date(), Some(("2015".into(), Type::Date))); + assert_eq!(l.cursor, 4); + } + + #[test] + fn test_is_date_year_minus() { + let mut l = Lexer::new("2015-xyz"); + assert_eq!(l.is_date(), Some(("2015-".into(), Type::Date))); + assert_eq!(l.cursor, 5); + } + + #[test] + fn test_is_duration_1w() { + let mut l = Lexer::new("1w"); + assert_eq!(l.is_duration(), Some(("1w".into(), Type::Duration))); + assert_eq!(l.cursor, 2); + } + + #[test] + fn test_is_duration_op() { + let mut l = Lexer::new("!!"); + assert_eq!(l.is_duration(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_number_digit() { + let mut l = Lexer::new("3"); + assert_eq!(l.is_number(), Some(("3".into(), Type::Number))); + assert_eq!(l.cursor, 1); + } + + #[test] + fn test_is_number_integer() { + let mut l = Lexer::new("13"); + assert_eq!(l.is_number(), Some(("13".into(), Type::Number))); + assert_eq!(l.cursor, 2); + } + + #[test] + fn test_is_number_trailing_minus() { + let mut l = Lexer::new("13-"); + assert_eq!(l.is_number(), Some(("13".into(), Type::Number))); + assert_eq!(l.cursor, 2); + } + + #[test] + fn test_is_number_decimal() { + let mut l = Lexer::new("1.3"); + assert_eq!(l.is_number(), Some(("1.3".into(), Type::Number))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_number_multiple_decimal() { + let mut l = Lexer::new("1.3.4"); + assert_eq!(l.is_number(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_number_decimal_no_digits() { + let mut l = Lexer::new("1."); + assert_eq!(l.is_number(), Some(("1.".into(), Type::Number))); + assert_eq!(l.cursor, 2); + } + + #[test] + fn test_is_number_decimal_multi_digit() { + let mut l = Lexer::new("12.32"); + assert_eq!(l.is_number(), Some(("12.32".into(), Type::Number))); + assert_eq!(l.cursor, 5); + } + + #[test] + fn test_is_number_decimal_e_no_exponent() { + let mut l = Lexer::new("12.32e"); + assert_eq!(l.is_number(), Some(("12.32e".into(), Type::Number))); + assert_eq!(l.cursor, 6); + } + + #[test] + fn test_is_number_decimal_e_plus_no_exponent() { + let mut l = Lexer::new("12.32e+"); + assert_eq!(l.is_number(), Some(("12.32e+".into(), Type::Number))); + assert_eq!(l.cursor, 7); + } + + #[test] + fn test_is_number_decimal_e_integer_exponent() { + let mut l = Lexer::new("12.32e-12"); + assert_eq!(l.is_number(), Some(("12.32e-12".into(), Type::Number))); + assert_eq!(l.cursor, 9); + } + + #[test] + fn test_is_number_decimal_e_decimal_exponent() { + let mut l = Lexer::new("12.32e12.34"); + assert_eq!(l.is_number(), Some(("12.32e12.34".into(), Type::Number))); + assert_eq!(l.cursor, 11); + } + + #[test] + fn test_is_number_integer_invalid_lookahead() { + let mut l = Lexer::new("13a"); + assert_eq!(l.is_number(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_set_singletons() { + let mut l = Lexer::new("12,13"); + assert_eq!(l.is_set(), Some(("12,13".into(), Type::Set))); + assert_eq!(l.cursor, 5); + } + + #[test] + fn test_is_set_ranges() { + let mut l = Lexer::new("12-13,19-200"); + assert_eq!(l.is_set(), Some(("12-13,19-200".into(), Type::Set))); + assert_eq!(l.cursor, 12); + } + + #[test] + fn test_is_set_double_comma() { + let mut l = Lexer::new("12-13,,19-200"); + assert_eq!(l.is_set(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_set_trailing_comma() { + let mut l = Lexer::new("12-13,"); + assert_eq!(l.is_set(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_set_trailing_ws() { + let mut l = Lexer::new("12-13 "); + assert_eq!(l.is_set(), Some(("12-13".into(), Type::Set))); + assert_eq!(l.cursor, 5); + } + + #[test] + fn test_is_set_trailing_non_hard_boundary() { + let mut l = Lexer::new("12-13abc"); + assert_eq!(l.is_set(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_separator() { + let mut l = Lexer::new(" -- "); + l.cursor = 2; + assert_eq!(l.is_separator(), Some(("--".into(), Type::Separator))); + assert_eq!(l.cursor, 4); + } + + #[test] + fn test_is_separator_negative() { + let mut l = Lexer::new("- "); + assert_eq!(l.is_separator(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_tag_plus() { + let mut l = Lexer::new("+foo"); + assert_eq!(l.is_tag(), Some(("+foo".into(), Type::Tag))); + assert_eq!(l.cursor, 4); + } + + #[test] + fn test_is_tag_not_after_whitespace() { + let mut l = Lexer::new("x+y"); + l.cursor = 1; + assert_eq!(l.is_tag(), NONE); + assert_eq!(l.cursor, 1); + } + + #[test] + fn test_is_tag_after_whitespace() { + let mut l = Lexer::new(" +y"); + l.cursor = 1; + assert_eq!(l.is_tag(), Some(("+y".into(), Type::Tag))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_tag_after_lparen() { + let mut l = Lexer::new("(+y"); + l.cursor = 1; + assert_eq!(l.is_tag(), Some(("+y".into(), Type::Tag))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_tag_after_rparen() { + let mut l = Lexer::new(")+y"); + l.cursor = 1; + assert_eq!(l.is_tag(), Some(("+y".into(), Type::Tag))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_tag_after_multibyte_char() { + let mut l = Lexer::new("€+y"); + l.cursor = 3; + assert_eq!(l.is_tag(), NONE); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_url_http() { + let mut l = Lexer::new("http://foo.com/bar"); + assert_eq!(l.is_url(), Some(("http://foo.com/bar".into(), Type::URL))); + assert_eq!(l.cursor, 18); + } + + #[test] + fn test_is_url_https() { + let mut l = Lexer::new("https://foo.com/bar"); + assert_eq!(l.is_url(), Some(("https://foo.com/bar".into(), Type::URL))); + assert_eq!(l.cursor, 19); + } + + #[test] + fn test_is_url_ws() { + let mut l = Lexer::new("https://foo.com/bar "); + assert_eq!(l.is_url(), Some(("https://foo.com/bar".into(), Type::URL))); + assert_eq!(l.cursor, 19); + } + + #[test] + fn test_is_url_with_ops() { + let mut l = Lexer::new("https://foo.com/bar()+-~"); + assert_eq!( + l.is_url(), + Some(("https://foo.com/bar()+-~".into(), Type::URL)) + ); + assert_eq!(l.cursor, 24); + } + + #[test] + fn test_is_url_negative() { + let mut l = Lexer::new("file://foo.com/bar"); + assert_eq!(l.is_url(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_pair_double_colon() { + let mut l = Lexer::new("foo::bar "); + assert_eq!(l.is_pair(), Some(("foo::bar".into(), Type::Pair))); + assert_eq!(l.cursor, 8); + } + + #[test] + fn test_is_pair_colon_eq() { + let mut l = Lexer::new("foo:=bar "); + assert_eq!(l.is_pair(), Some(("foo:=bar".into(), Type::Pair))); + assert_eq!(l.cursor, 8); + } + + #[test] + fn test_is_pair_colon() { + let mut l = Lexer::new("foo:bar "); + assert_eq!(l.is_pair(), Some(("foo:bar".into(), Type::Pair))); + assert_eq!(l.cursor, 7); + } + + #[test] + fn test_is_pair_equal() { + let mut l = Lexer::new("foo=bar"); + assert_eq!(l.is_pair(), Some(("foo=bar".into(), Type::Pair))); + assert_eq!(l.cursor, 7); + } + + #[test] + fn test_is_pair_quoted() { + let mut l = Lexer::new("foo='abc def'"); + assert_eq!(l.is_pair(), Some(("foo='abc def'".into(), Type::Pair))); + assert_eq!(l.cursor, 13); + } + + #[test] + fn test_is_pair_quoted_escapes() { + let mut l = Lexer::new("foo='abc\\u20acdef'"); + assert_eq!(l.is_pair(), Some(("foo='abc€def'".into(), Type::Pair))); + assert_eq!(l.cursor, 18); + } + + #[test] + fn test_is_uuid_long_eof() { + let u = "ffffffff-ffff-ffff-ffff-ffffffffff"; + let mut l = Lexer::new(u); + assert_eq!(l.is_uuid(true), Some((u.into(), Type::Uuid))); + assert_eq!(l.cursor, 34); + } + + #[test] + fn test_is_uuid_long_ws() { + let u = "ffffffff-ffff-ffff-ffff-ffffffffff kjdf"; + let mut l = Lexer::new(u); + assert_eq!(l.is_uuid(true), Some((u[..34].into(), Type::Uuid))); + assert_eq!(l.cursor, 34); + } + + #[test] + fn test_is_uuid_long_op() { + let u = "ffffffff-ffff-ffff-ffff-ffffffffff+"; + let mut l = Lexer::new(u); + assert_eq!(l.is_uuid(true), Some((u[..34].into(), Type::Uuid))); + assert_eq!(l.cursor, 34); + } + + #[test] + fn test_is_uuid_long_bad_boundary() { + let u = "ffffffff-ffff-ffff-ffff-ffffffffff_"; + let mut l = Lexer::new(u); + assert_eq!(l.is_uuid(true), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_uuid_long_bad_boundary_ignored() { + let u = "ffffffff-ffff-ffff-ffff-ffffffffff_"; + let mut l = Lexer::new(u); + assert_eq!(l.is_uuid(false), Some((u[..34].into(), Type::Uuid))); + assert_eq!(l.cursor, 34); + } + + #[test] + fn test_is_uuid_too_short() { + let u = "ffffff"; + let mut l = Lexer::new(u); + assert_eq!(l.is_uuid(true), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_path_simple() { + let mut l = Lexer::new("/path/to/a/file"); + assert_eq!(l.is_path(), Some(("/path/to/a/file".into(), Type::Path))); + assert_eq!(l.cursor, 15); + } + + #[test] + fn test_is_path_too_short() { + let mut l = Lexer::new("/a/file"); + assert_eq!(l.is_path(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_path_trailing_slash() { + let mut l = Lexer::new("/path/to/a/dir/"); + assert_eq!(l.is_path(), Some(("/path/to/a/dir/".into(), Type::Path))); + assert_eq!(l.cursor, 15); + } + + #[test] + fn test_is_path_double_slash() { + let mut l = Lexer::new("/a//file"); + assert_eq!(l.is_path(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_path_no_initial_slash() { + let mut l = Lexer::new("a/path/to/a/file"); + assert_eq!(l.is_path(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_substitution_simple() { + let mut l = Lexer::new("/foo/bar/"); + assert_eq!( + l.is_substitution(), + Some(("/foo/bar/".into(), Type::Substitution)) + ); + assert_eq!(l.cursor, 9); + } + + #[test] + fn test_is_substitution_simple_ws() { + let mut l = Lexer::new("/foo/bar/ "); + assert_eq!( + l.is_substitution(), + Some(("/foo/bar/".into(), Type::Substitution)) + ); + assert_eq!(l.cursor, 9); + } + + #[test] + fn test_is_substitution_simple_g() { + let mut l = Lexer::new("/foo/bar/g"); + assert_eq!( + l.is_substitution(), + Some(("/foo/bar/g".into(), Type::Substitution)) + ); + assert_eq!(l.cursor, 10); + } + + #[test] + fn test_is_substitution_simple_g_ws() { + let mut l = Lexer::new("/foo/bar/g "); + assert_eq!( + l.is_substitution(), + Some(("/foo/bar/g".into(), Type::Substitution)) + ); + assert_eq!(l.cursor, 10); + } + + #[test] + fn test_is_substitution_simple_not_g() { + let mut l = Lexer::new("/foo/bar/h"); + assert_eq!(l.is_substitution(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_substitution_simple_not_g_op() { + let mut l = Lexer::new("/foo/bar/+"); + assert_eq!(l.is_substitution(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_substitution_simple_g_but_not_ws() { + let mut l = Lexer::new("/foo/bar/ghi"); + assert_eq!(l.is_substitution(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_pattern_simple() { + let mut l = Lexer::new("/foo/"); + assert_eq!(l.is_pattern(), Some(("/foo/".into(), Type::Pattern))); + assert_eq!(l.cursor, 5); + } + + #[test] + fn test_is_pattern_escaped() { + let mut l = Lexer::new("/f\\u20A4o/"); + assert_eq!(l.is_pattern(), Some(("/f\\u20A4o/".into(), Type::Pattern))); + assert_eq!(l.cursor, 10); + } + + #[test] + fn test_is_pattern_simple_trailing_ws() { + let mut l = Lexer::new("/foo/\n\t"); + assert_eq!(l.is_pattern(), Some(("/foo/".into(), Type::Pattern))); + assert_eq!(l.cursor, 5); + } + + #[test] + fn test_is_operator_hastag() { + let mut l = Lexer::new("_hastag_"); + assert_eq!(l.is_operator(), Some(("_hastag_".into(), Type::Op))); + } + + #[test] + fn test_is_operator_notag() { + let mut l = Lexer::new("_notag_"); + assert_eq!(l.is_operator(), Some(("_notag_".into(), Type::Op))); + } + + #[test] + fn test_is_operator_neg() { + let mut l = Lexer::new("_neg_"); + assert_eq!(l.is_operator(), Some(("_neg_".into(), Type::Op))); + } + + #[test] + fn test_is_operator_xor() { + let mut l = Lexer::new("xor"); + assert_eq!(l.is_operator(), Some(("xor".into(), Type::Op))); + } + + #[test] + fn test_is_identifier_empty() { + let mut l = Lexer::new(""); + assert_eq!(l.is_identifier(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_identifier_multibyte_nonpunct_first_char() { + let mut l = Lexer::new("☺"); + assert_eq!(l.is_identifier(), Some(("☺".into(), Type::Identifier))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_identifier_bad_first_char() { + let mut l = Lexer::new("1abc"); + assert_eq!(l.is_identifier(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_identifier_bad_next_char() { + let mut l = Lexer::new("a:bc"); + assert_eq!(l.is_identifier(), Some(("a".into(), Type::Identifier))); + assert_eq!(l.cursor, 1); + } + + #[test] + fn test_is_identifier_ok() { + let mut l = Lexer::new("abc"); + assert_eq!(l.is_identifier(), Some(("abc".into(), Type::Identifier))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_word_no() { + let mut l = Lexer::new("+"); + assert!(l.is_word().is_none()); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_word_pending() { + let mut l = Lexer::new("foo.PENDING"); + l.cursor = 4; + assert_eq!(l.is_word(), Some(("PENDING".into(), Type::Word))); + assert_eq!(l.cursor, 11); + } + + #[test] + fn test_is_word_to_eof() { + let mut l = Lexer::new("abc"); + assert_eq!(l.is_word(), Some(("abc".into(), Type::Word))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_word_nonzero_start() { + let mut l = Lexer::new("--abc"); + l.cursor = 2; + assert_eq!(l.is_word(), Some(("abc".into(), Type::Word))); + assert_eq!(l.cursor, 5); + } + + #[test] + fn test_is_word_to_ws() { + let mut l = Lexer::new("abc def"); + assert_eq!(l.is_word(), Some(("abc".into(), Type::Word))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_word_to_op() { + let mut l = Lexer::new("abc*def"); + assert_eq!(l.is_word(), Some(("abc".into(), Type::Word))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_split_simple() { + assert_eq!( + Lexer::split(" ( A or B ) "), + vec![ + String::from("("), + String::from("A"), + String::from("or"), + String::from("B"), + String::from(")"), + ] + ); + } + + #[test] + fn test_split_confusing() { + assert_eq!( + Lexer::split(" +-* a+b 12.3e4 'c d'"), + vec![ + String::from("+"), + String::from("-"), + String::from("*"), + String::from("a"), + String::from("+"), + String::from("b"), + String::from("12.3e4"), + String::from("'c d'"), + ] + ); + } + + #[test] + fn test_decompose_pair_combos() { + let name = "name"; + for modifier in ["", "mod"].iter() { + for separator in [":", "=", "::", ":="].iter() { + for value in ["", "value", "a:b", "a::b", "a=b", "a:=b"].iter() { + let input = format!( + "{}{}{}{}{}", + name, + if modifier.len() > 0 { "." } else { "" }, + modifier, + separator, + value + ); + assert_eq!( + Lexer::decompose_pair(&input), + Some(DecomposedPair { + name: name.into(), + modifier: String::from(*modifier), + separator: String::from(*separator), + value: String::from(*value), + }) + ); + } + } + } + } + + #[test] + fn test_is_one_of() { + let mut l = Lexer::new("Grumpy."); + let dwarves = vec![ + "Sneezy", "Doc", "Bashful", "Grumpy", "Happy", "Sleepy", "Dopey", + ]; + assert!(!l.is_one_of(&dwarves, false, true)); + assert_eq!(l.cursor, 0); + assert!(l.is_one_of(&dwarves, false, false)); + assert_eq!(l.cursor, 6); + } + + #[test] + fn test_is_integer_negative() { + let mut l = Lexer::new("one"); + assert_eq!(l.is_integer(), NONE); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_integer_positive() { + let mut l = Lexer::new("123"); + assert_eq!(l.is_integer(), Some(("123".into(), Type::Number))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_integer_trailing_dot() { + let mut l = Lexer::new("123.foo"); + assert_eq!(l.is_integer(), Some(("123".into(), Type::Number))); + assert_eq!(l.cursor, 3); + } + + #[test] + fn test_is_integer_not_at_start() { + let mut l = Lexer::new("abc.123.foo"); + l.cursor = 4; + assert_eq!(l.is_integer(), Some(("123".into(), Type::Number))); + assert_eq!(l.cursor, 7); + } + + #[test] + fn test_is_literal_no_match() { + let mut l = Lexer::new("one.two"); + assert!(!l.is_literal("zero", false, false)); + assert_eq!(l.cursor, 0); + } + + #[test] + fn test_is_literal_multi() { + let mut l = Lexer::new("one.two"); + assert!(l.is_literal("one", false, false)); + assert_eq!(l.cursor, 3); + assert!(l.is_literal(".", false, false)); + assert_eq!(l.cursor, 4); + assert!(l.is_literal("two", false, true)); + assert_eq!(l.cursor, 7); + } + + #[test] + fn test_is_literal_abbrev() { + let mut l = Lexer::new("wonder"); + assert!(!l.is_literal("wonderful", false, false)); + assert_eq!(l.cursor, 0); + assert!(l.is_literal("wonderful", true, false)); + assert_eq!(l.cursor, 6); + } + + mod integ { + use super::super::*; + + fn lexer_test(input: &str, expected: Vec<(&str, Type)>) { + // isolated case.. + let mut lexer = Lexer::new(input); + lexer.add_attribute("due"); + lexer.add_attribute("tags"); + lexer.add_attribute("description"); + let got: Vec<_> = lexer.into_iter().collect(); + let got_strs: Vec<_> = got.iter().map(|(s, t)| (s.as_ref(), *t)).collect(); + assert_eq!(got_strs, expected); + + // embedded case.. + let mut lexer = Lexer::new(format!(" {} ", input)); + lexer.add_attribute("due"); + lexer.add_attribute("tags"); + lexer.add_attribute("description"); + let got: Vec<_> = lexer.into_iter().collect(); + let got_strs: Vec<_> = got.iter().map(|(s, t)| (s.as_ref(), *t)).collect(); + assert_eq!(got_strs, expected); + } + + #[test] + fn test_pattern_foo() { + lexer_test("/foo/", vec![("/foo/", Type::Pattern)]); + } + + #[test] + fn test_pattern_escaped_slash() { + lexer_test("/a\\/b/", vec![("/a\\/b/", Type::Pattern)]); + } + + #[test] + fn test_pattern_quote() { + lexer_test("/'/", vec![("/'/", Type::Pattern)]); + } + + // Substitution + // + #[test] + fn test_subst_g() { + lexer_test("/from/to/g", vec![("/from/to/g", Type::Substitution)]); + } + + #[test] + fn test_subst() { + lexer_test("/from/to/", vec![("/from/to/", Type::Substitution)]); + } + + // Tag + // + #[test] + fn test_tag_simple() { + lexer_test("+tag", vec![("+tag", Type::Tag)]); + } + + #[test] + fn test_tag_negative() { + lexer_test("-tag", vec![("-tag", Type::Tag)]); + } + + #[test] + fn test_tag_at() { + lexer_test("+@tag", vec![("+@tag", Type::Tag)]); + } + + // Path + // + #[test] + fn test_path() { + lexer_test( + "/long/path/to/file.txt", + vec![("/long/path/to/file.txt", Type::Path)], + ); + } + + #[test] + fn test_path_dir() { + lexer_test( + "/long/path/to/dir/", + vec![("/long/path/to/dir/", Type::Path)], + ); + } + + // Word + // + #[test] + fn test_1_foo_bar() { + lexer_test("1.foo.bar", vec![("1.foo.bar", Type::Word)]); + } + + // Identifier + // + #[test] + fn test_foo() { + lexer_test("foo", vec![("foo", Type::Identifier)]); + } + + #[test] + fn test_multibyte_ident() { + lexer_test("Çirçös", vec![("Çirçös", Type::Identifier)]); + } + + #[test] + fn test_multibyte_nonpunctuation_single_char() { + lexer_test("☺", vec![("☺", Type::Identifier)]); + } + + #[test] + fn test_name() { + lexer_test("name", vec![("name", Type::Identifier)]); + } + + #[test] + fn test_f1() { + lexer_test("f1", vec![("f1", Type::Identifier)]); + } + + #[test] + fn test_foo_dot_bar() { + lexer_test("foo.bar", vec![("foo.bar", Type::Identifier)]); + } + + #[test] + fn test_long_with_underscore() { + lexer_test( + "a1a1a1a1_a1a1_a1a1_a1a1_a1a1a1a1a1a1", + vec![("a1a1a1a1_a1a1_a1a1_a1a1_a1a1a1a1a1a1", Type::Identifier)], + ); + } + + // Word that starts wih 'or', which is an operator, but should be ignored. + // + #[test] + fn test_starts_with_or() { + lexer_test("ordinary", vec![("ordinary", Type::Identifier)]); + } + + // DOM + // + #[test] + fn test_due() { + lexer_test("due", vec![("due", Type::DOM)]); + } + + #[test] + fn test_123_tags() { + lexer_test("123.tags", vec![("123.tags", Type::DOM)]); + } + + #[test] + fn test_123_tags_pending() { + lexer_test("123.tags.PENDING", vec![("123.tags.PENDING", Type::DOM)]); + } + + #[test] + fn test_123_description() { + lexer_test("123.description", vec![("123.description", Type::DOM)]); + } + + #[test] + fn test_123_annotations_count() { + lexer_test( + "123.annotations.count", + vec![("123.annotations.count", Type::DOM)], + ); + } + + #[test] + fn test_123_annotations_1_description() { + lexer_test( + "123.annotations.1.description", + vec![("123.annotations.1.description", Type::DOM)], + ); + } + + #[test] + fn test_123_annotations_1_entry() { + lexer_test( + "123.annotations.1.entry", + vec![("123.annotations.1.entry", Type::DOM)], + ); + } + + #[test] + fn test_123_annotations_1_entry_year() { + lexer_test( + "123.annotations.1.entry.year", + vec![("123.annotations.1.entry.year", Type::DOM)], + ); + } + + #[test] + fn test_uuid_due() { + lexer_test( + "a360fc44-315c-4366-b70c-ea7e7520b749.due", + vec![("a360fc44-315c-4366-b70c-ea7e7520b749.due", Type::DOM)], + ); + } + + #[test] + fn test_numeric_uuid_due() { + lexer_test( + "12345678-1234-1234-1234-123456789012.due", + vec![("12345678-1234-1234-1234-123456789012.due", Type::DOM)], + ); + } + + #[test] + fn test_system_os() { + lexer_test("system.os", vec![("system.os", Type::DOM)]); + } + + #[test] + fn test_rc_foo() { + lexer_test("rc.foo", vec![("rc.foo", Type::DOM)]); + } + + // URL + // + #[test] + fn test_lexer_31() { + lexer_test( + "http://example.com", + vec![("http://example.com", Type::URL)], + ); + } + + #[test] + fn test_lexer_32() { + lexer_test( + "https://foo.example.com", + vec![("https://foo.example.com", Type::URL)], + ); + } + + // String + // + #[test] + fn test_quoted_string() { + lexer_test("'one two'", vec![("'one two'", Type::String)]); + } + + #[test] + fn test_double_quoted_string() { + lexer_test("\"three\"", vec![("\"three\"", Type::String)]); + } + + #[test] + fn test_string_quoted_with_escapes() { + lexer_test("'\\''", vec![("'''", Type::String)]); + } + + #[test] + fn test_string_quoted_quotes() { + lexer_test("\"\\\"\"", vec![("\"\"\"", Type::String)]); + } + + #[test] + fn test_quoted_tabs() { + lexer_test("\"\tfoo\t\"", vec![("\"\tfoo\t\"", Type::String)]); + } + + #[test] + fn test_multibyte_slash_u() { + lexer_test("\"\\u20A43\"", vec![("\"₤3\"", Type::String)]); + } + + #[test] + fn test_multibyte_u_plus() { + lexer_test("\"U+20AC4\"", vec![("\"€4\"", Type::String)]); + } + + // Number + // + #[test] + fn test_one() { + lexer_test("1", vec![("1", Type::Number)]); + } + + #[test] + fn test_pi() { + lexer_test("3.14", vec![("3.14", Type::Number)]); + } + + #[test] + fn test_avogadro() { + lexer_test("6.02217e23", vec![("6.02217e23", Type::Number)]); + } + + #[test] + fn test_expo() { + lexer_test("1.2e-3.4", vec![("1.2e-3.4", Type::Number)]); + } + + #[test] + fn test_hex() { + lexer_test("0x2f", vec![("0x2f", Type::Hex)]); + } + + // Set (1,2,4-7,9) + // + #[test] + fn test_set_pair() { + lexer_test("1,2", vec![("1,2", Type::Set)]); + } + + #[test] + fn test_set_range() { + lexer_test("1-2", vec![("1-2", Type::Set)]); + } + + #[test] + fn test_set_range_pair() { + lexer_test("1-2,4", vec![("1-2,4", Type::Set)]); + } + + #[test] + fn test_set_range_pair_ws() { + lexer_test("1-2,4 ", vec![("1-2,4", Type::Set)]); + } + + #[test] + fn test_set_range_pair_paren() { + lexer_test("1-2,4(", vec![("1-2,4", Type::Set), ("(", Type::Op)]); + } + + #[test] + fn test_ranges_and_singletons() { + lexer_test("1-2,4,6-8", vec![("1-2,4,6-8", Type::Set)]); + } + + #[test] + fn test_set_more_ranges_and_singletons() { + lexer_test("1-2,4,6-8,10-12", vec![("1-2,4,6-8,10-12", Type::Set)]); + } + + // Pair + // + #[test] + fn test_name_colon_value() { + lexer_test("name:value", vec![("name:value", Type::Pair)]); + } + + #[test] + fn test_name_eq_value() { + lexer_test("name=value", vec![("name=value", Type::Pair)]); + } + + #[test] + fn test_name_colon_eq_value() { + lexer_test("name:=value", vec![("name:=value", Type::Pair)]); + } + + #[test] + fn test_name_dot_mod_colon_value() { + lexer_test("name.mod:value", vec![("name.mod:value", Type::Pair)]); + } + + #[test] + fn test_name_dot_mod_eq_value() { + lexer_test("name.mod=value", vec![("name.mod=value", Type::Pair)]); + } + + #[test] + fn test_name_colon() { + lexer_test("name:", vec![("name:", Type::Pair)]); + } + + #[test] + fn test_name_eq() { + lexer_test("name=", vec![("name=", Type::Pair)]); + } + + #[test] + fn test_name_dot_mod_colon() { + lexer_test("name.mod:", vec![("name.mod:", Type::Pair)]); + } + + #[test] + fn test_name_dot_mod_equal() { + lexer_test("name.mod=", vec![("name.mod=", Type::Pair)]); + } + + #[test] + fn test_pro_quoted() { + lexer_test("pro:'P 1'", vec![("pro:'P 1'", Type::Pair)]); + } + + #[test] + fn test_rc_colon_x() { + lexer_test("rc:x", vec![("rc:x", Type::Pair)]); + } + + #[test] + fn test_rc_dot_name_colon_value() { + lexer_test("rc.name:value", vec![("rc.name:value", Type::Pair)]); + } + + #[test] + fn test_rc_dot_name_eq_value() { + lexer_test("rc.name=value", vec![("rc.name=value", Type::Pair)]); + } + + #[test] + fn test_rc_dot_name_colon_eq_value() { + lexer_test("rc.name:=value", vec![("rc.name:=value", Type::Pair)]); + } + + #[test] + fn test_due_colon_eq_quoted() { + lexer_test("due:='eow - 2d'", vec![("due:='eow - 2d'", Type::Pair)]); + } + + #[test] + fn test_name_colon_quoted_with_newline() { + lexer_test("name:'foo\nbar'", vec![("name:'foo\nbar'", Type::Pair)]); + } + + // Operator - complete set + // + #[test] + fn test_caret() { + lexer_test("^", vec![("^", Type::Op)]); + } + + #[test] + fn test_bang() { + lexer_test("!", vec![("!", Type::Op)]); + } + + #[test] + fn test_neg() { + lexer_test("_neg_", vec![("_neg_", Type::Op)]); + } + + #[test] + fn test_pos() { + lexer_test("_pos_", vec![("_pos_", Type::Op)]); + } + + #[test] + fn test_hastag() { + lexer_test("_hastag_", vec![("_hastag_", Type::Op)]); + } + + #[test] + fn test_notag() { + lexer_test("_notag_", vec![("_notag_", Type::Op)]); + } + + #[test] + fn test_star() { + lexer_test("*", vec![("*", Type::Op)]); + } + + #[test] + fn test_slash() { + lexer_test("/", vec![("/", Type::Op)]); + } + + #[test] + fn test_percent() { + lexer_test("%", vec![("%", Type::Op)]); + } + + #[test] + fn test_plus() { + lexer_test("+", vec![("+", Type::Op)]); + } + + #[test] + fn test_minus() { + lexer_test("-", vec![("-", Type::Op)]); + } + + #[test] + fn test_leq() { + lexer_test("<=", vec![("<=", Type::Op)]); + } + + #[test] + fn test_geq() { + lexer_test(">=", vec![(">=", Type::Op)]); + } + + #[test] + fn test_gt() { + lexer_test(">", vec![(">", Type::Op)]); + } + + #[test] + fn test_lt() { + lexer_test("<", vec![("<", Type::Op)]); + } + + #[test] + fn test_eq() { + lexer_test("=", vec![("=", Type::Op)]); + } + + #[test] + fn test_double_eq() { + lexer_test("==", vec![("==", Type::Op)]); + } + + #[test] + fn test_not_eq() { + lexer_test("!=", vec![("!=", Type::Op)]); + } + + #[test] + fn test_not_double_eq() { + lexer_test("!==", vec![("!==", Type::Op)]); + } + + #[test] + fn test_tilde() { + lexer_test("~", vec![("~", Type::Op)]); + } + + #[test] + fn test_not_tilde() { + lexer_test("!~", vec![("!~", Type::Op)]); + } + + #[test] + fn test_and() { + lexer_test("and", vec![("and", Type::Op)]); + } + + #[test] + fn test_or() { + lexer_test("or", vec![("or", Type::Op)]); + } + + #[test] + fn test_xor() { + lexer_test("xor", vec![("xor", Type::Op)]); + } + + #[test] + fn test_lparen() { + lexer_test("(", vec![("(", Type::Op)]); + } + + #[test] + fn test_rparen() { + lexer_test(")", vec![(")", Type::Op)]); + } + + // UUID + // + #[test] + fn test_uuid_ffs() { + lexer_test( + "ffffffff-ffff-ffff-ffff-ffffffffffff", + vec![("ffffffff-ffff-ffff-ffff-ffffffffffff", Type::Uuid)], + ); + } + + #[test] + fn test_uuid_00s() { + lexer_test( + "00000000-0000-0000-0000-0000000", + vec![("00000000-0000-0000-0000-0000000", Type::Uuid)], + ); + } + + #[test] + fn test_uuid_shorter() { + lexer_test( + "00000000-0000-0000-0000", + vec![("00000000-0000-0000-0000", Type::Uuid)], + ); + } + + #[test] + fn test_uuid_shorter_still() { + lexer_test( + "00000000-0000-0000", + vec![("00000000-0000-0000", Type::Uuid)], + ); + } + + #[test] + fn test_uuid_even_shorter() { + lexer_test("00000000-0000", vec![("00000000-0000", Type::Uuid)]); + } + + #[test] + fn test_uuid_only_first_bit() { + lexer_test("00000000", vec![("00000000", Type::Uuid)]); + } + + #[test] + fn test_real_uuid() { + lexer_test( + "a360fc44-315c-4366-b70c-ea7e7520b749", + vec![("a360fc44-315c-4366-b70c-ea7e7520b749", Type::Uuid)], + ); + } + + #[test] + fn test_real_uuid_shorter() { + lexer_test( + "a360fc44-315c-4366-b70c-ea7e752", + vec![("a360fc44-315c-4366-b70c-ea7e752", Type::Uuid)], + ); + } + + #[test] + fn test_real_uuid_shorter_still() { + lexer_test( + "a360fc44-315c-4366-b70c", + vec![("a360fc44-315c-4366-b70c", Type::Uuid)], + ); + } + + #[test] + fn test_real_uuid_even_shorter() { + lexer_test( + "a360fc44-315c-4366", + vec![("a360fc44-315c-4366", Type::Uuid)], + ); + } + + #[test] + fn test_real_uuid_naming_is_hard() { + lexer_test("a360fc44-315c", vec![("a360fc44-315c", Type::Uuid)]); + } + + #[test] + fn test_real_uuid_only_first_bit() { + lexer_test("a360fc44", vec![("a360fc44", Type::Uuid)]); + } + + // Date + // + #[test] + fn test_year_week() { + lexer_test("2015-W01", vec![("2015-W01", Type::Date)]); + } + + #[test] + fn test_year_month_day() { + lexer_test("2015-02-17", vec![("2015-02-17", Type::Date)]); + } + + #[test] + fn test_timestamp() { + lexer_test( + "2013-11-29T22:58:00Z", + vec![("2013-11-29T22:58:00Z", Type::Date)], + ); + } + + #[test] + fn test_abbrev_timestamp() { + lexer_test("20131129T225800Z", vec![("20131129T225800Z", Type::Date)]); + } + + #[test] + fn test_9thn() { + lexer_test("9th", vec![("9th", Type::Date)]); + } + + #[test] + fn test_10th() { + lexer_test("10th", vec![("10th", Type::Date)]); + } + + #[test] + fn test_today() { + lexer_test("today", vec![("today", Type::Date)]); + } + + // Duration + // + #[test] + fn test_year() { + lexer_test("year", vec![("year", Type::Duration)]); + } + + #[test] + fn test_4weeks() { + lexer_test("4weeks", vec![("4weeks", Type::Duration)]); + } + + #[test] + fn test_pt23h() { + lexer_test("PT23H", vec![("PT23H", Type::Duration)]); + } + + #[test] + fn test_1second() { + lexer_test("1second", vec![("1second", Type::Duration)]); + } + + #[test] + fn test_1s() { + lexer_test("1s", vec![("1s", Type::Duration)]); + } + + #[test] + fn test_1minute() { + lexer_test("1minute", vec![("1minute", Type::Duration)]); + } + + #[test] + fn test_2hour() { + lexer_test("2hour", vec![("2hour", Type::Duration)]); + } + + #[test] + fn test_3_days() { + lexer_test("3 days", vec![("3 days", Type::Duration)]); + } + + #[test] + fn test_4w() { + lexer_test("4w", vec![("4w", Type::Duration)]); + } + + #[test] + fn test_5mo() { + lexer_test("5mo", vec![("5mo", Type::Duration)]); + } + + #[test] + fn test_6_years() { + lexer_test("6 years", vec![("6 years", Type::Duration)]); + } + + #[test] + fn test_p1y() { + lexer_test("P1Y", vec![("P1Y", Type::Duration)]); + } + + #[test] + fn test_pt1h() { + lexer_test("PT1H", vec![("PT1H", Type::Duration)]); + } + + #[test] + fn test_p_full() { + lexer_test("P1Y1M1DT1H1M1S", vec![("P1Y1M1DT1H1M1S", Type::Duration)]); + } + + // Misc + // + #[test] + fn test_separator() { + lexer_test("--", vec![("--", Type::Separator)]); + } + + #[test] + fn test_separator_ws() { + lexer_test(" -- ", vec![("--", Type::Separator)]); + } + + #[test] + fn test_separator_boundaries() { + lexer_test( + "123--123 ", + vec![ + ("123", Type::Number), + ("--", Type::Separator), + ("123", Type::Number), + ], + ); + } + + // Expression + // due:eom-2w + // due < eom + 1w + 1d + // ( /pattern/ or 8ad2e3db-914d-4832-b0e6-72fa04f6e331,3b6218f9-726a-44fc-aa63-889ff52be442 ) + // + #[test] + fn test_expression() { + lexer_test( + "(1+2)", + vec![ + ("(", Type::Op), + ("1", Type::Number), + ("+", Type::Op), + ("2", Type::Number), + (")", Type::Op), + ], + ); + } + + #[test] + fn test_expression_dom_tilde() { + lexer_test( + "description~pattern", + vec![ + ("description", Type::DOM), + ("~", Type::Op), + ("pattern", Type::Identifier), + ], + ); + } + + #[test] + fn test_expression_paren_tag() { + lexer_test( + "(+tag)", + vec![("(", Type::Op), ("+tag", Type::Tag), (")", Type::Op)], + ); + } + + #[test] + fn test_expression_paren_name_value() { + lexer_test( + "(name:value)", + vec![("(", Type::Op), ("name:value", Type::Pair), (")", Type::Op)], + ); + } + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 000000000..a5464ca53 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1 @@ +mod lexer; diff --git a/src/lib.rs b/src/lib.rs index 475ea1d6c..c84a35f98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ #[macro_use] extern crate failure; +mod cli; mod errors; mod operation; mod replica; @@ -13,6 +14,7 @@ mod task; mod taskdb; pub mod taskstorage; mod tdb2; +mod util; pub use operation::Operation; pub use replica::Replica; diff --git a/src/tdb2/ff4.rs b/src/tdb2/ff4.rs index fd554ed6e..ed64f1a94 100644 --- a/src/tdb2/ff4.rs +++ b/src/tdb2/ff4.rs @@ -1,7 +1,7 @@ use std::str; -use super::pig::Pig; use crate::task::{Task, TaskBuilder}; +use crate::util::pig::Pig; use failure::Fallible; /// Rust implementation of part of utf8_codepoint from Taskwarrior's src/utf8.cpp diff --git a/src/tdb2/mod.rs b/src/tdb2/mod.rs index 7b39d986c..009b741e9 100644 --- a/src/tdb2/mod.rs +++ b/src/tdb2/mod.rs @@ -2,7 +2,6 @@ //! support for the data structure as a compatibility layer. mod ff4; -mod pig; use self::ff4::parse_ff4; use crate::task::Task; diff --git a/src/util/datetime.rs b/src/util/datetime.rs new file mode 100644 index 000000000..ff7ca0340 --- /dev/null +++ b/src/util/datetime.rs @@ -0,0 +1,39 @@ +//! A re-implementation of the "Datetime" parsing utility from the Taskwarrior +//! source. + +// TODO: this module is not yet implemented + +pub(crate) struct DateTime {} + +impl DateTime { + /// Parse a datestamp from a prefix of input and return the number of bytes consumed in the + /// input + pub(crate) fn parse>( + input: S, + format: &'static str, + ) -> Option<(DateTime, usize)> { + let input = input.as_ref(); + let mut len = input.len(); + + // try parsing the whole string and repeatedly drop suffixes until a match + while len > 0 { + if let Some(str) = input.get(..len) { + match str { + "2015" => return Some((DateTime {}, len)), + "2015-" => return Some((DateTime {}, len)), + "9th" => return Some((DateTime {}, len)), + "10th" => return Some((DateTime {}, len)), + "2015-W01" => return Some((DateTime {}, len)), + "2015-02-17" => return Some((DateTime {}, len)), + "2013-11-29T22:58:00Z" => return Some((DateTime {}, len)), + "315532800" => return Some((DateTime {}, len)), + "20131129T225800Z" => return Some((DateTime {}, len)), + "today" => return Some((DateTime {}, len)), + _ => (), + } + } + len -= 1; + } + None + } +} diff --git a/src/util/duration.rs b/src/util/duration.rs new file mode 100644 index 000000000..e7ec7fb32 --- /dev/null +++ b/src/util/duration.rs @@ -0,0 +1,44 @@ +//! A re-implementation of the "Duration" parsing utility from the Taskwarrior +//! source. + +// TODO: this module is not yet implemented + +pub(crate) struct Duration {} + +impl Duration { + /// Parse a duration from a prefix of input and return the number of bytes consumed in the + /// input + pub(crate) fn parse>( + input: S, + format: &'static str, + ) -> Option<(Duration, usize)> { + let input = input.as_ref(); + let mut len = input.len(); + + // try parsing the whole string and repeatedly drop suffixes until a match + while len > 0 { + if let Some(str) = input.get(..len) { + match str { + "1w" => return Some((Duration {}, len)), + "4w" => return Some((Duration {}, len)), + "4weeks" => return Some((Duration {}, len)), + "5mo" => return Some((Duration {}, len)), + "6 years" => return Some((Duration {}, len)), + "3 days" => return Some((Duration {}, len)), + "1minute" => return Some((Duration {}, len)), + "2hour" => return Some((Duration {}, len)), + "1s" => return Some((Duration {}, len)), + "1second" => return Some((Duration {}, len)), + "PT23H" => return Some((Duration {}, len)), + "PT1H" => return Some((Duration {}, len)), + "P1Y" => return Some((Duration {}, len)), + "P1Y1M1DT1H1M1S" => return Some((Duration {}, len)), + "year" => return Some((Duration {}, len)), + _ => (), + } + } + len -= 1; + } + None + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 000000000..efbbc7fb5 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod datetime; +pub(crate) mod duration; +pub(crate) mod pig; diff --git a/src/tdb2/pig.rs b/src/util/pig.rs similarity index 99% rename from src/tdb2/pig.rs rename to src/util/pig.rs index a3d837629..08a883639 100644 --- a/src/tdb2/pig.rs +++ b/src/util/pig.rs @@ -3,7 +3,7 @@ use failure::Fallible; -pub struct Pig<'a> { +pub(crate) struct Pig<'a> { input: &'a [u8], cursor: usize, } From ae93d91b6ed80ae4ba2c0daf05b6ba9fac50a217 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 2 Feb 2020 18:46:27 -0500 Subject: [PATCH 042/548] more CLI compatibility WIP --- src/bin/task.rs | 6 +- src/cli/a2.rs | 273 +++++++++++++++++++++++++++++ src/cli/cli2.rs | 345 +++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 3 +- src/{cli => util}/lexer.rs | 252 ++++++++++++++++++--------- src/util/mod.rs | 1 + 6 files changed, 798 insertions(+), 82 deletions(-) create mode 100644 src/cli/a2.rs create mode 100644 src/cli/cli2.rs rename src/{cli => util}/lexer.rs (94%) diff --git a/src/bin/task.rs b/src/bin/task.rs index 8149374d5..c2affafda 100644 --- a/src/bin/task.rs +++ b/src/bin/task.rs @@ -11,8 +11,8 @@ fn main() { .about("Replacement for TaskWarrior") .subcommand( SubCommand::with_name("add").about("adds a task").arg( - Arg::with_name("descrpition") - .help("task descrpition") + Arg::with_name("description") + .help("task description") .required(true), ), ) @@ -35,7 +35,7 @@ fn main() { .new_task( uuid, Status::Pending, - matches.value_of("descrpition").unwrap().into(), + matches.value_of("description").unwrap().into(), ) .unwrap(); } diff --git a/src/cli/a2.rs b/src/cli/a2.rs new file mode 100644 index 000000000..0d39cd1a9 --- /dev/null +++ b/src/cli/a2.rs @@ -0,0 +1,273 @@ +//! Re-implementation of TaskWarrior's A2 module. + +use crate::util::lexer::*; +use std::collections::{HashMap, HashSet}; +use std::fmt; + +/// A2 represents a single argument. +#[derive(Clone)] +pub(crate) struct A2 { + pub(crate) lextype: Type, + tags: HashSet, + attributes: HashMap, +} + +impl A2 { + pub(crate) fn new>(raw: S, lextype: Type) -> A2 { + let mut attributes = HashMap::new(); + attributes.insert("raw".into(), raw.into()); + let mut rv = A2 { + lextype, + tags: HashSet::new(), + attributes, + }; + rv.decompose(); + rv + } + + /// Return true if the given tag exists in this argument. + pub(crate) fn has_tag>(&self, tag: S) -> bool { + self.tags.contains(tag.as_ref()) + } + + /// Add the given tag to this argument. + pub(crate) fn tag>(&mut self, tag: S) { + self.tags.insert(tag.into()); + } + + /// Remove the given tag from this argument. + pub(crate) fn untag>(&mut self, tag: S) { + self.tags.remove(tag.as_ref()); + } + + /// Set the given attribute + pub(crate) fn set_attribute, S2: Into>( + &mut self, + name: S1, + value: S2, + ) { + self.attributes.insert(name.into(), value.into()); + } + + /// Get the given attribute + pub(crate) fn get_attribute>(&self, name: S) -> Option<&str> { + self.attributes.get(name.as_ref()).map(|s| s.as_ref()) + } + + /// Get either the canonical or raw form (attribute) + pub(crate) fn get_token(&self) -> &str { + self.attributes + .get("canonical") + .or_else(|| self.attributes.get("raw")) + .unwrap() + .as_ref() + } + + /// Decompose the raw form into tags and attributes based on the lextype: + /// + /// * Tag - + /// - "name" is the tag name + /// - "sign" is the sign (`+` or `-`) + /// * Substitution + /// - "from" is the first part + /// - "to" is the second part + /// - "flags' is the substitution flag, or empty string + /// * Pair + /// - "name" + /// - "modifier" + /// - "separator" + /// - "value" are the parts of the pair (a pair has four parts..?) + /// - tag "RC" is set if the name is "rc" with no modifier + /// - tag "CONFIG" is set if the name is "rc" with a monitor + /// * Pattern + /// - "pattern" is the pattern value + /// - "flags" is the pattern flag, or empty string + /// + /// all other types are left unchanged + pub(crate) fn decompose(&mut self) { + let raw = self.get_attribute("raw").unwrap(); + match self.lextype { + Type::Tag => { + let (sign, name) = (raw[..1].to_string(), raw[1..].to_string()); + self.set_attribute("sign", sign); + self.set_attribute("name", name); + } + Type::Substitution => { + let DecomposedSubstitution { from, to, flags } = + decompose_substitution(raw).unwrap(); + self.set_attribute("from", from); + self.set_attribute("to", to); + self.set_attribute("flags", flags); + } + Type::Pair => { + let DecomposedPair { + name, + modifier, + separator, + value, + } = decompose_pair(raw).unwrap(); + + if &name == "rc" { + if &modifier != "" { + self.tag("CONFIG"); + } else { + self.tag("RC"); + } + } + + self.set_attribute("name", name); + self.set_attribute("modifier", modifier); + self.set_attribute("separator", separator); + self.set_attribute("value", value); + } + Type::Pattern => { + let DecomposedPattern { pattern, flags } = decompose_pattern(raw).unwrap(); + + self.set_attribute("pattern", pattern); + self.set_attribute("flags", flags); + } + _ => (), + } + } +} + +impl fmt::Debug for A2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "A2{}{:?}", "{", self.lextype)?; + let mut tags = self.tags.iter().collect::>(); + tags.sort(); + for tag in tags { + write!(f, ", {}", tag)?; + } + let mut attributes = self.attributes.iter().collect::>(); + attributes.sort(); + for (name, value) in attributes { + write!(f, ", {}={:?}", name, value)?; + } + write!(f, "{}", "}")?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn tags() { + let mut a2 = A2::new("ident", Type::Identifier); + assert!(!a2.has_tag("foo")); + a2.tag("foo"); + assert!(a2.has_tag("foo")); + a2.untag("foo"); + assert!(!a2.has_tag("foo")); + } + + #[test] + fn raw_attribute() { + let a2 = A2::new("ident", Type::Identifier); + assert_eq!(a2.get_attribute("raw"), Some("ident")); + } + + #[test] + fn set_get_attribute() { + let mut a2 = A2::new("ident", Type::Identifier); + assert_eq!(a2.get_attribute("foo"), None); + a2.set_attribute("foo", "bar"); + assert_eq!(a2.get_attribute("foo"), Some("bar")); + a2.set_attribute("foo", "bing"); + assert_eq!(a2.get_attribute("foo"), Some("bing")); + } + + #[test] + fn get_token_raw() { + let a2 = A2::new("ident", Type::Identifier); + assert_eq!(a2.get_token(), "ident"); + } + + #[test] + fn get_token_canonical() { + let mut a2 = A2::new("ident", Type::Identifier); + a2.set_attribute("canonical", "identifier"); + assert_eq!(a2.get_token(), "identifier"); + } + + #[test] + fn decompose_tag() { + let mut a2 = A2::new("+foo", Type::Tag); + a2.decompose(); + assert_eq!(a2.get_attribute("sign"), Some("+")); + assert_eq!(a2.get_attribute("name"), Some("foo")); + } + + #[test] + fn decompose_substitution() { + let mut a2 = A2::new("/foo/bar/g", Type::Substitution); + a2.decompose(); + assert_eq!(a2.get_attribute("from"), Some("foo")); + assert_eq!(a2.get_attribute("to"), Some("bar")); + assert_eq!(a2.get_attribute("flags"), Some("g")); + } + + #[test] + fn decompose_pair() { + let mut a2 = A2::new("thing.foo:bar", Type::Pair); + a2.decompose(); + assert_eq!(a2.get_attribute("name"), Some("thing")); + assert_eq!(a2.get_attribute("modifier"), Some("foo")); + assert_eq!(a2.get_attribute("separator"), Some(":")); + assert_eq!(a2.get_attribute("value"), Some("bar")); + assert!(!a2.has_tag("RC")); + assert!(!a2.has_tag("CONFIG")); + } + + #[test] + fn decompose_pair_rc() { + let mut a2 = A2::new("rc:bar", Type::Pair); + a2.decompose(); + assert_eq!(a2.get_attribute("name"), Some("rc")); + assert_eq!(a2.get_attribute("modifier"), Some("")); + assert_eq!(a2.get_attribute("separator"), Some(":")); + assert_eq!(a2.get_attribute("value"), Some("bar")); + assert!(a2.has_tag("RC")); + assert!(!a2.has_tag("CONFIG")); + } + + #[test] + fn decompose_pair_config() { + let mut a2 = A2::new("rc.foo:bar", Type::Pair); + a2.decompose(); + assert_eq!(a2.get_attribute("name"), Some("rc")); + assert_eq!(a2.get_attribute("modifier"), Some("foo")); + assert_eq!(a2.get_attribute("separator"), Some(":")); + assert_eq!(a2.get_attribute("value"), Some("bar")); + assert!(!a2.has_tag("RC")); + assert!(a2.has_tag("CONFIG")); + } + + #[test] + fn decompose_pattern() { + let mut a2 = A2::new("/foobar/g", Type::Pattern); + a2.decompose(); + assert_eq!(a2.get_attribute("pattern"), Some("foobar")); + assert_eq!(a2.get_attribute("flags"), Some("g")); + } + + #[test] + fn decompose_other() { + let mut a2 = A2::new("123", Type::Number); + a2.decompose(); + assert_eq!(a2.get_attribute("raw"), Some("123")); + } + + #[test] + fn debug() { + let mut a2 = A2::new("/ab/g", Type::Pattern); + a2.decompose(); + a2.tag("FOO"); + assert_eq!( + format!("{:?}", a2), + "A2{Pattern, FOO, flags=\"g\", pattern=\"ab\", raw=\"/ab/g\"}" + ); + } +} diff --git a/src/cli/cli2.rs b/src/cli/cli2.rs new file mode 100644 index 000000000..2415abace --- /dev/null +++ b/src/cli/cli2.rs @@ -0,0 +1,345 @@ +//! Reimplementation of the CLI2 class in TaskWarrior. +//! +//! This class is sparsely tested in TaskWarrior, but the intent is to replicate its functionality +//! reliably enough that any command-line accepted by TaskWarrior will also be accepted by this +//! implementation. + +use super::a2::A2; +use crate::util::lexer::{dequote, read_word_quoted, was_quoted, Lexer, Type}; +use std::collections::{HashMap, HashSet}; + +#[derive(Default)] +pub(crate) struct CLI2 { + entities: HashMap>, + aliases: HashMap, + original_args: Vec, + args: Vec, + id_ranges: Vec<(String, String)>, + uuid_list: Vec, + context_filter_added: bool, +} + +impl CLI2 { + pub(crate) fn new() -> CLI2 { + CLI2 { + ..Default::default() + } + } + + /// Add an alias + pub(crate) fn alias, S2: Into>(&mut self, name: S1, value: S2) { + self.aliases.insert(name.into(), value.into()); + } + + /// Add an entity category thing ?? + pub(crate) fn entity, S2: Into>(&mut self, category: S1, name: S2) { + self.entities + .entry(category.into()) + .or_insert_with(|| HashSet::new()) + .insert(name.into()); + } + + /// Capture a single argument, tagged as ORIGINAL + pub(crate) fn add>(&mut self, argument: S) { + let mut arg = A2::new(argument, Type::Word); + arg.tag("ORIGINAL"); + self.original_args.push(arg); + self.args.clear(); + } + + /// Capture a set of arguments, inserted immediately after the binary. + /// There must be at least one argument set already. The new args are not + /// tagged as ORIGINAL. + /// + /// Note that this is in no way equivalent to calling `add` in a loop! + pub(crate) fn add_args>(&mut self, arguments: Vec) { + let mut replacement = vec![self.original_args[0].clone()]; + for arg in arguments { + replacement.push(A2::new(arg, Type::Word)); + } + for arg in self.original_args.drain(1..) { + replacement.push(arg); + } + self.original_args = replacement; + self.args.clear(); + } + + /// Perform the command-line analysis after arguments are added with `add` and `add_args`. + pub(crate) fn analyze(&mut self) { + self.args.clear(); + self.handle_arg0(); + self.lex_arguments(); + // self.alias_expansion(); - TODO + if !self.find_command() { + self.default_command(); + assert!(self.find_command()); // default_command guarantees this + } + // self.demotion(); - TODO + // self.canonicalizeNames(); - TODO + // self.categorizeArgs(); - TODO + // self.parenthesizeOriginalFilter(); - TODO + } + + /// Handle the first argument, indicating the invoked binary. + fn handle_arg0(&mut self) { + // NOTE: this omits the special handling for "cal" and "calendar" + self.original_args[0].tag("BINARY"); + } + + /// Use the lexer to process all arguments (except the first, handled by handle_arg0). + /// + /// All arguments must be individually and wholly recognized by the Lexer. Any argument not + /// recognized is considered a lexer::Type::Word. + /// + /// As a side effect, tags all arguments after a terminator ('--') with TERMINATED. + fn lex_arguments(&mut self) { + let mut terminated = false; + + // Note: Starts iterating at index 1, because ::handleArg0 has already + // processed it. + for arg in &self.original_args[1..] { + let raw = arg.get_attribute("raw").unwrap(); + let quoted = was_quoted(raw); + + // Process single-token arguments. + let mut lex = Lexer::new(raw); + match lex.token() { + // if we got a token and it goes to EOS (quoted pairs automatically go to EOS) + Some((lexeme, mut lextype)) + if lex.is_eos() || (quoted && lextype == Type::Pair) => + { + if !terminated && lextype == Type::Separator { + terminated = true; + } else if terminated { + lextype = Type::Word; + } + + let mut lexed_arg = A2::new(raw, lextype); + if terminated { + lexed_arg.tag("TERMINATED"); + } + if quoted { + lexed_arg.tag("QUOTED"); + } + if arg.has_tag("ORIGINAL") { + lexed_arg.tag("ORIGINAL"); + } + self.args.push(lexed_arg) + } + // ..otherwise, process "muktiple-token" arguments + _ => { + // TODO: this is kind of insane and almost certainly wrong, but + // implements what the C++ code does.. + let quote = "'"; + let escaped = format!("'{}'", raw.replace(quote, "\\'")); + let mut lexed_arg; + if let Some((word, _)) = read_word_quoted(&escaped, quote, 0) { + let word = dequote(&word, "'\""); + lexed_arg = A2::new(word, Type::Word); + } else { + // "This branch may have no use-case"! + lexed_arg = A2::new(raw, Type::Word); + lexed_arg.tag("UNKNOWN"); + } + if quoted { + lexed_arg.tag("QUOTED"); + } + if arg.has_tag("ORIGINAL") { + lexed_arg.tag("ORIGINAL"); + } + self.args.push(lexed_arg) + } + } + } + /* + println!("lexed args:"); + for arg in &self.args { + println!("{:?}", arg); + } + */ + } + + /// Scan all arguments and if any are an exact match for a command name, then tag as CMD. If an + /// argument is an exact match for an attribute, despite being an inexact match for a command, + /// then it is not a command. + fn find_command(&mut self) -> bool { + for (i, arg) in self.args.iter().enumerate() { + let raw = arg.get_attribute("raw").unwrap(); + let canonical; + + if self.exact_match_entity("cmd", raw) { + canonical = raw.into(); + } else if self.exact_match_entity("attribute", raw) { + continue; + } else if let Some(cannon) = self.canonicalize_entity("cmd", raw) { + canonical = cannon; + } else { + continue; + } + + let mut arg = arg.clone(); + arg.set_attribute("canonical", canonical); + arg.tag("CMD"); + + // TODO: apply "command DNA" + + self.args[i] = arg; + + return true; + } + + false + } + + /// Set a default command argument. Look for situations that require defaults: + /// + /// 1. If no command was found, and no ID/UUID, and if rc.default.command is + /// configured, inject the lexed tokens from rc.default.command. + /// + /// 2. If no command was found, but an ID/UUID was found, then assume a command + /// of 'information'. + fn default_command(&mut self) { + let mut found_command = false; + let mut found_sequence = false; + + for arg in &self.args { + if arg.has_tag("CMD") { + found_command = true; + } + if arg.lextype == Type::Uuid || arg.lextype == Type::Number { + found_sequence = true; + } + } + + if !found_command { + if !found_sequence { + unreachable!(); // TODO (requires default.command, context, etc.) + } else { + let mut info = A2::new("information", Type::Word); + info.tag("ASSUMED"); + info.tag("CMD"); + self.args.insert(0, info); + } + } + } + + /// Search for 'value' in _entities category, return canonicalized value. + fn canonicalize_entity(&self, category: &str, value: &str) -> Option { + // TODO: for the moment this only accepts exact matches + if let Some(names) = self.entities.get(category) { + if names.contains(value) { + Some(value.into()) + } else { + None + } + } else { + None + } + } + + /// Search for exact 'value' in _entities category. + fn exact_match_entity(&self, category: &str, value: &str) -> bool { + if let Some(names) = self.entities.get(category) { + names.contains(value) + } else { + false + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn assert_args(args: &Vec, exp: Vec<&str>) { + assert_eq!( + args.iter().map(|a| format!("{:?}", a)).collect::>(), + exp.iter().map(|s| s.to_string()).collect::>(), + ); + } + + #[test] + fn alias() { + let mut c = CLI2::new(); + c.alias("foo", "bar"); + assert_eq!(c.aliases.get("foo"), Some(&"bar".to_string())); + } + + #[test] + fn entities() { + let mut c = CLI2::new(); + c.entity("cat", "foo"); + c.entity("cat", "bar"); + let mut exp = HashSet::new(); + exp.insert("foo".to_string()); + exp.insert("bar".to_string()); + assert_eq!(c.entities.get("cat"), Some(&exp)); + } + + #[test] + fn add() { + let mut c = CLI2::new(); + c.add("foo"); + c.add("bar"); + assert_eq!( + c.original_args + .iter() + .map(|a| format!("{:?}", a)) + .collect::>(), + vec![ + "A2{Word, ORIGINAL, raw=\"foo\"}", + "A2{Word, ORIGINAL, raw=\"bar\"}" + ] + ); + } + + #[test] + fn add_args() { + let mut c = CLI2::new(); + c.add("0"); + c.add("1"); + c.add("2"); + c.add_args(vec!["foo", "bar"]); + assert_args( + &c.original_args, + vec![ + "A2{Word, ORIGINAL, raw=\"0\"}", + "A2{Word, raw=\"foo\"}", + "A2{Word, raw=\"bar\"}", + "A2{Word, ORIGINAL, raw=\"1\"}", + "A2{Word, ORIGINAL, raw=\"2\"}", + ], + ); + } + + #[test] + fn analyze_example_cmdline() { + let mut c = CLI2::new(); + c.entity("cmd", "next"); + c.add("arg0"); + c.add("rc.gc=0"); + c.add("next"); + c.add("+PENDING"); + c.add("due:tomorrow"); + c.analyze(); + assert_args( + &c.args, + vec![ + "A2{Pair, CONFIG, ORIGINAL, modifier=\"gc\", name=\"rc\", raw=\"rc.gc=0\", separator=\"=\", value=\"0\"}", + "A2{Identifier, CMD, ORIGINAL, canonical=\"next\", raw=\"next\"}", + "A2{Tag, ORIGINAL, name=\"PENDING\", raw=\"+PENDING\", sign=\"+\"}", + "A2{Pair, ORIGINAL, modifier=\"\", name=\"due\", raw=\"due:tomorrow\", separator=\":\", value=\"tomorrow\"}", + ], + ); + } + + #[test] + fn exact_match_entity() { + let mut c = CLI2::new(); + c.entity("cmd", "next"); + c.entity("cmd", "list"); + assert!(c.exact_match_entity("cmd", "next")); + assert!(!c.exact_match_entity("cmd", "bar")); + assert!(!c.exact_match_entity("foo", "bar")); + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a5464ca53..f7b7486d5 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1 +1,2 @@ -mod lexer; +mod a2; +mod cli2; diff --git a/src/cli/lexer.rs b/src/util/lexer.rs similarity index 94% rename from src/cli/lexer.rs rename to src/util/lexer.rs index 9cc9bb237..1e04218dd 100644 --- a/src/cli/lexer.rs +++ b/src/util/lexer.rs @@ -1,9 +1,12 @@ +//! A re-implementation of TaskWarrior's Lexer. +//! +//! This is tested to pass that module's tests, and includes some additional tests that were also +//! verified against that module. + use crate::util::datetime::DateTime; use crate::util::duration::Duration; use std::convert::TryFrom; -// based on src/Lexer.{h,cpp} in the Taskwarrior code - const UUID_PATTERN: &[u8] = b"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; const UUID_MIN_LENGTH: usize = 8; const MINIMUM_MATCH_LEN: usize = 3; @@ -12,7 +15,7 @@ const DATE_SUBELEMENTS: &[&str] = &[ ]; #[derive(PartialEq, Debug, Clone, Copy)] -enum Type { +pub(crate) enum Type { Uuid, Number, Hex, @@ -33,7 +36,7 @@ enum Type { Duration, } -struct Lexer { +pub(crate) struct Lexer { text: String, cursor: usize, eos: usize, @@ -151,11 +154,6 @@ fn is_hard_boundary(left: char, right: char) -> bool { right == '\0' || left == '(' || left == ')' || right == '(' || right == ')' } -/// Returns true if the given string must have been shell-quoted -fn was_quoted(s: &str) -> bool { - s.contains(&[' ', '\t', '(', ')', '<', '>', '&', '~'][..]) -} - fn is_unicode_hex_digit(c: char) -> bool { match c { '0'..='9' | 'a'..='f' | 'A'..='F' => true, @@ -184,7 +182,7 @@ fn hex_to_char(hex: &str) -> Option { /// Strips matching quote symbols from the beginning and end of the given string /// (removing all quotes if given a single quote `'`) -fn dequote<'a, 'b>(s: &'a str, quotes: &'b str) -> &'a str { +pub(crate) fn dequote<'a, 'b>(s: &'a str, quotes: &'b str) -> &'a str { // note that this returns a new ref to the same string, rather // than modifying its argument as the C++ version does. if let Some(first_char) = s.chars().next() { @@ -202,7 +200,7 @@ fn dequote<'a, 'b>(s: &'a str, quotes: &'b str) -> &'a str { s } -fn read_word_quoted(text: &str, quotes: &str, cursor: usize) -> Option<(String, usize)> { +pub(crate) fn read_word_quoted(text: &str, quotes: &str, cursor: usize) -> Option<(String, usize)> { let mut pos = cursor; let mut res = String::new(); let mut skipchars = 0; @@ -281,7 +279,7 @@ fn read_word_quoted(text: &str, quotes: &str, cursor: usize) -> Option<(String, None } -fn read_word_unquoted(text: &str, cursor: usize) -> Option<(String, usize)> { +pub(crate) fn read_word_unquoted(text: &str, cursor: usize) -> Option<(String, usize)> { let mut pos = cursor; let mut res = String::new(); let mut prev = None; @@ -365,12 +363,118 @@ fn common_length(s1: &str, s2: &str) -> usize { .len() } +/// Returns true if the given string must have been shell-quoted +pub(crate) fn was_quoted(s: &str) -> bool { + s.contains(&[' ', '\t', '(', ')', '<', '>', '&', '~'][..]) +} + #[derive(Debug, PartialEq)] -pub struct DecomposedPair { - name: String, - modifier: String, - separator: String, - value: String, +pub(crate) struct DecomposedPair { + pub(crate) name: String, + pub(crate) modifier: String, + pub(crate) separator: String, + pub(crate) value: String, +} + +/// Parse ("decompose") a pair into its constituent parts. This assumes the text is a valid pair +/// string. +pub(crate) fn decompose_pair(text: &str) -> Option { + let npos = usize::max_value(); + let dot = text.find(".").unwrap_or(npos); + let sep_defer = text.find("::").unwrap_or(npos); + let sep_eval = text.find(":=").unwrap_or(npos); + let sep_colon = text.find(":").unwrap_or(npos); + let sep_equal = text.find("=").unwrap_or(npos); + + let (sep, sep_end) = if sep_defer != npos + && sep_defer <= sep_eval + && sep_defer <= sep_colon + && sep_defer <= sep_equal + { + (sep_defer, sep_defer + 2) + } else if sep_eval != npos + && sep_eval <= sep_defer + && sep_eval <= sep_colon + && sep_eval <= sep_equal + { + (sep_eval, sep_eval + 2) + } else if sep_colon != npos + && sep_colon <= sep_defer + && sep_colon <= sep_eval + && sep_colon <= sep_equal + { + (sep_colon, sep_colon + 1) + } else if sep_equal != npos + && sep_equal <= sep_defer + && sep_equal <= sep_eval + && sep_equal <= sep_colon + { + (sep_equal, sep_equal + 1) + } else { + return None; + }; + + let (name, modifier) = if dot != npos && dot < sep { + ( + text.get(0..dot).unwrap().into(), + text.get(dot + 1..sep).unwrap().into(), + ) + } else { + (text.get(0..sep).unwrap().into(), "".into()) + }; + + let separator = text.get(sep..sep_end).unwrap().into(); + let value = text.get(sep_end..).unwrap().into(); + + Some(DecomposedPair { + name, + modifier, + separator, + value, + }) +} + +#[derive(Debug, PartialEq)] +pub(crate) struct DecomposedSubstitution { + pub(crate) from: String, + pub(crate) to: String, + pub(crate) flags: String, +} + +/// Parse ("decompose") a substitution into its constituent parts. This assumes +/// the text is a valid substitution string. +pub(crate) fn decompose_substitution(text: &str) -> Option { + let mut cursor = 0; + if let Some((from, from_curs)) = read_word_quoted(text, "/", cursor) { + cursor = from_curs - 1; + if let Some((to, to_curs)) = read_word_quoted(text, "/", cursor) { + cursor = to_curs; + let from = dequote(&from, "/").into(); + let to = dequote(&to, "/").into(); + let flags = text[cursor..].into(); + return Some(DecomposedSubstitution { from, to, flags }); + } + } + None +} + +#[derive(Debug, PartialEq)] +pub(crate) struct DecomposedPattern { + pub(crate) pattern: String, + pub(crate) flags: String, +} + +/// Parse ("decompose") a pattern into its constituent parts. This assumes the text is a valid +/// pattern string. +pub(crate) fn decompose_pattern(text: &str) -> Option { + let mut cursor = 0; + if let Some((pattern, pattern_curs)) = read_word_quoted(text, "/", cursor) { + cursor = pattern_curs; + let pattern = dequote(&pattern, "/").into(); + let flags = text[cursor..].into(); + return Some(DecomposedPattern { pattern, flags }); + } + None } impl Lexer { @@ -475,65 +579,8 @@ impl Lexer { None } - pub fn decompose_pair(text: &str) -> Option { - let npos = usize::max_value(); - // npos - let dot = text.find(".").unwrap_or(npos); - // npos - let sep_defer = text.find("::").unwrap_or(npos); - // npos - let sep_eval = text.find(":=").unwrap_or(npos); - // 4 - let sep_colon = text.find(":").unwrap_or(npos); - // npos - let sep_equal = text.find("=").unwrap_or(npos); - - let (sep, sep_end) = if sep_defer != npos - && sep_defer <= sep_eval - && sep_defer <= sep_colon - && sep_defer <= sep_equal - { - (sep_defer, sep_defer + 2) - } else if sep_eval != npos - && sep_eval <= sep_defer - && sep_eval <= sep_colon - && sep_eval <= sep_equal - { - (sep_eval, sep_eval + 2) - } else if sep_colon != npos - && sep_colon <= sep_defer - && sep_colon <= sep_eval - && sep_colon <= sep_equal - { - (sep_colon, sep_colon + 1) - } else if sep_equal != npos - && sep_equal <= sep_defer - && sep_equal <= sep_eval - && sep_equal <= sep_colon - { - (sep_equal, sep_equal + 1) - } else { - return None; - }; - - let (name, modifier) = if dot != npos && dot < sep { - ( - text.get(0..dot).unwrap().into(), - text.get(dot + 1..sep).unwrap().into(), - ) - } else { - (text.get(0..sep).unwrap().into(), "".into()) - }; - - let separator = text.get(sep..sep_end).unwrap().into(); - let value = text.get(sep_end..).unwrap().into(); - - Some(DecomposedPair { - name, - modifier, - separator, - value, - }) + pub fn is_eos(&self) -> bool { + self.cursor == self.eos } // recognizers for the `token` method @@ -1195,7 +1242,7 @@ impl Lexer { } } -struct LexerIterator(Lexer); +pub(crate) struct LexerIterator(Lexer); impl Iterator for LexerIterator { type Item = (String, Type); @@ -1319,6 +1366,7 @@ mod test { fn test_token_empty() { let mut l = Lexer::new(""); assert_eq!(l.token(), NONE); + assert!(l.is_eos()); } #[test] @@ -1326,6 +1374,7 @@ mod test { let mut l = Lexer::new( " one 'two \\'three\\''+456-(1.3*2 - 0x12) 1.2e-3.4 foo.bar and '\\u20ac'", ); + assert!(!l.is_eos()); assert_eq!(l.token(), Some((String::from("one"), Type::Identifier))); assert_eq!( l.token(), @@ -1346,6 +1395,7 @@ mod test { assert_eq!(l.token(), Some((String::from("and"), Type::Op))); assert_eq!(l.token(), Some((String::from("'€'"), Type::String))); assert_eq!(l.token(), None); + assert!(l.is_eos()); } #[test] @@ -2193,7 +2243,7 @@ mod test { value ); assert_eq!( - Lexer::decompose_pair(&input), + decompose_pair(&input), Some(DecomposedPair { name: name.into(), modifier: String::from(*modifier), @@ -2206,6 +2256,52 @@ mod test { } } + #[test] + fn decompose_substitution_no_flags() { + assert_eq!( + decompose_substitution("/a/b/"), + Some(DecomposedSubstitution { + from: "a".into(), + to: "b".into(), + flags: "".into(), + }) + ); + } + + #[test] + fn decompose_substitution_flags() { + assert_eq!( + decompose_substitution("/a/b/g"), + Some(DecomposedSubstitution { + from: "a".into(), + to: "b".into(), + flags: "g".into(), + }) + ); + } + + #[test] + fn decompose_pattern_no_flags() { + assert_eq!( + decompose_pattern("/foober/"), + Some(DecomposedPattern { + pattern: "foober".into(), + flags: "".into(), + }) + ); + } + + #[test] + fn decompose_pattern_flags() { + assert_eq!( + decompose_pattern("/foober/g"), + Some(DecomposedPattern { + pattern: "foober".into(), + flags: "g".into(), + }) + ); + } + #[test] fn test_is_one_of() { let mut l = Lexer::new("Grumpy."); diff --git a/src/util/mod.rs b/src/util/mod.rs index efbbc7fb5..a6465d5d7 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod datetime; pub(crate) mod duration; +pub(crate) mod lexer; pub(crate) mod pig; From 3ddaff07ca2d7c8f8e26fac65cd01d51e9727827 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 21 Nov 2020 17:52:09 -0500 Subject: [PATCH 043/548] remove taskwarrior compatibility stuff --- src/cli/a2.rs | 273 -------------------------------------- src/cli/cli2.rs | 345 ------------------------------------------------ src/cli/mod.rs | 2 - src/lib.rs | 10 -- src/tdb2/ff4.rs | 245 ---------------------------------- src/tdb2/mod.rs | 24 ---- tests/parse.rs | 20 --- 7 files changed, 919 deletions(-) delete mode 100644 src/cli/a2.rs delete mode 100644 src/cli/cli2.rs delete mode 100644 src/cli/mod.rs delete mode 100644 src/tdb2/ff4.rs delete mode 100644 src/tdb2/mod.rs delete mode 100644 tests/parse.rs diff --git a/src/cli/a2.rs b/src/cli/a2.rs deleted file mode 100644 index 0d39cd1a9..000000000 --- a/src/cli/a2.rs +++ /dev/null @@ -1,273 +0,0 @@ -//! Re-implementation of TaskWarrior's A2 module. - -use crate::util::lexer::*; -use std::collections::{HashMap, HashSet}; -use std::fmt; - -/// A2 represents a single argument. -#[derive(Clone)] -pub(crate) struct A2 { - pub(crate) lextype: Type, - tags: HashSet, - attributes: HashMap, -} - -impl A2 { - pub(crate) fn new>(raw: S, lextype: Type) -> A2 { - let mut attributes = HashMap::new(); - attributes.insert("raw".into(), raw.into()); - let mut rv = A2 { - lextype, - tags: HashSet::new(), - attributes, - }; - rv.decompose(); - rv - } - - /// Return true if the given tag exists in this argument. - pub(crate) fn has_tag>(&self, tag: S) -> bool { - self.tags.contains(tag.as_ref()) - } - - /// Add the given tag to this argument. - pub(crate) fn tag>(&mut self, tag: S) { - self.tags.insert(tag.into()); - } - - /// Remove the given tag from this argument. - pub(crate) fn untag>(&mut self, tag: S) { - self.tags.remove(tag.as_ref()); - } - - /// Set the given attribute - pub(crate) fn set_attribute, S2: Into>( - &mut self, - name: S1, - value: S2, - ) { - self.attributes.insert(name.into(), value.into()); - } - - /// Get the given attribute - pub(crate) fn get_attribute>(&self, name: S) -> Option<&str> { - self.attributes.get(name.as_ref()).map(|s| s.as_ref()) - } - - /// Get either the canonical or raw form (attribute) - pub(crate) fn get_token(&self) -> &str { - self.attributes - .get("canonical") - .or_else(|| self.attributes.get("raw")) - .unwrap() - .as_ref() - } - - /// Decompose the raw form into tags and attributes based on the lextype: - /// - /// * Tag - - /// - "name" is the tag name - /// - "sign" is the sign (`+` or `-`) - /// * Substitution - /// - "from" is the first part - /// - "to" is the second part - /// - "flags' is the substitution flag, or empty string - /// * Pair - /// - "name" - /// - "modifier" - /// - "separator" - /// - "value" are the parts of the pair (a pair has four parts..?) - /// - tag "RC" is set if the name is "rc" with no modifier - /// - tag "CONFIG" is set if the name is "rc" with a monitor - /// * Pattern - /// - "pattern" is the pattern value - /// - "flags" is the pattern flag, or empty string - /// - /// all other types are left unchanged - pub(crate) fn decompose(&mut self) { - let raw = self.get_attribute("raw").unwrap(); - match self.lextype { - Type::Tag => { - let (sign, name) = (raw[..1].to_string(), raw[1..].to_string()); - self.set_attribute("sign", sign); - self.set_attribute("name", name); - } - Type::Substitution => { - let DecomposedSubstitution { from, to, flags } = - decompose_substitution(raw).unwrap(); - self.set_attribute("from", from); - self.set_attribute("to", to); - self.set_attribute("flags", flags); - } - Type::Pair => { - let DecomposedPair { - name, - modifier, - separator, - value, - } = decompose_pair(raw).unwrap(); - - if &name == "rc" { - if &modifier != "" { - self.tag("CONFIG"); - } else { - self.tag("RC"); - } - } - - self.set_attribute("name", name); - self.set_attribute("modifier", modifier); - self.set_attribute("separator", separator); - self.set_attribute("value", value); - } - Type::Pattern => { - let DecomposedPattern { pattern, flags } = decompose_pattern(raw).unwrap(); - - self.set_attribute("pattern", pattern); - self.set_attribute("flags", flags); - } - _ => (), - } - } -} - -impl fmt::Debug for A2 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "A2{}{:?}", "{", self.lextype)?; - let mut tags = self.tags.iter().collect::>(); - tags.sort(); - for tag in tags { - write!(f, ", {}", tag)?; - } - let mut attributes = self.attributes.iter().collect::>(); - attributes.sort(); - for (name, value) in attributes { - write!(f, ", {}={:?}", name, value)?; - } - write!(f, "{}", "}")?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn tags() { - let mut a2 = A2::new("ident", Type::Identifier); - assert!(!a2.has_tag("foo")); - a2.tag("foo"); - assert!(a2.has_tag("foo")); - a2.untag("foo"); - assert!(!a2.has_tag("foo")); - } - - #[test] - fn raw_attribute() { - let a2 = A2::new("ident", Type::Identifier); - assert_eq!(a2.get_attribute("raw"), Some("ident")); - } - - #[test] - fn set_get_attribute() { - let mut a2 = A2::new("ident", Type::Identifier); - assert_eq!(a2.get_attribute("foo"), None); - a2.set_attribute("foo", "bar"); - assert_eq!(a2.get_attribute("foo"), Some("bar")); - a2.set_attribute("foo", "bing"); - assert_eq!(a2.get_attribute("foo"), Some("bing")); - } - - #[test] - fn get_token_raw() { - let a2 = A2::new("ident", Type::Identifier); - assert_eq!(a2.get_token(), "ident"); - } - - #[test] - fn get_token_canonical() { - let mut a2 = A2::new("ident", Type::Identifier); - a2.set_attribute("canonical", "identifier"); - assert_eq!(a2.get_token(), "identifier"); - } - - #[test] - fn decompose_tag() { - let mut a2 = A2::new("+foo", Type::Tag); - a2.decompose(); - assert_eq!(a2.get_attribute("sign"), Some("+")); - assert_eq!(a2.get_attribute("name"), Some("foo")); - } - - #[test] - fn decompose_substitution() { - let mut a2 = A2::new("/foo/bar/g", Type::Substitution); - a2.decompose(); - assert_eq!(a2.get_attribute("from"), Some("foo")); - assert_eq!(a2.get_attribute("to"), Some("bar")); - assert_eq!(a2.get_attribute("flags"), Some("g")); - } - - #[test] - fn decompose_pair() { - let mut a2 = A2::new("thing.foo:bar", Type::Pair); - a2.decompose(); - assert_eq!(a2.get_attribute("name"), Some("thing")); - assert_eq!(a2.get_attribute("modifier"), Some("foo")); - assert_eq!(a2.get_attribute("separator"), Some(":")); - assert_eq!(a2.get_attribute("value"), Some("bar")); - assert!(!a2.has_tag("RC")); - assert!(!a2.has_tag("CONFIG")); - } - - #[test] - fn decompose_pair_rc() { - let mut a2 = A2::new("rc:bar", Type::Pair); - a2.decompose(); - assert_eq!(a2.get_attribute("name"), Some("rc")); - assert_eq!(a2.get_attribute("modifier"), Some("")); - assert_eq!(a2.get_attribute("separator"), Some(":")); - assert_eq!(a2.get_attribute("value"), Some("bar")); - assert!(a2.has_tag("RC")); - assert!(!a2.has_tag("CONFIG")); - } - - #[test] - fn decompose_pair_config() { - let mut a2 = A2::new("rc.foo:bar", Type::Pair); - a2.decompose(); - assert_eq!(a2.get_attribute("name"), Some("rc")); - assert_eq!(a2.get_attribute("modifier"), Some("foo")); - assert_eq!(a2.get_attribute("separator"), Some(":")); - assert_eq!(a2.get_attribute("value"), Some("bar")); - assert!(!a2.has_tag("RC")); - assert!(a2.has_tag("CONFIG")); - } - - #[test] - fn decompose_pattern() { - let mut a2 = A2::new("/foobar/g", Type::Pattern); - a2.decompose(); - assert_eq!(a2.get_attribute("pattern"), Some("foobar")); - assert_eq!(a2.get_attribute("flags"), Some("g")); - } - - #[test] - fn decompose_other() { - let mut a2 = A2::new("123", Type::Number); - a2.decompose(); - assert_eq!(a2.get_attribute("raw"), Some("123")); - } - - #[test] - fn debug() { - let mut a2 = A2::new("/ab/g", Type::Pattern); - a2.decompose(); - a2.tag("FOO"); - assert_eq!( - format!("{:?}", a2), - "A2{Pattern, FOO, flags=\"g\", pattern=\"ab\", raw=\"/ab/g\"}" - ); - } -} diff --git a/src/cli/cli2.rs b/src/cli/cli2.rs deleted file mode 100644 index 2415abace..000000000 --- a/src/cli/cli2.rs +++ /dev/null @@ -1,345 +0,0 @@ -//! Reimplementation of the CLI2 class in TaskWarrior. -//! -//! This class is sparsely tested in TaskWarrior, but the intent is to replicate its functionality -//! reliably enough that any command-line accepted by TaskWarrior will also be accepted by this -//! implementation. - -use super::a2::A2; -use crate::util::lexer::{dequote, read_word_quoted, was_quoted, Lexer, Type}; -use std::collections::{HashMap, HashSet}; - -#[derive(Default)] -pub(crate) struct CLI2 { - entities: HashMap>, - aliases: HashMap, - original_args: Vec, - args: Vec, - id_ranges: Vec<(String, String)>, - uuid_list: Vec, - context_filter_added: bool, -} - -impl CLI2 { - pub(crate) fn new() -> CLI2 { - CLI2 { - ..Default::default() - } - } - - /// Add an alias - pub(crate) fn alias, S2: Into>(&mut self, name: S1, value: S2) { - self.aliases.insert(name.into(), value.into()); - } - - /// Add an entity category thing ?? - pub(crate) fn entity, S2: Into>(&mut self, category: S1, name: S2) { - self.entities - .entry(category.into()) - .or_insert_with(|| HashSet::new()) - .insert(name.into()); - } - - /// Capture a single argument, tagged as ORIGINAL - pub(crate) fn add>(&mut self, argument: S) { - let mut arg = A2::new(argument, Type::Word); - arg.tag("ORIGINAL"); - self.original_args.push(arg); - self.args.clear(); - } - - /// Capture a set of arguments, inserted immediately after the binary. - /// There must be at least one argument set already. The new args are not - /// tagged as ORIGINAL. - /// - /// Note that this is in no way equivalent to calling `add` in a loop! - pub(crate) fn add_args>(&mut self, arguments: Vec) { - let mut replacement = vec![self.original_args[0].clone()]; - for arg in arguments { - replacement.push(A2::new(arg, Type::Word)); - } - for arg in self.original_args.drain(1..) { - replacement.push(arg); - } - self.original_args = replacement; - self.args.clear(); - } - - /// Perform the command-line analysis after arguments are added with `add` and `add_args`. - pub(crate) fn analyze(&mut self) { - self.args.clear(); - self.handle_arg0(); - self.lex_arguments(); - // self.alias_expansion(); - TODO - if !self.find_command() { - self.default_command(); - assert!(self.find_command()); // default_command guarantees this - } - // self.demotion(); - TODO - // self.canonicalizeNames(); - TODO - // self.categorizeArgs(); - TODO - // self.parenthesizeOriginalFilter(); - TODO - } - - /// Handle the first argument, indicating the invoked binary. - fn handle_arg0(&mut self) { - // NOTE: this omits the special handling for "cal" and "calendar" - self.original_args[0].tag("BINARY"); - } - - /// Use the lexer to process all arguments (except the first, handled by handle_arg0). - /// - /// All arguments must be individually and wholly recognized by the Lexer. Any argument not - /// recognized is considered a lexer::Type::Word. - /// - /// As a side effect, tags all arguments after a terminator ('--') with TERMINATED. - fn lex_arguments(&mut self) { - let mut terminated = false; - - // Note: Starts iterating at index 1, because ::handleArg0 has already - // processed it. - for arg in &self.original_args[1..] { - let raw = arg.get_attribute("raw").unwrap(); - let quoted = was_quoted(raw); - - // Process single-token arguments. - let mut lex = Lexer::new(raw); - match lex.token() { - // if we got a token and it goes to EOS (quoted pairs automatically go to EOS) - Some((lexeme, mut lextype)) - if lex.is_eos() || (quoted && lextype == Type::Pair) => - { - if !terminated && lextype == Type::Separator { - terminated = true; - } else if terminated { - lextype = Type::Word; - } - - let mut lexed_arg = A2::new(raw, lextype); - if terminated { - lexed_arg.tag("TERMINATED"); - } - if quoted { - lexed_arg.tag("QUOTED"); - } - if arg.has_tag("ORIGINAL") { - lexed_arg.tag("ORIGINAL"); - } - self.args.push(lexed_arg) - } - // ..otherwise, process "muktiple-token" arguments - _ => { - // TODO: this is kind of insane and almost certainly wrong, but - // implements what the C++ code does.. - let quote = "'"; - let escaped = format!("'{}'", raw.replace(quote, "\\'")); - let mut lexed_arg; - if let Some((word, _)) = read_word_quoted(&escaped, quote, 0) { - let word = dequote(&word, "'\""); - lexed_arg = A2::new(word, Type::Word); - } else { - // "This branch may have no use-case"! - lexed_arg = A2::new(raw, Type::Word); - lexed_arg.tag("UNKNOWN"); - } - if quoted { - lexed_arg.tag("QUOTED"); - } - if arg.has_tag("ORIGINAL") { - lexed_arg.tag("ORIGINAL"); - } - self.args.push(lexed_arg) - } - } - } - /* - println!("lexed args:"); - for arg in &self.args { - println!("{:?}", arg); - } - */ - } - - /// Scan all arguments and if any are an exact match for a command name, then tag as CMD. If an - /// argument is an exact match for an attribute, despite being an inexact match for a command, - /// then it is not a command. - fn find_command(&mut self) -> bool { - for (i, arg) in self.args.iter().enumerate() { - let raw = arg.get_attribute("raw").unwrap(); - let canonical; - - if self.exact_match_entity("cmd", raw) { - canonical = raw.into(); - } else if self.exact_match_entity("attribute", raw) { - continue; - } else if let Some(cannon) = self.canonicalize_entity("cmd", raw) { - canonical = cannon; - } else { - continue; - } - - let mut arg = arg.clone(); - arg.set_attribute("canonical", canonical); - arg.tag("CMD"); - - // TODO: apply "command DNA" - - self.args[i] = arg; - - return true; - } - - false - } - - /// Set a default command argument. Look for situations that require defaults: - /// - /// 1. If no command was found, and no ID/UUID, and if rc.default.command is - /// configured, inject the lexed tokens from rc.default.command. - /// - /// 2. If no command was found, but an ID/UUID was found, then assume a command - /// of 'information'. - fn default_command(&mut self) { - let mut found_command = false; - let mut found_sequence = false; - - for arg in &self.args { - if arg.has_tag("CMD") { - found_command = true; - } - if arg.lextype == Type::Uuid || arg.lextype == Type::Number { - found_sequence = true; - } - } - - if !found_command { - if !found_sequence { - unreachable!(); // TODO (requires default.command, context, etc.) - } else { - let mut info = A2::new("information", Type::Word); - info.tag("ASSUMED"); - info.tag("CMD"); - self.args.insert(0, info); - } - } - } - - /// Search for 'value' in _entities category, return canonicalized value. - fn canonicalize_entity(&self, category: &str, value: &str) -> Option { - // TODO: for the moment this only accepts exact matches - if let Some(names) = self.entities.get(category) { - if names.contains(value) { - Some(value.into()) - } else { - None - } - } else { - None - } - } - - /// Search for exact 'value' in _entities category. - fn exact_match_entity(&self, category: &str, value: &str) -> bool { - if let Some(names) = self.entities.get(category) { - names.contains(value) - } else { - false - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - fn assert_args(args: &Vec, exp: Vec<&str>) { - assert_eq!( - args.iter().map(|a| format!("{:?}", a)).collect::>(), - exp.iter().map(|s| s.to_string()).collect::>(), - ); - } - - #[test] - fn alias() { - let mut c = CLI2::new(); - c.alias("foo", "bar"); - assert_eq!(c.aliases.get("foo"), Some(&"bar".to_string())); - } - - #[test] - fn entities() { - let mut c = CLI2::new(); - c.entity("cat", "foo"); - c.entity("cat", "bar"); - let mut exp = HashSet::new(); - exp.insert("foo".to_string()); - exp.insert("bar".to_string()); - assert_eq!(c.entities.get("cat"), Some(&exp)); - } - - #[test] - fn add() { - let mut c = CLI2::new(); - c.add("foo"); - c.add("bar"); - assert_eq!( - c.original_args - .iter() - .map(|a| format!("{:?}", a)) - .collect::>(), - vec![ - "A2{Word, ORIGINAL, raw=\"foo\"}", - "A2{Word, ORIGINAL, raw=\"bar\"}" - ] - ); - } - - #[test] - fn add_args() { - let mut c = CLI2::new(); - c.add("0"); - c.add("1"); - c.add("2"); - c.add_args(vec!["foo", "bar"]); - assert_args( - &c.original_args, - vec![ - "A2{Word, ORIGINAL, raw=\"0\"}", - "A2{Word, raw=\"foo\"}", - "A2{Word, raw=\"bar\"}", - "A2{Word, ORIGINAL, raw=\"1\"}", - "A2{Word, ORIGINAL, raw=\"2\"}", - ], - ); - } - - #[test] - fn analyze_example_cmdline() { - let mut c = CLI2::new(); - c.entity("cmd", "next"); - c.add("arg0"); - c.add("rc.gc=0"); - c.add("next"); - c.add("+PENDING"); - c.add("due:tomorrow"); - c.analyze(); - assert_args( - &c.args, - vec![ - "A2{Pair, CONFIG, ORIGINAL, modifier=\"gc\", name=\"rc\", raw=\"rc.gc=0\", separator=\"=\", value=\"0\"}", - "A2{Identifier, CMD, ORIGINAL, canonical=\"next\", raw=\"next\"}", - "A2{Tag, ORIGINAL, name=\"PENDING\", raw=\"+PENDING\", sign=\"+\"}", - "A2{Pair, ORIGINAL, modifier=\"\", name=\"due\", raw=\"due:tomorrow\", separator=\":\", value=\"tomorrow\"}", - ], - ); - } - - #[test] - fn exact_match_entity() { - let mut c = CLI2::new(); - c.entity("cmd", "next"); - c.entity("cmd", "list"); - assert!(c.exact_match_entity("cmd", "next")); - assert!(!c.exact_match_entity("cmd", "bar")); - assert!(!c.exact_match_entity("foo", "bar")); - } -} diff --git a/src/cli/mod.rs b/src/cli/mod.rs deleted file mode 100644 index f7b7486d5..000000000 --- a/src/cli/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod a2; -mod cli2; diff --git a/src/lib.rs b/src/lib.rs index c84a35f98..0cf26fda2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ #[macro_use] extern crate failure; -mod cli; mod errors; mod operation; mod replica; @@ -13,7 +12,6 @@ mod server; mod task; mod taskdb; pub mod taskstorage; -mod tdb2; mod util; pub use operation::Operation; @@ -23,11 +21,3 @@ pub use task::Priority; pub use task::Status; pub use task::Task; pub use taskdb::DB; - -use failure::Fallible; -use std::io::BufRead; - -// TODO: remove (artifact of merging projects) -pub fn parse(filename: &str, reader: impl BufRead) -> Fallible> { - Ok(tdb2::parse(filename, reader)?) -} diff --git a/src/tdb2/ff4.rs b/src/tdb2/ff4.rs deleted file mode 100644 index ed64f1a94..000000000 --- a/src/tdb2/ff4.rs +++ /dev/null @@ -1,245 +0,0 @@ -use std::str; - -use crate::task::{Task, TaskBuilder}; -use crate::util::pig::Pig; -use failure::Fallible; - -/// Rust implementation of part of utf8_codepoint from Taskwarrior's src/utf8.cpp -/// -/// Note that the original function will return garbage for invalid hex sequences; -/// this panics instead. -fn hex_to_unicode(value: &[u8]) -> Fallible { - if value.len() < 4 { - bail!("too short"); - } - - fn nyb(c: u8) -> Fallible { - match c { - b'0'..=b'9' => Ok((c - b'0') as u16), - b'a'..=b'f' => Ok((c - b'a' + 10) as u16), - b'A'..=b'F' => Ok((c - b'A' + 10) as u16), - _ => bail!("invalid hex character"), - } - }; - - let words = [nyb(value[0])? << 12 | nyb(value[1])? << 8 | nyb(value[2])? << 4 | nyb(value[3])?]; - Ok(String::from_utf16(&words[..])?) -} - -/// Rust implementation of JSON::decode in Taskwarrior's src/JSON.cpp -/// -/// Decode the given byte slice into a string using Taskwarrior JSON's escaping The slice is -/// assumed to be ASCII; unicode escapes within it will be expanded. -fn json_decode(value: &[u8]) -> Fallible { - let length = value.len(); - let mut rv = String::with_capacity(length); - - let mut pos = 0; - while pos < length { - let v = value[pos]; - if v == b'\\' { - pos += 1; - if pos == length { - rv.push(v as char); - break; - } - let v = value[pos]; - match v { - b'"' | b'\\' | b'/' => rv.push(v as char), - b'b' => rv.push(8 as char), - b'f' => rv.push(12 as char), - b'n' => rv.push('\n' as char), - b'r' => rv.push('\r' as char), - b't' => rv.push('\t' as char), - b'u' => { - let unicode = hex_to_unicode(&value[pos + 1..pos + 5]).map_err(|_| { - let esc = &value[pos - 1..pos + 5]; - match str::from_utf8(esc) { - Ok(s) => format_err!("invalid unicode escape `{}`", s), - Err(_) => format_err!("invalid unicode escape bytes {:?}", esc), - } - })?; - rv.push_str(&unicode); - pos += 4; - } - _ => { - rv.push(b'\\' as char); - rv.push(v as char); - } - } - } else { - rv.push(v as char) - } - pos += 1; - } - - Ok(rv) -} - -/// Rust implementation of Task::decode in Taskwarrior's src/Task.cpp -/// -/// Note that the docstring for the C++ function does not match the -/// implementation! -fn decode(value: String) -> String { - if let Some(_) = value.find('&') { - return value.replace("&open;", "[").replace("&close;", "]"); - } - value -} - -/// Parse an "FF4" formatted task line. From Task::parse in Taskwarrior's src/Task.cpp. -/// -/// While Taskwarrior supports additional formats, this is the only format supported by -/// taskwarrior_rust. -pub(super) fn parse_ff4(line: &str) -> Fallible { - let mut pig = Pig::new(line.as_bytes()); - let mut builder = TaskBuilder::new(); - - pig.skip(b'[')?; - let line = pig.get_until(b']')?; - let mut subpig = Pig::new(line); - while !subpig.depleted() { - let name = subpig.get_until(b':')?; - let name = str::from_utf8(name)?; - subpig.skip(b':')?; - let value = subpig.get_quoted(b'"')?; - let value = json_decode(value)?; - let value = decode(value); - builder = builder.set(name, value); - subpig.skip(b' ').ok(); // ignore if not found.. - } - pig.skip(b']')?; - if !pig.depleted() { - bail!("trailing characters on line"); - } - Ok(builder.finish()) -} - -#[cfg(test)] -mod test { - use super::{decode, hex_to_unicode, json_decode, parse_ff4}; - use crate::task::Pending; - - #[test] - fn test_hex_to_unicode_digits() { - assert_eq!(hex_to_unicode(b"1234").unwrap(), "\u{1234}"); - } - - #[test] - fn test_hex_to_unicode_lower() { - assert_eq!(hex_to_unicode(b"abcd").unwrap(), "\u{abcd}"); - } - - #[test] - fn test_hex_to_unicode_upper() { - assert_eq!(hex_to_unicode(b"ABCD").unwrap(), "\u{abcd}"); - } - - #[test] - fn test_hex_to_unicode_too_short() { - assert!(hex_to_unicode(b"AB").is_err()); - } - - #[test] - fn test_hex_to_unicode_invalid() { - assert!(hex_to_unicode(b"defg").is_err()); - } - - #[test] - fn test_json_decode_no_change() { - assert_eq!(json_decode(b"abcd").unwrap(), "abcd"); - } - - #[test] - fn test_json_decode_escape_quote() { - assert_eq!(json_decode(b"ab\\\"cd").unwrap(), "ab\"cd"); - } - - #[test] - fn test_json_decode_escape_backslash() { - assert_eq!(json_decode(b"ab\\\\cd").unwrap(), "ab\\cd"); - } - - #[test] - fn test_json_decode_escape_frontslash() { - assert_eq!(json_decode(b"ab\\/cd").unwrap(), "ab/cd"); - } - - #[test] - fn test_json_decode_escape_b() { - assert_eq!(json_decode(b"ab\\bcd").unwrap(), "ab\x08cd"); - } - - #[test] - fn test_json_decode_escape_f() { - assert_eq!(json_decode(b"ab\\fcd").unwrap(), "ab\x0ccd"); - } - - #[test] - fn test_json_decode_escape_n() { - assert_eq!(json_decode(b"ab\\ncd").unwrap(), "ab\ncd"); - } - - #[test] - fn test_json_decode_escape_r() { - assert_eq!(json_decode(b"ab\\rcd").unwrap(), "ab\rcd"); - } - - #[test] - fn test_json_decode_escape_t() { - assert_eq!(json_decode(b"ab\\tcd").unwrap(), "ab\tcd"); - } - - #[test] - fn test_json_decode_escape_other() { - assert_eq!(json_decode(b"ab\\xcd").unwrap(), "ab\\xcd"); - } - - #[test] - fn test_json_decode_escape_eos() { - assert_eq!(json_decode(b"ab\\").unwrap(), "ab\\"); - } - - #[test] - fn test_json_decode_escape_unicode() { - assert_eq!(json_decode(b"ab\\u1234").unwrap(), "ab\u{1234}"); - } - - #[test] - fn test_json_decode_escape_unicode_bad() { - let rv = json_decode(b"ab\\uwxyz"); - assert_eq!( - rv.unwrap_err().to_string(), - "invalid unicode escape `\\uwxyz`" - ); - } - - #[test] - fn test_decode_no_change() { - let s = "abcd " efgh &".to_string(); - assert_eq!(decode(s.clone()), s); - } - - #[test] - fn test_decode_multi() { - let s = "abcd &open; efgh &close; &open".to_string(); - assert_eq!(decode(s), "abcd [ efgh ] &open".to_string()); - } - - #[test] - fn test_parse_ff4() { - let s = "[description:\"desc\" entry:\"1437855511\" modified:\"1479480556\" \ - priority:\"L\" project:\"lists\" status:\"pending\" tags:\"watch\" \ - uuid:\"83ce989e-8634-4d62-841c-eb309383ff1f\"]"; - let task = parse_ff4(s).unwrap(); - assert_eq!(task.status, Pending); - assert_eq!(task.description, "desc"); - } - - #[test] - fn test_parse_ff4_fail() { - assert!(parse_ff4("abc:10]").is_err()); - assert!(parse_ff4("[abc:10").is_err()); - assert!(parse_ff4("[abc:10 123:123]").is_err()); - } -} diff --git a/src/tdb2/mod.rs b/src/tdb2/mod.rs deleted file mode 100644 index 009b741e9..000000000 --- a/src/tdb2/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! TDB2 is Taskwarrior's on-disk database format. This module implements -//! support for the data structure as a compatibility layer. - -mod ff4; - -use self::ff4::parse_ff4; -use crate::task::Task; -use failure::Fallible; -use std::io::BufRead; - -pub(crate) fn parse(filename: &str, reader: impl BufRead) -> Fallible> { - let mut tasks = vec![]; - for (i, line) in reader.lines().enumerate() { - tasks.push(parse_ff4(&line?).map_err(|e| { - format_err!( - "TDB2 Error at {}:{}: {}", - filename.to_string(), - i as u64 + 1, - e - ) - })?); - } - Ok(tasks) -} diff --git a/tests/parse.rs b/tests/parse.rs deleted file mode 100644 index e84aa00d1..000000000 --- a/tests/parse.rs +++ /dev/null @@ -1,20 +0,0 @@ -use chrono::prelude::*; -use std::fs::File; -use std::io::BufReader; - -#[test] -fn test_parse() { - let filename = "tests/data/tdb2-test.data"; - let file = File::open(filename).unwrap(); - let tasks = taskwarrior_rust::parse(filename, BufReader::new(file)).unwrap(); - assert_eq!( - tasks[0].description, - "https://phabricator.services.example.com/D7364 [taskgraph] Download debian packages" - ); - assert_eq!(tasks[0].entry, Utc.timestamp(1538520624, 0)); - assert_eq!(tasks[0].udas.get("phabricatorid").unwrap(), "D7364"); - assert_eq!(tasks[1].annotations[0].entry, Utc.timestamp(1541461824, 0)); - assert!(tasks[1].annotations[0] - .description - .starts_with("https://github.com",)); -} From 1fa6155b227e9a20f8c351cc55d310bc0b490f63 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 21 Nov 2020 18:01:00 -0500 Subject: [PATCH 044/548] rename to taskchampion --- Cargo.lock | 484 ++++++++++++------------- Cargo.toml | 2 +- README.md | 113 +----- docs/development-notes.md | 106 ++++++ src/bin/task.rs | 2 +- tests/operation_transform_invariant.rs | 2 +- tests/sync.rs | 2 +- tests/sync_action_sequences.rs | 2 +- 8 files changed, 360 insertions(+), 353 deletions(-) create mode 100644 docs/development-notes.md diff --git a/Cargo.lock b/Cargo.lock index 2aa865cc7..61a97dabe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,754 +4,752 @@ name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "atty" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "winapi", ] [[package]] name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] name = "backtrace" version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" dependencies = [ - "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys", + "cfg-if", + "libc", + "rustc-demangle", ] [[package]] name = "backtrace-sys" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" dependencies = [ - "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "libc", ] [[package]] name = "bit-set" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" dependencies = [ - "bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-vec", ] [[package]] name = "bit-vec" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "byteorder" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" [[package]] name = "c2-chacha" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" dependencies = [ - "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86", ] [[package]] name = "cc" version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "chrono" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" dependencies = [ - "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer", + "num-traits", + "serde", + "time", ] [[package]] name = "clap" version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", ] [[package]] name = "failure" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" dependencies = [ - "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace", + "failure_derive", ] [[package]] name = "failure_derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] name = "fnv" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "getrandom" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "libc", + "wasi", ] [[package]] name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" [[package]] name = "kv" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" dependencies = [ - "lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rmp-serde 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lmdb-rkv", + "rmp-serde", + "serde", + "thiserror", + "toml", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" [[package]] name = "lmdb-rkv" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605061e5465304475be2041f19967a900175ea1b6d8f47fbab84a84fb8c48452" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "lmdb-rkv-sys 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "byteorder", + "libc", + "lmdb-rkv-sys", ] [[package]] name = "lmdb-rkv-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7982ba0460e939e26a52ee12c8075deab0ebd44ed21881f656841b70e021b7c8" dependencies = [ - "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "libc", + "pkg-config", ] [[package]] name = "num-integer" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "num-traits", ] [[package]] name = "num-traits" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "pkg-config" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" [[package]] name = "ppv-lite86" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" [[package]] name = "proc-macro2" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] name = "proptest" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f" dependencies = [ - "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error", + "rand 0.6.5", + "rand_chacha 0.1.1", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", ] [[package]] name = "quick-error" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" [[package]] name = "quote" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] name = "rand" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" dependencies = [ - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", ] [[package]] name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", ] [[package]] name = "rand" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", + "libc", + "rand_chacha 0.2.1", + "rand_core 0.5.1", + "rand_hc 0.2.0", ] [[package]] name = "rand_chacha" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "rand_core 0.3.1", ] [[package]] name = "rand_chacha" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" dependencies = [ - "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "c2-chacha", + "rand_core 0.5.1", ] [[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2", ] [[package]] name = "rand_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", ] [[package]] name = "rand_hc" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1", ] [[package]] name = "rand_isaac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1", ] [[package]] name = "rand_jitter" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "rand_core 0.4.2", + "winapi", ] [[package]] name = "rand_os" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", ] [[package]] name = "rand_pcg" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "rand_core 0.4.2", ] [[package]] name = "rand_xorshift" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1", ] [[package]] name = "rdrand" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1", ] [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "regex-syntax" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" [[package]] name = "remove_dir_all" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "rmp" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "num-traits", ] [[package]] name = "rmp-serde" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a31c0798045f039ace94e0166f76478b3ba83116ec7c9d4bc934c5b13b8df21" dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rmp 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "rmp", + "serde", ] [[package]] name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" [[package]] name = "rusty-fork" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" dependencies = [ - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv", + "quick-error", + "tempfile", + "wait-timeout", ] [[package]] name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" [[package]] name = "serde" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" dependencies = [ - "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" dependencies = [ - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "ryu", + "serde", ] [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc157159e2a7df58cd67b1cace10b8ed256a404fb0070593f137d8ba6bef4de" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "synstructure" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", + "unicode-xid", ] [[package]] -name = "taskwarrior-rust" +name = "taskchampion" version = "0.1.0" dependencies = [ - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "kv 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", - "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono", + "clap", + "failure", + "kv", + "lmdb-rkv", + "proptest", + "serde", + "serde_json", + "tempdir", + "uuid", ] [[package]] name = "tempdir" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" dependencies = [ - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6", + "remove_dir_all", ] [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "libc", + "rand 0.7.2", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] name = "thiserror" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" dependencies = [ - "thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "redox_syscall", + "winapi", ] [[package]] name = "toml" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf" dependencies = [ - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", ] [[package]] name = "unicode-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "uuid" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2", + "serde", ] [[package]] name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "wasi" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" -"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" -"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" -"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" -"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" -"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" -"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" -"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" -"checksum kv 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" -"checksum lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "605061e5465304475be2041f19967a900175ea1b6d8f47fbab84a84fb8c48452" -"checksum lmdb-rkv-sys 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7982ba0460e939e26a52ee12c8075deab0ebd44ed21881f656841b70e021b7c8" -"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" -"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" -"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" -"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" -"checksum proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" -"checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f" -"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" -"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" -"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" -"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum rmp 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" -"checksum rmp-serde 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4a31c0798045f039ace94e0166f76478b3ba83116ec7c9d4bc934c5b13b8df21" -"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" -"checksum rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" -"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" -"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" -"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" -"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ddc157159e2a7df58cd67b1cace10b8ed256a404fb0070593f137d8ba6bef4de" -"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" -"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" -"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" -"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf" -"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 9cc649f10..8e5c07492 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "taskwarrior-rust" +name = "taskchampion" version = "0.1.0" authors = ["Dustin J. Mitchell "] edition = "2018" diff --git a/README.md b/README.md index 313270718..2ced65b38 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,11 @@ -Sketch for a Taskd Replacement ------------------------------- +TaskChampion +------------ -Goals: +TaskChampion is an open-source personal task-tracking application. +Use it to keep track of what you need to do, with a quick command-line interface and flexible sorting and filtering. +It is modeled on [TaskWarrior](https://taskwarrior.org), but not a drop-in replacement for that application. - * Reasonable privacy: user's task details not visible on server - * Reliable concurrency - clients do not diverge - * Storage O(n) with n number of tasks +See: -# Data Model - -A client manages a single offline instance of a single user's task list. -The data model is only seen from the clients' perspective. - -## Task Database - -The task database is composed of an un-ordered collection of tasks, each keyed by a UUID. -Each task has an arbitrary-sized set of key/value properties, with string values. - -Tasks are only created, never deleted. -See below for details on how tasks can "expire" from the task database. - -## Operations - -Every change to the task database is captured as an operation. -Each operation has one of the forms - * `Create(uuid)` - * `Delete(uuid)` - * `Update(uuid, property, value, timestamp)` - -The Create form creates a new task. -It is invalid to create a task that already exists. - -Similarly, the Delete form deletes an existing task. -It is invalid to delete a task that does not exist. - -The Update form updates the given property of the given task, where property and value are both strings. -Value can also be `None` to indicate deletion of a property. -It is invalid to update a task that does not exist. -The timestamp on updates serves as additional metadata and is used to resolve conflicts. - -Operations act as deltas between database states. - -## Versions and Synchronization - -Occasionally, database states are named with an integer, called a version. -The system as a whole (server and clients) constructs a monotonic sequence of versions and the operations that separate each version from the next. -No gaps are allowed in the verison numbering. -Version 0 is implicitly the empty database. - -The server stores the operations for each version, and provides them as needed to clients. -Clients use this information to update their local task databases, and to generate new versions to send to the server. - -Clients generate a new version to transmit changes made locally to the server. -The changes are represented as a sequence of operations with the final operation being tagged as the version. -In order to keep the gap-free monotonic numbering, the server will only accept a proposed version from a client if its number is one greater that the latest version on the server. -When this is not the case, the client must "rebase" the local changes onto the latest version from the server and try again. -This operation is performed using operational transformation (OT). -The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the client can apply to its local task database to "catch up" to the version on the server. - -## Snapshots - -As designed, storage required on the server would grow with time, as would the time required for new clients to update to the latest version. -As an optimization, the server also stores "snapshots" containing a full copy of the task database at a given version. -Based on configurable heuristics, it may delete older operations and snapshots, as long as enough data remains for active clients to synchronize and for new clients to initialize. - -Since snapshots must be computed by clients, the server may "request" a snapshot when providing the latest version to a client. -This request comes with a number indicating how much it 'wants" the snapshot. -Clients which can easily generate and transmit a snapshot should be generous to the server, while clients with more limited resources can wait until the server's requests are more desperate. -The intent is, where possible, to request snapshots created on well-connected desktop clients over mobile and low-power clients. - -## Encryption and Signing - -From the server's perspective, all data except for version numbers are opaque binary blobs. -Clients encrypt and sign these blobs using a symmetric key known only to the clients. -This secures the data at-rest on the server. -Note that privacy is not complete, as the server still has some information about users, including source and frequency of synchronization transactions and size of those transactions. - -## Backups - -In this design, the server is little more than an authenticated storage for encrypted blobs provided by the client. -To allow for failure or data loss on the server, clients are expected to cache these blobs locally for a short time (a week), along with a server-provided HMAC signature. -When data loss is detected -- such as when a client expects the server to have a version N or higher, and the server only has N-1, the client can send those blobs to the server. -The server can validate the HMAC and, if successful, add the blobs to its datastore. - -## Expiration - -TBD - -.. conditions on flushing to allow consistent handling - -# Implementation Notes - -## Client / Server Protocol - -TBD - -.. using HTTP -.. user auth -.. user setup process - -## Batching Operations - -TBD - -## Recurrence - -TBD + * [Development Notes](docs/development-notes.md) + * [Progress on the first version](https://github.com/djmitche/taskwarrior-rust/projects/1) diff --git a/docs/development-notes.md b/docs/development-notes.md new file mode 100644 index 000000000..24f5d87b6 --- /dev/null +++ b/docs/development-notes.md @@ -0,0 +1,106 @@ +Goals: + + * Reasonable privacy: user's task details not visible on server + * Reliable concurrency - clients do not diverge + * Storage O(n) with n number of tasks + +# Data Model + +A client manages a single offline instance of a single user's task list. +The data model is only seen from the clients' perspective. + +## Task Database + +The task database is composed of an un-ordered collection of tasks, each keyed by a UUID. +Each task has an arbitrary-sized set of key/value properties, with string values. + +Tasks are only created, never deleted. +See below for details on how tasks can "expire" from the task database. + +## Operations + +Every change to the task database is captured as an operation. +Each operation has one of the forms + * `Create(uuid)` + * `Delete(uuid)` + * `Update(uuid, property, value, timestamp)` + +The Create form creates a new task. +It is invalid to create a task that already exists. + +Similarly, the Delete form deletes an existing task. +It is invalid to delete a task that does not exist. + +The Update form updates the given property of the given task, where property and value are both strings. +Value can also be `None` to indicate deletion of a property. +It is invalid to update a task that does not exist. +The timestamp on updates serves as additional metadata and is used to resolve conflicts. + +Operations act as deltas between database states. + +## Versions and Synchronization + +Occasionally, database states are named with an integer, called a version. +The system as a whole (server and clients) constructs a monotonic sequence of versions and the operations that separate each version from the next. +No gaps are allowed in the verison numbering. +Version 0 is implicitly the empty database. + +The server stores the operations for each version, and provides them as needed to clients. +Clients use this information to update their local task databases, and to generate new versions to send to the server. + +Clients generate a new version to transmit changes made locally to the server. +The changes are represented as a sequence of operations with the final operation being tagged as the version. +In order to keep the gap-free monotonic numbering, the server will only accept a proposed version from a client if its number is one greater that the latest version on the server. +When this is not the case, the client must "rebase" the local changes onto the latest version from the server and try again. +This operation is performed using operational transformation (OT). +The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the client can apply to its local task database to "catch up" to the version on the server. + +## Snapshots + +As designed, storage required on the server would grow with time, as would the time required for new clients to update to the latest version. +As an optimization, the server also stores "snapshots" containing a full copy of the task database at a given version. +Based on configurable heuristics, it may delete older operations and snapshots, as long as enough data remains for active clients to synchronize and for new clients to initialize. + +Since snapshots must be computed by clients, the server may "request" a snapshot when providing the latest version to a client. +This request comes with a number indicating how much it 'wants" the snapshot. +Clients which can easily generate and transmit a snapshot should be generous to the server, while clients with more limited resources can wait until the server's requests are more desperate. +The intent is, where possible, to request snapshots created on well-connected desktop clients over mobile and low-power clients. + +## Encryption and Signing + +From the server's perspective, all data except for version numbers are opaque binary blobs. +Clients encrypt and sign these blobs using a symmetric key known only to the clients. +This secures the data at-rest on the server. +Note that privacy is not complete, as the server still has some information about users, including source and frequency of synchronization transactions and size of those transactions. + +## Backups + +In this design, the server is little more than an authenticated storage for encrypted blobs provided by the client. +To allow for failure or data loss on the server, clients are expected to cache these blobs locally for a short time (a week), along with a server-provided HMAC signature. +When data loss is detected -- such as when a client expects the server to have a version N or higher, and the server only has N-1, the client can send those blobs to the server. +The server can validate the HMAC and, if successful, add the blobs to its datastore. + +## Expiration + +TBD + +.. conditions on flushing to allow consistent handling + +# Implementation Notes + +## Client / Server Protocol + +TBD + +.. using HTTP +.. user auth +.. user setup process + +## Batching Operations + +TBD + +## Recurrence + +TBD + diff --git a/src/bin/task.rs b/src/bin/task.rs index c2affafda..b816bcc3c 100644 --- a/src/bin/task.rs +++ b/src/bin/task.rs @@ -1,7 +1,7 @@ extern crate clap; use clap::{App, Arg, SubCommand}; use std::path::Path; -use taskwarrior_rust::{taskstorage, Replica, Status, DB}; +use taskchampion::{taskstorage, Replica, Status, DB}; use uuid::Uuid; fn main() { diff --git a/tests/operation_transform_invariant.rs b/tests/operation_transform_invariant.rs index 4a588d707..461aba009 100644 --- a/tests/operation_transform_invariant.rs +++ b/tests/operation_transform_invariant.rs @@ -1,6 +1,6 @@ use chrono::Utc; use proptest::prelude::*; -use taskwarrior_rust::{taskstorage, Operation, DB}; +use taskchampion::{taskstorage, Operation, DB}; use uuid::Uuid; fn newdb() -> DB { diff --git a/tests/sync.rs b/tests/sync.rs index b3f73f6de..fc594bbd3 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -1,5 +1,5 @@ use chrono::Utc; -use taskwarrior_rust::{taskstorage, Operation, Server, DB}; +use taskchampion::{taskstorage, Operation, Server, DB}; use uuid::Uuid; fn newdb() -> DB { diff --git a/tests/sync_action_sequences.rs b/tests/sync_action_sequences.rs index ddc8d328c..741988b11 100644 --- a/tests/sync_action_sequences.rs +++ b/tests/sync_action_sequences.rs @@ -1,6 +1,6 @@ use chrono::Utc; use proptest::prelude::*; -use taskwarrior_rust::{taskstorage, Operation, Server, DB}; +use taskchampion::{taskstorage, Operation, Server, DB}; use uuid::Uuid; fn newdb() -> DB { From 0e792ad58433a417c42f29061db728852e310804 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 21 Nov 2020 18:48:26 -0500 Subject: [PATCH 045/548] use a v1 .taskcluster.yml --- .taskcluster.yml | 72 +++++++++++++++++++++++++++++------------------- src/task/mod.rs | 4 +-- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/.taskcluster.yml b/.taskcluster.yml index d6406c4ac..07e8fe02b 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -1,29 +1,45 @@ -version: 0 +version: 1 +reporting: checks-v1 +policy: + pullRequests: public tasks: - - provisionerId: '{{ taskcluster.docker.provisionerId }}' - workerType: '{{ taskcluster.docker.workerType }}' - extra: - github: - events: - - pull_request.opened - - pull_request.reopened - - pull_request.synchronize - - push - payload: - maxRunTime: 3600 - image: 'rust:latest' - command: - - /bin/bash - - '-c' - - >- - git clone {{event.head.repo.url}} repo && - cd repo && - git config advice.detachedHead false && - git checkout {{event.head.sha}} && - cargo test - metadata: - name: Test - description: 'Run tests' - owner: '{{ event.head.user.email }}' - source: '{{ event.head.repo.url }}' -allowPullRequests: collaborators + $if: 'tasks_for in ["github-push", "github-pull-request"]' + then: + $let: + run: + $if: 'tasks_for == "github-push"' + then: true + else: {$eval: 'event.action in ["opened", "reopened", "synchronize"]'} + repo_url: + $if: 'tasks_for == "github-push"' + then: ${event.repository.clone_url} + else: ${event.pull_request.head.repo.clone_url} + ref: + $if: 'tasks_for == "github-push"' + then: ${event.after} + else: ${event.pull_request.head.sha} + in: + - $if: run + then: + provisionerId: 'proj-misc' + workerType: 'ci' + deadline: {$fromNow: '1 hour'} + expires: {$fromNow: '1 day'} + payload: + maxRunTime: 3600 + image: rust:latest + command: + - /bin/bash + - '-c' + - >- + git clone ${repo_url} repo && + cd repo && + git config advice.detachedHead false && + git checkout ${ref} && + cargo test && + cargo fmt -- --check + metadata: + name: taskchampion-tests + description: Run tests for taskchampion + owner: dustin@v.igoro.us + source: ${repo_url} diff --git a/src/task/mod.rs b/src/task/mod.rs index 6587458ba..f22b30c70 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -1,7 +1,7 @@ mod task; mod taskbuilder; -pub use self::taskbuilder::TaskBuilder; -pub use self::task::{Task, Priority, Status, Timestamp, Annotation}; pub use self::task::Priority::*; pub use self::task::Status::*; +pub use self::task::{Annotation, Priority, Status, Task, Timestamp}; +pub use self::taskbuilder::TaskBuilder; From 2bd68e85d3f2ddffe03657156a07d9727ecf8d8a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 21 Nov 2020 19:06:15 -0500 Subject: [PATCH 046/548] remove TODO (now moved to issues) --- TODO.txt | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 TODO.txt diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 0f210f2ce..000000000 --- a/TODO.txt +++ /dev/null @@ -1,18 +0,0 @@ -* move rebuild_working_set logic to replica.rs, also include states != - completed / deleted -* [WIP] assign types to properties in Replica - - db / operation model is just k/v, but formatted names can be used for - structure: - - dependencies: `dependency. = ""` - - annotations: `annotation. = "annotation"` - - tags: `tags. = ""` -* abstract server into trait -* add HTTP API -* fix TODO items in replica.rs -* implement snapshot requests -* implement backups -* implement client-side encryption -* design expiration - - need to be sure that create / delete operations don't get reversed -* cli tools -* move test-only tools somewhere else (helpers in tests/?) From c8a28b5ab37dff17d40a6c7a5ed3f483d4e483e2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 21 Nov 2020 19:09:55 -0500 Subject: [PATCH 047/548] install rustfmt --- .taskcluster.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.taskcluster.yml b/.taskcluster.yml index 07e8fe02b..37cb4a98f 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -32,6 +32,7 @@ tasks: - /bin/bash - '-c' - >- + rustup component add rustfmt && git clone ${repo_url} repo && cd repo && git config advice.detachedHead false && From ae8872d51e2cb3aa4acea523e0261ccdd5ae4976 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 21 Nov 2020 19:41:36 -0500 Subject: [PATCH 048/548] add an mdbook --- .taskcluster.yml | 27 +++++++++++++++++++++++++++ docs/.gitignore | 1 + docs/README.md | 3 +++ docs/book.toml | 6 ++++++ docs/src/SUMMARY.md | 3 +++ docs/{ => src}/development-notes.md | 0 6 files changed, 40 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/README.md create mode 100644 docs/book.toml create mode 100644 docs/src/SUMMARY.md rename docs/{ => src}/development-notes.md (100%) diff --git a/.taskcluster.yml b/.taskcluster.yml index 37cb4a98f..5f7883c74 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -44,3 +44,30 @@ tasks: description: Run tests for taskchampion owner: dustin@v.igoro.us source: ${repo_url} + - $if: run + then: + provisionerId: 'proj-misc' + workerType: 'ci' + deadline: {$fromNow: '1 hour'} + expires: {$fromNow: '1 day'} + payload: + maxRunTime: 3600 + image: rust:latest + command: + - /bin/bash + - '-c' + - >- + git clone ${repo_url} repo && + cd repo && + git config advice.detachedHead false && + git checkout ${ref} && + cd docs && + curl -L --compressed https://github.com/rust-lang/mdBook/releases/download/v0.4.4/mdbook-v0.4.4-x86_64-unknown-linux-gnu.tar.gz | gunzip -c | tar -xf - && + chmod +x mdbook && + ./mdbook test && + ./mdbook build + metadata: + name: taskchampion-book + description: Verify that the docs build with mdbook + owner: dustin@v.igoro.us + source: ${repo_url} diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..7585238ef --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +book diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..7aaa35c16 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +This is an [mdbook](https://rust-lang.github.io/mdBook/index.html) book. +Minor modifications can be made without installing the mdbook tool, as the content is simple Markdown. +Changes are verified on pull requests. diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 000000000..0e19be438 --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Dustin J. Mitchell"] +language = "en" +multilingual = false +src = "src" +title = "TaskChampion" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 000000000..e499e5c62 --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Development Notes](./development-notes.md) diff --git a/docs/development-notes.md b/docs/src/development-notes.md similarity index 100% rename from docs/development-notes.md rename to docs/src/development-notes.md From 17fe90c8d0f12b4eefd7d6b3037fe4cfed9e06dc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 21 Nov 2020 19:47:40 -0500 Subject: [PATCH 049/548] poor excuse for documentation --- docs/src/SUMMARY.md | 3 +++ docs/src/installation.md | 3 +++ docs/src/usage.md | 4 ++++ 3 files changed, 10 insertions(+) create mode 100644 docs/src/installation.md create mode 100644 docs/src/usage.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index e499e5c62..1bfe24c75 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,3 +1,6 @@ # Summary +- [Installation](./installation.md) +- [Usage](./usage.md) +--- - [Development Notes](./development-notes.md) diff --git a/docs/src/installation.md b/docs/src/installation.md new file mode 100644 index 000000000..a597a11da --- /dev/null +++ b/docs/src/installation.md @@ -0,0 +1,3 @@ +# Installation + +As this is currently in development, installation is by cloning the repository and running "cargo build". diff --git a/docs/src/usage.md b/docs/src/usage.md new file mode 100644 index 000000000..39a73fc03 --- /dev/null +++ b/docs/src/usage.md @@ -0,0 +1,4 @@ +# Usage + +The main interface to your tasks is the `task` command, which supports various subcommands. +You can find a quick list of all subcommands with `task help`. From 74fb3c3c413e57daae67bb758147a373748c1831 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 21 Nov 2020 23:46:49 -0500 Subject: [PATCH 050/548] fix up some TODOs in replica.rs --- Cargo.lock | 16 ++++ Cargo.toml | 1 + docs/src/SUMMARY.md | 2 + docs/src/data-model.md | 42 ++++++++++ docs/src/development-notes.md | 15 +--- docs/src/internals.md | 4 + src/errors.rs | 4 +- src/replica.rs | 150 ++++++++++++++++++++++++++-------- src/task/taskbuilder.rs | 2 - 9 files changed, 183 insertions(+), 53 deletions(-) create mode 100644 docs/src/data-model.md create mode 100644 docs/src/internals.md diff --git a/Cargo.lock b/Cargo.lock index 61a97dabe..8f78fcdfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "failure" version = "0.1.6" @@ -176,6 +182,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.4" @@ -607,6 +622,7 @@ dependencies = [ "chrono", "clap", "failure", + "itertools", "kv", "lmdb-rkv", "proptest", diff --git a/Cargo.toml b/Cargo.toml index 8e5c07492..e1f5ba545 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ failure = {version = "0.1.5", features = ["derive"] } clap = "~2.33.0" kv = {version = "0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "0.12.3"} +itertools = "0.9.0" [dev-dependencies] proptest = "0.9.4" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 1bfe24c75..106400d47 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -2,5 +2,7 @@ - [Installation](./installation.md) - [Usage](./usage.md) +- [Internal Details](./internals.md) + - [Data Model](./data-model.md) --- - [Development Notes](./development-notes.md) diff --git a/docs/src/data-model.md b/docs/src/data-model.md new file mode 100644 index 000000000..908ba5fe1 --- /dev/null +++ b/docs/src/data-model.md @@ -0,0 +1,42 @@ +# Data Model + +A client manages a single offline instance of a single user's task list. +The data model is only seen from the clients' perspective. + +## Task Database + +The task database is composed of an un-ordered collection of tasks, each keyed by a UUID. +Each task in the database has an arbitrary-sized set of key/value properties, with string values. + +Tasks are only created and modified; "deleted" tasks continue to stick around and can be modified and even un-deleted. +Tasks have an expiration time, after which they may be purged from the database. + +## Task Fields + +Each task can have any of the following fields. +Timestamps are stored as UNIX epoch timestamps, in the form of an integer expressed in decimal notation. +Note that it is possible for any field to be omitted. + +NOTE: This structure is based on https://taskwarrior.org/docs/design/task.html, but will diverge from that +model over time. + +* `status` - one of `Pending`, `Completed`, `Deleted`, `Recurring`, or `Waiting` +* `entry` (timestamp) - time that the task was created +* `description` - the one-line summary of the task +* `start` (timestamp) - if set, the task is active and this field gives the time the task was started +* `end` (timestamp) - the time at which the task was deleted or completed +* `due` (timestamp) - the time at which the task is due +* `until` (timestamp) - the time after which recurrent child tasks should not be created +* `wait` (timestamp) - the time before which this task is considered waiting and should not be shown +* `modified` (timestamp) - time that the task was last modified +* `scheduled` (timestamp) - time that the task is available to start +* `recur` - recurrence frequency +* `mask` - recurrence history +* `imask` - for children of recurring tasks, the index into the `mask` property on the parent +* `parent` - for children of recurring tasks, the uuid of the parent task +* `project` - the task's project (usually a short identifier) +* `priority` - the task's priority, one of `L`, `M`, or `H`. +* `depends` - a comma (`,`) separated list of uuids of tasks on which this task depends +* `tags` - a comma (`,`) separated list of tags for this task +* `annotation_` - an annotation for this task, with the timestamp as part of the key +* `udas` - user-defined attributes diff --git a/docs/src/development-notes.md b/docs/src/development-notes.md index 24f5d87b6..ffdbb6314 100644 --- a/docs/src/development-notes.md +++ b/docs/src/development-notes.md @@ -4,20 +4,7 @@ Goals: * Reliable concurrency - clients do not diverge * Storage O(n) with n number of tasks -# Data Model - -A client manages a single offline instance of a single user's task list. -The data model is only seen from the clients' perspective. - -## Task Database - -The task database is composed of an un-ordered collection of tasks, each keyed by a UUID. -Each task has an arbitrary-sized set of key/value properties, with string values. - -Tasks are only created, never deleted. -See below for details on how tasks can "expire" from the task database. - -## Operations +# Operations Every change to the task database is captured as an operation. Each operation has one of the forms diff --git a/docs/src/internals.md b/docs/src/internals.md new file mode 100644 index 000000000..d845d59a9 --- /dev/null +++ b/docs/src/internals.md @@ -0,0 +1,4 @@ +# Internal Details + +This section describes some of the internal details of TaskChampion. +While this section is not required to use TaskChampion, understanding some of these details may help to understand how TaskChampion behaves. diff --git a/src/errors.rs b/src/errors.rs index 314a7a0a5..8a180936f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -5,6 +5,6 @@ pub enum Error { #[fail(display = "Task Database Error: {}", _0)] DBError(String), - #[fail(display = "TDB2 Error: {}", _0)] - TDB2Error(String), + #[fail(display = "Replica Error: {}", _0)] + ReplicaError(String), } diff --git a/src/replica.rs b/src/replica.rs index abfe2ab7e..4d4a0e96e 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -1,9 +1,11 @@ +use crate::errors::Error; use crate::operation::Operation; use crate::task::{Priority, Status, Task, TaskBuilder}; use crate::taskdb::DB; use crate::taskstorage::TaskMap; -use chrono::Utc; +use chrono::{DateTime, Utc}; use failure::Fallible; +use itertools::join; use std::collections::HashMap; use uuid::Uuid; @@ -77,7 +79,12 @@ impl Replica { status: Status, description: String, ) -> Fallible { - // TODO: check that it doesn't exist + // check that it doesn't exist; this is a convenience check, as the task + // may already exist when this Create operation is finally sync'd with + // operations from other replicas + if self.taskdb.get_task(&uuid)?.is_some() { + return Err(Error::DBError(format!("Task {} already exists", uuid)).into()); + } self.taskdb .apply(Operation::Create { uuid: uuid.clone() })?; self.update_task(uuid.clone(), "status", Some(String::from(status.as_ref())))?; @@ -90,7 +97,11 @@ impl Replica { /// Delete a task. The task must exist. pub fn delete_task(&mut self, uuid: Uuid) -> Fallible<()> { - // TODO: must verify task does exist + // check that it already exists; this is a convenience check, as the task may already exist + // when this Create operation is finally sync'd with operations from other replicas + if self.taskdb.get_task(&uuid)?.is_none() { + return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); + } self.taskdb.apply(Operation::Delete { uuid }) } @@ -121,6 +132,8 @@ impl From<&TaskMap> for Task { } } +// TODO: move this struct to crate::task, with a trait for update_task, since it is the reverse +// of TaskBuilder::set /// TaskMut allows changes to a task. It is intended for short-term use, such as changing a few /// properties, and should not be held for long periods of wall-clock time. pub struct TaskMut<'a> { @@ -150,47 +163,108 @@ impl<'a> TaskMut<'a> { Ok(()) } - /// Set the task's status - pub fn status(&mut self, status: Status) -> Fallible<()> { + fn set_string(&mut self, property: &str, value: Option) -> Fallible<()> { + self.lastmod()?; + self.replica.update_task(self.uuid.clone(), property, value) + } + + fn set_timestamp(&mut self, property: &str, value: Option>) -> Fallible<()> { self.lastmod()?; self.replica.update_task( self.uuid.clone(), - "status", - Some(String::from(status.as_ref())), + property, + value.map(|v| format!("{}", v.timestamp())), ) } - // TODO: description - // TODO: start - // TODO: end - // TODO: due - // TODO: until - // TODO: wait - // TODO: scheduled - // TODO: recur - // TODO: mask - // TODO: imask - // TODO: parent + /// Set the task's status + pub fn status(&mut self, status: Status) -> Fallible<()> { + self.set_string("status", Some(String::from(status.as_ref()))) + } + + /// Set the task's description + pub fn description(&mut self, description: String) -> Fallible<()> { + self.set_string("description", Some(description)) + } + + /// Set the task's start time + pub fn start(&mut self, time: Option>) -> Fallible<()> { + self.set_timestamp("start", time) + } + + /// Set the task's end time + pub fn end(&mut self, time: Option>) -> Fallible<()> { + self.set_timestamp("end", time) + } + + /// Set the task's due time + pub fn due(&mut self, time: Option>) -> Fallible<()> { + self.set_timestamp("due", time) + } + + /// Set the task's until time + pub fn until(&mut self, time: Option>) -> Fallible<()> { + self.set_timestamp("until", time) + } + + /// Set the task's wait time + pub fn wait(&mut self, time: Option>) -> Fallible<()> { + self.set_timestamp("wait", time) + } + + /// Set the task's scheduled time + pub fn scheduled(&mut self, time: Option>) -> Fallible<()> { + self.set_timestamp("scheduled", time) + } + + /// Set the task's recur value + pub fn recur(&mut self, recur: Option) -> Fallible<()> { + self.set_string("recur", recur) + } + + /// Set the task's mask value + pub fn mask(&mut self, mask: Option) -> Fallible<()> { + self.set_string("mask", mask) + } + + /// Set the task's imask value + pub fn imask(&mut self, imask: Option) -> Fallible<()> { + self.set_string("imask", imask.map(|v| format!("{}", v))) + } + + /// Set the task's parent task + pub fn parent(&mut self, parent: Option) -> Fallible<()> { + self.set_string("parent", parent.map(|v| format!("{}", v))) + } /// Set the task's project - pub fn project(&mut self, project: String) -> Fallible<()> { - self.lastmod()?; - self.replica - .update_task(self.uuid.clone(), "project", Some(project)) + pub fn project(&mut self, project: Option) -> Fallible<()> { + self.set_string("project", project) } /// Set the task's priority - pub fn priority(&mut self, priority: Priority) -> Fallible<()> { - self.lastmod()?; - self.replica.update_task( - self.uuid.clone(), - "priority", - Some(String::from(priority.as_ref())), + pub fn priority(&mut self, priority: Option) -> Fallible<()> { + self.set_string("priority", priority.map(|v| String::from(v.as_ref()))) + } + + /// Set the task's depends; note that this completely replaces the list of tasks on which this + /// one depends. + pub fn depends(&mut self, depends: Vec) -> Fallible<()> { + self.set_string( + "depends", + if depends.len() > 0 { + Some(join(depends.iter(), ",")) + } else { + None + }, ) } - // TODO: depends - // TODO: tags + /// Set the task's tags; note that this completely replaces the list of tags + pub fn tags(&mut self, tags: Vec) -> Fallible<()> { + self.set_string("tags", Some(join(tags.iter(), ","))) + } + // TODO: annotations // TODO: udas } @@ -209,7 +283,7 @@ mod tests { let mut tm = rep .new_task(uuid.clone(), Status::Pending, "a task".into()) .unwrap(); - tm.priority(Priority::L).unwrap(); + tm.priority(Some(Priority::L)).unwrap(); let t = rep.get_task(&uuid).unwrap().unwrap(); assert_eq!(t.description, String::from("a task")); @@ -237,12 +311,18 @@ mod tests { rep.new_task(uuid.clone(), Status::Pending, "another task".into()) .unwrap(); - let mut tm = rep.get_task_mut(&uuid).unwrap().unwrap(); - tm.priority(Priority::L).unwrap(); - tm.project("work".into()).unwrap(); - let t = rep.get_task(&uuid).unwrap().unwrap(); assert_eq!(t.description, String::from("another task")); + + let mut tm = rep.get_task_mut(&uuid).unwrap().unwrap(); + tm.status(Status::Completed).unwrap(); + tm.description("another task, updated".into()).unwrap(); + tm.priority(Some(Priority::L)).unwrap(); + tm.project(Some("work".into())).unwrap(); + + let t = rep.get_task(&uuid).unwrap().unwrap(); + assert_eq!(t.status, Status::Completed); + assert_eq!(t.description, String::from("another task, updated")); assert_eq!(t.project, Some("work".into())); } diff --git a/src/task/taskbuilder.rs b/src/task/taskbuilder.rs index 53d805cf0..6d5bc0d92 100644 --- a/src/task/taskbuilder.rs +++ b/src/task/taskbuilder.rs @@ -131,8 +131,6 @@ impl TaskBuilder { annotations: self.annotations, udas: self.udas, } - - // TODO: check validity per https://taskwarrior.org/docs/design/task.html } } From f45292d049bc407b3494e6d0d2bab44a8c47ad55 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 22 Nov 2020 00:51:41 -0500 Subject: [PATCH 051/548] remove more taskwarrior compatibility stuff --- src/errors.rs | 3 - src/lib.rs | 1 - src/util/datetime.rs | 39 - src/util/duration.rs | 44 - src/util/lexer.rs | 3194 ------------------------------------------ src/util/mod.rs | 4 - src/util/pig.rs | 201 --- 7 files changed, 3486 deletions(-) delete mode 100644 src/util/datetime.rs delete mode 100644 src/util/duration.rs delete mode 100644 src/util/lexer.rs delete mode 100644 src/util/mod.rs delete mode 100644 src/util/pig.rs diff --git a/src/errors.rs b/src/errors.rs index 314a7a0a5..08884376f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -4,7 +4,4 @@ use failure::Fail; pub enum Error { #[fail(display = "Task Database Error: {}", _0)] DBError(String), - - #[fail(display = "TDB2 Error: {}", _0)] - TDB2Error(String), } diff --git a/src/lib.rs b/src/lib.rs index 0cf26fda2..97b25b9a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,6 @@ mod server; mod task; mod taskdb; pub mod taskstorage; -mod util; pub use operation::Operation; pub use replica::Replica; diff --git a/src/util/datetime.rs b/src/util/datetime.rs deleted file mode 100644 index ff7ca0340..000000000 --- a/src/util/datetime.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! A re-implementation of the "Datetime" parsing utility from the Taskwarrior -//! source. - -// TODO: this module is not yet implemented - -pub(crate) struct DateTime {} - -impl DateTime { - /// Parse a datestamp from a prefix of input and return the number of bytes consumed in the - /// input - pub(crate) fn parse>( - input: S, - format: &'static str, - ) -> Option<(DateTime, usize)> { - let input = input.as_ref(); - let mut len = input.len(); - - // try parsing the whole string and repeatedly drop suffixes until a match - while len > 0 { - if let Some(str) = input.get(..len) { - match str { - "2015" => return Some((DateTime {}, len)), - "2015-" => return Some((DateTime {}, len)), - "9th" => return Some((DateTime {}, len)), - "10th" => return Some((DateTime {}, len)), - "2015-W01" => return Some((DateTime {}, len)), - "2015-02-17" => return Some((DateTime {}, len)), - "2013-11-29T22:58:00Z" => return Some((DateTime {}, len)), - "315532800" => return Some((DateTime {}, len)), - "20131129T225800Z" => return Some((DateTime {}, len)), - "today" => return Some((DateTime {}, len)), - _ => (), - } - } - len -= 1; - } - None - } -} diff --git a/src/util/duration.rs b/src/util/duration.rs deleted file mode 100644 index e7ec7fb32..000000000 --- a/src/util/duration.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! A re-implementation of the "Duration" parsing utility from the Taskwarrior -//! source. - -// TODO: this module is not yet implemented - -pub(crate) struct Duration {} - -impl Duration { - /// Parse a duration from a prefix of input and return the number of bytes consumed in the - /// input - pub(crate) fn parse>( - input: S, - format: &'static str, - ) -> Option<(Duration, usize)> { - let input = input.as_ref(); - let mut len = input.len(); - - // try parsing the whole string and repeatedly drop suffixes until a match - while len > 0 { - if let Some(str) = input.get(..len) { - match str { - "1w" => return Some((Duration {}, len)), - "4w" => return Some((Duration {}, len)), - "4weeks" => return Some((Duration {}, len)), - "5mo" => return Some((Duration {}, len)), - "6 years" => return Some((Duration {}, len)), - "3 days" => return Some((Duration {}, len)), - "1minute" => return Some((Duration {}, len)), - "2hour" => return Some((Duration {}, len)), - "1s" => return Some((Duration {}, len)), - "1second" => return Some((Duration {}, len)), - "PT23H" => return Some((Duration {}, len)), - "PT1H" => return Some((Duration {}, len)), - "P1Y" => return Some((Duration {}, len)), - "P1Y1M1DT1H1M1S" => return Some((Duration {}, len)), - "year" => return Some((Duration {}, len)), - _ => (), - } - } - len -= 1; - } - None - } -} diff --git a/src/util/lexer.rs b/src/util/lexer.rs deleted file mode 100644 index 1e04218dd..000000000 --- a/src/util/lexer.rs +++ /dev/null @@ -1,3194 +0,0 @@ -//! A re-implementation of TaskWarrior's Lexer. -//! -//! This is tested to pass that module's tests, and includes some additional tests that were also -//! verified against that module. - -use crate::util::datetime::DateTime; -use crate::util::duration::Duration; -use std::convert::TryFrom; - -const UUID_PATTERN: &[u8] = b"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; -const UUID_MIN_LENGTH: usize = 8; -const MINIMUM_MATCH_LEN: usize = 3; -const DATE_SUBELEMENTS: &[&str] = &[ - "year", "month", "day", "week", "weekday", "julian", "hour", "minute", "second", -]; - -#[derive(PartialEq, Debug, Clone, Copy)] -pub(crate) enum Type { - Uuid, - Number, - Hex, - String, - URL, - Pair, - Set, - Separator, - Tag, - Path, - Substitution, - Pattern, - Op, - DOM, - Identifier, - Word, - Date, - Duration, -} - -pub(crate) struct Lexer { - text: String, - cursor: usize, - eos: usize, - attributes: Vec, -} - -// TaskWarrior uses some non-standard character definitions, so they are repeated verbatim here, -// rather than defaulting to the unicode functions available on the char type. - -/// Returns true if this character is whitespace, as defined in TaskWarrior's libshared. -fn unicode_whitespace(c: char) -> bool { - unicode_horizontal_whitespace(c) || unicode_vertical_whitespace(c) -} - -/// Returns true if this character is horizontal whitespace, as defined in TaskWarrior's libshared. -fn unicode_horizontal_whitespace(c: char) -> bool { - let c: u32 = c.into(); - return c == 0x0020 || // space Common Separator, space - c == 0x0009 || // Common Other, control HT, Horizontal Tab - c == 0x00A0 || // no-break space Common Separator, space - c == 0x1680 || // ogham space mark Ogham Separator, space - c == 0x180E || // mongolian vowel separator Mongolian Separator, space - c == 0x2000 || // en quad Common Separator, space - c == 0x2001 || // em quad Common Separator, space - c == 0x2002 || // en space Common Separator, space - c == 0x2003 || // em space Common Separator, space - c == 0x2004 || // three-per-em space Common Separator, space - c == 0x2005 || // four-per-em space Common Separator, space - c == 0x2006 || // six-per-em space Common Separator, space - c == 0x2007 || // figure space Common Separator, space - c == 0x2008 || // punctuation space Common Separator, space - c == 0x2009 || // thin space Common Separator, space - c == 0x200A || // hair space Common Separator, space - c == 0x200B || // zero width space - c == 0x200C || // zero width non-joiner - c == 0x200D || // zero width joiner - c == 0x202F || // narrow no-break space Common Separator, space - c == 0x205F || // medium mathematical space Common Separator, space - c == 0x2060 || // word joiner - c == 0x3000; // ideographic space Common Separator, space -} - -/// Returns true if this character is vertical whitespace, as defined in TaskWarrior's libshared. -fn unicode_vertical_whitespace(c: char) -> bool { - let c: u32 = c.into(); - return c == 0x000A || // Common Other, control LF, Line feed - c == 0x000B || // Common Other, control VT, Vertical Tab - c == 0x000C || // Common Other, control FF, Form feed - c == 0x000D || // Common Other, control CR, Carriage return - c == 0x0085 || // Common Other, control NEL, Next line - c == 0x2028 || // line separator Common Separator, line - c == 0x2029; // paragraph separator Common Separator, paragraph -} - -/// Returns true if the given character is an ascii digit -fn unicode_latin_digit(c: char) -> bool { - c.is_ascii_digit() -} - -/// Returns true if the given character is an ascii letter -fn unicode_latin_alpha(c: char) -> bool { - c.is_ascii_alphabetic() -} - -/// Replicates the C function of the same name, which only recognizes ASCII printable -fn isprint(c: char) -> bool { - c.is_ascii_graphic() -} - -/// Returns true if the given character is punctuation. -fn is_punctuation(c: char) -> bool { - isprint(c) - && c != ' ' - && c != '@' - && c != '#' - && c != '$' - && c != '_' - && !unicode_latin_digit(c) - && !unicode_latin_alpha(c) -} - -/// Returns true if this character is an operator -fn is_single_char_operator(c: char) -> bool { - match c { - '+' | '-' | '*' | '/' | '(' | ')' | '<' | '>' | '^' | '!' | '%' | '=' | '~' => true, - _ => false, - } -} - -/// Returns true if this character can start an identifier -fn is_identifier_start(c: char) -> bool { - !unicode_whitespace(c) - && !unicode_latin_digit(c) - && !is_single_char_operator(c) - && !is_punctuation(c) -} - -/// Returns true if this character can be in the middle of an identifier -fn is_identifier_next(c: char) -> bool { - c != ':' && c != '=' && !unicode_whitespace(c) && !is_single_char_operator(c) -} - -/// Returns true if the sequence `` represents a token boundary. -fn is_boundary(left: char, right: char) -> bool { - right == '\0' - || (unicode_latin_alpha(left) != unicode_latin_alpha(right)) - || (unicode_latin_digit(left) != unicode_latin_digit(right)) - || (unicode_whitespace(left) != unicode_whitespace(right)) - || is_punctuation(left) - || is_punctuation(right) -} - -/// Returns true if the sequence `` represents a hard token boundary. -fn is_hard_boundary(left: char, right: char) -> bool { - right == '\0' || left == '(' || left == ')' || right == '(' || right == ')' -} - -fn is_unicode_hex_digit(c: char) -> bool { - match c { - '0'..='9' | 'a'..='f' | 'A'..='F' => true, - _ => false, - } -} - -fn hex_to_char(hex: &str) -> Option { - let mut num = 0u32; - for c in hex.chars() { - num <<= 4; - num += match c { - '0'..='9' => c as u32 - '0' as u32, - 'a'..='f' => 10 + (c as u32 - 'a' as u32), - 'A'..='F' => 10 + (c as u32 - 'A' as u32), - _ => return None, - } - } - - if let Ok(c) = char::try_from(num) { - Some(c) - } else { - None - } -} - -/// Strips matching quote symbols from the beginning and end of the given string -/// (removing all quotes if given a single quote `'`) -pub(crate) fn dequote<'a, 'b>(s: &'a str, quotes: &'b str) -> &'a str { - // note that this returns a new ref to the same string, rather - // than modifying its argument as the C++ version does. - if let Some(first_char) = s.chars().next() { - if let Some(last_char) = s.chars().rev().next() { - if first_char == last_char && quotes.contains(first_char) { - let quote_len = first_char.len_utf8(); - if s.len() > 2 * quote_len { - return &s[quote_len..s.len() - quote_len]; - } else { - return ""; - } - } - } - } - s -} - -pub(crate) fn read_word_quoted(text: &str, quotes: &str, cursor: usize) -> Option<(String, usize)> { - let mut pos = cursor; - let mut res = String::new(); - let mut skipchars = 0; - - let mut chars = text.get(cursor..)?.chars(); - let quote = chars.next(); - if quote.is_none() { - return None; - } - let quote = quote.unwrap(); - if !quotes.contains(quote) { - return None; - } - - res.push(quote); - pos += quote.len_utf8(); - - for c in chars { - if skipchars > 0 { - skipchars -= 1; - pos += c.len_utf8(); - continue; - } - if c == quote { - res.push(c); - pos += quote.len_utf8(); - return Some((res, pos)); - } - - if c == 'U' { - if let Some('+') = text.get(pos + 1..).unwrap().chars().next() { - if let Some(hex) = text.get(pos + 2..pos + 6) { - if let Some(c) = hex_to_char(hex) { - res.push(c); - skipchars += 5; - } else { - res.push('U'); - } - } else { - res.push('U'); - } - } else { - res.push('U'); - } - } else if c == '\\' { - match text.get(pos + 1..).unwrap().chars().next() { - None => res.push(c), - Some('b') => res.push('\x08'), - Some('f') => res.push('\x0c'), - Some('n') => res.push('\x0a'), - Some('r') => res.push('\x0d'), - Some('t') => res.push('\x09'), - Some('v') => res.push('\x0b'), - Some('u') => { - if let Some(hex) = text.get(pos + 2..pos + 6) { - if let Some(c) = hex_to_char(hex) { - res.push(c); - skipchars += 4; - } else { - res.push('u') - } - } else { - res.push('u') - } - } - Some(c @ _) => res.push(c), - } - skipchars += 1; - } else { - res.push(c); - } - - pos += c.len_utf8(); - } - - None -} - -pub(crate) fn read_word_unquoted(text: &str, cursor: usize) -> Option<(String, usize)> { - let mut pos = cursor; - let mut res = String::new(); - let mut prev = None; - let mut skipchars = 0; - - for c in text.get(cursor..)?.chars() { - if skipchars > 0 { - skipchars -= 1; - pos += c.len_utf8(); - prev = Some(c); - continue; - } - if unicode_whitespace(c) { - break; - } - if let Some(p) = prev { - if is_hard_boundary(p, c) { - break; - } - } - - if c == 'U' { - if let Some('+') = text.get(pos + 1..).unwrap().chars().next() { - if let Some(hex) = text.get(pos + 2..pos + 6) { - if let Some(c) = hex_to_char(hex) { - res.push(c); - skipchars += 5; - } else { - res.push('U'); - } - } else { - res.push('U'); - } - } else { - res.push('U'); - } - } else if c == '\\' { - match text.get(pos + 1..).unwrap().chars().next() { - None => res.push(c), - Some('b') => res.push('\x08'), - Some('f') => res.push('\x0c'), - Some('n') => res.push('\x0a'), - Some('r') => res.push('\x0d'), - Some('t') => res.push('\x09'), - Some('v') => res.push('\x0b'), - Some('u') => { - if let Some(hex) = text.get(pos + 2..pos + 6) { - if let Some(c) = hex_to_char(hex) { - res.push(c); - skipchars += 4; - } else { - res.push('u') - } - } else { - res.push('u') - } - } - Some(c @ _) => res.push(c), - } - skipchars += 1; - } else { - res.push(c); - } - - pos += c.len_utf8(); - prev = Some(c); - } - - if pos != cursor { - Some((res, pos)) - } else { - None - } -} - -fn common_length(s1: &str, s2: &str) -> usize { - s1.chars() - .zip(s2.chars()) - .take_while(|(c1, c2)| c1 == c2) - .collect::>() - .len() -} - -/// Returns true if the given string must have been shell-quoted -pub(crate) fn was_quoted(s: &str) -> bool { - s.contains(&[' ', '\t', '(', ')', '<', '>', '&', '~'][..]) -} - -#[derive(Debug, PartialEq)] -pub(crate) struct DecomposedPair { - pub(crate) name: String, - pub(crate) modifier: String, - pub(crate) separator: String, - pub(crate) value: String, -} - -/// Parse ("decompose") a pair into its constituent parts. This assumes the text is a valid pair -/// string. -pub(crate) fn decompose_pair(text: &str) -> Option { - let npos = usize::max_value(); - let dot = text.find(".").unwrap_or(npos); - let sep_defer = text.find("::").unwrap_or(npos); - let sep_eval = text.find(":=").unwrap_or(npos); - let sep_colon = text.find(":").unwrap_or(npos); - let sep_equal = text.find("=").unwrap_or(npos); - - let (sep, sep_end) = if sep_defer != npos - && sep_defer <= sep_eval - && sep_defer <= sep_colon - && sep_defer <= sep_equal - { - (sep_defer, sep_defer + 2) - } else if sep_eval != npos - && sep_eval <= sep_defer - && sep_eval <= sep_colon - && sep_eval <= sep_equal - { - (sep_eval, sep_eval + 2) - } else if sep_colon != npos - && sep_colon <= sep_defer - && sep_colon <= sep_eval - && sep_colon <= sep_equal - { - (sep_colon, sep_colon + 1) - } else if sep_equal != npos - && sep_equal <= sep_defer - && sep_equal <= sep_eval - && sep_equal <= sep_colon - { - (sep_equal, sep_equal + 1) - } else { - return None; - }; - - let (name, modifier) = if dot != npos && dot < sep { - ( - text.get(0..dot).unwrap().into(), - text.get(dot + 1..sep).unwrap().into(), - ) - } else { - (text.get(0..sep).unwrap().into(), "".into()) - }; - - let separator = text.get(sep..sep_end).unwrap().into(); - let value = text.get(sep_end..).unwrap().into(); - - Some(DecomposedPair { - name, - modifier, - separator, - value, - }) -} - -#[derive(Debug, PartialEq)] -pub(crate) struct DecomposedSubstitution { - pub(crate) from: String, - pub(crate) to: String, - pub(crate) flags: String, -} - -/// Parse ("decompose") a substitution into its constituent parts. This assumes -/// the text is a valid substitution string. -pub(crate) fn decompose_substitution(text: &str) -> Option { - let mut cursor = 0; - if let Some((from, from_curs)) = read_word_quoted(text, "/", cursor) { - cursor = from_curs - 1; - if let Some((to, to_curs)) = read_word_quoted(text, "/", cursor) { - cursor = to_curs; - let from = dequote(&from, "/").into(); - let to = dequote(&to, "/").into(); - let flags = text[cursor..].into(); - return Some(DecomposedSubstitution { from, to, flags }); - } - } - None -} - -#[derive(Debug, PartialEq)] -pub(crate) struct DecomposedPattern { - pub(crate) pattern: String, - pub(crate) flags: String, -} - -/// Parse ("decompose") a pattern into its constituent parts. This assumes the text is a valid -/// pattern string. -pub(crate) fn decompose_pattern(text: &str) -> Option { - let mut cursor = 0; - if let Some((pattern, pattern_curs)) = read_word_quoted(text, "/", cursor) { - cursor = pattern_curs; - let pattern = dequote(&pattern, "/").into(); - let flags = text[cursor..].into(); - return Some(DecomposedPattern { pattern, flags }); - } - None -} - -impl Lexer { - pub fn new>(text: S) -> Lexer { - let text = text.into(); - let eos = text.len(); - Lexer { - text, - cursor: 0, - eos, - attributes: vec![], - } - } - - pub fn add_attribute>(&mut self, attribute: S) { - self.attributes.push(attribute.into()); - } - - /// This static method tokenizes the input, but discards the type information. - pub fn split>(text: S) -> Vec { - Lexer::new(text).into_iter().map(|(tx, ty)| tx).collect() - } - - pub fn token(&mut self) -> Option<(String, Type)> { - // Eat whitespace - while let Some(c) = self.text[self.cursor..].chars().next() { - if unicode_whitespace(c) { - self.cursor += c.len_utf8(); - continue; - } - break; - } - - if self.cursor == self.eos { - return None; - } - - // The sequence is specific, and must follow these rules: - // - date < duration < uuid < identifier - // - dom < uuid - // - uuid < hex < number - // - url < pair < identifier - // - hex < number - // - separator < tag < operator - // - path < substitution < pattern - // - set < number - // - word last - if let Some(r) = self.is_string("\"'") { - return Some(r); - } - if let Some(r) = self.is_date() { - return Some(r); - } - if let Some(r) = self.is_duration() { - return Some(r); - } - if let Some(r) = self.is_url() { - return Some(r); - } - if let Some(r) = self.is_pair() { - return Some(r); - } - if let Some(r) = self.is_uuid(true) { - return Some(r); - } - if let Some(r) = self.is_set() { - return Some(r); - } - if let Some(r) = self.is_dom() { - return Some(r); - } - if let Some(r) = self.is_hexnumber() { - return Some(r); - } - if let Some(r) = self.is_number() { - return Some(r); - } - if let Some(r) = self.is_separator() { - return Some(r); - } - if let Some(r) = self.is_tag() { - return Some(r); - } - if let Some(r) = self.is_path() { - return Some(r); - } - if let Some(r) = self.is_substitution() { - return Some(r); - } - if let Some(r) = self.is_pattern() { - return Some(r); - } - if let Some(r) = self.is_operator() { - return Some(r); - } - if let Some(r) = self.is_identifier() { - return Some(r); - } - if let Some(r) = self.is_word() { - return Some(r); - } - None - } - - pub fn is_eos(&self) -> bool { - self.cursor == self.eos - } - - // recognizers for the `token` method - - fn is_string(&mut self, quotes: &str) -> Option<(String, Type)> { - if let Some((s, pos)) = read_word_quoted(&self.text, quotes, self.cursor) { - self.cursor = pos; - return Some((s, Type::String)); - } - None - } - - fn is_date(&mut self) -> Option<(String, Type)> { - let (_, read) = DateTime::parse(&self.text[self.cursor..], "")?; - let token = self.text[self.cursor..self.cursor + read].into(); - self.cursor += read; - Some((token, Type::Date)) - } - - fn is_duration(&mut self) -> Option<(String, Type)> { - let marker = self.cursor; - - if self.is_operator().is_some() { - self.cursor = marker; - return None; - } - - let (_, read) = Duration::parse(&self.text[self.cursor..], "")?; - let token = self.text[self.cursor..self.cursor + read].into(); - self.cursor += read; - Some((token, Type::Duration)) - } - - fn is_url(&mut self) -> Option<(String, Type)> { - let remainder = &self.text[self.cursor..]; - if remainder.starts_with("https://") || remainder.starts_with("http://") { - if let Some(i) = remainder.find(unicode_whitespace) { - let token = &remainder[..i]; - self.cursor += i; - return Some((token.into(), Type::URL)); - } else { - self.cursor = self.eos; - return Some((remainder.into(), Type::URL)); - } - } - None - } - - fn is_pair(&mut self) -> Option<(String, Type)> { - let marker = self.cursor; - if self.is_identifier().is_some() { - let separator = &self.text[self.cursor..]; - if separator.starts_with("::") || separator.starts_with(":=") { - self.cursor += 2; - } else if separator.starts_with(":") || separator.starts_with("=") { - self.cursor += 1; - } else { - self.cursor = marker; - return None; - } - - // String, word, or nothing are all valid - let marker2 = self.cursor; - if let Some((word, end)) = read_word_quoted(&self.text[..], "'\"", self.cursor) { - self.cursor = end; - return Some(( - format!("{}{}", &self.text[marker..marker2], word), - Type::Pair, - )); - } - if let Some((word, end)) = read_word_unquoted(&self.text[..], self.cursor) { - self.cursor = end; - return Some(( - format!("{}{}", &self.text[marker..marker2], word), - Type::Pair, - )); - } - if self.cursor == self.eos - || unicode_whitespace(self.text[self.cursor..].chars().next().unwrap()) - { - return Some((self.text[marker..self.cursor].into(), Type::Pair)); - } - } - self.cursor = marker; - None - } - - fn is_uuid(&mut self, end_boundary: bool) -> Option<(String, Type)> { - let mut i = 0; - for c in self.text[self.cursor..].chars() { - if UUID_PATTERN[i] == b'x' { - if !is_unicode_hex_digit(c) { - break; - } - } else { - if c != '-' { - break; - } - } - i += 1; - if i >= UUID_PATTERN.len() { - break; - } - } - - if i < UUID_MIN_LENGTH { - return None; - } - - if end_boundary { - let c = self.text[self.cursor + i..].chars().next(); - if let Some(c) = c { - if !unicode_whitespace(c) && !is_single_char_operator(c) { - return None; - } - } - } - - let token = self.text[self.cursor..self.cursor + i].into(); - self.cursor += i; - Some((token, Type::Uuid)) - } - - fn is_set(&mut self) -> Option<(String, Type)> { - let marker = self.cursor; - let mut count = 0; - loop { - if self.is_integer().is_some() { - count += 1; - if self.is_literal("-", false, false) { - if self.is_integer().is_some() { - count += 1; - } else { - self.cursor = marker; - return None; - } - } - } else { - self.cursor = marker; - return None; - } - if !self.is_literal(",", false, false) { - break; - } - } - - if count <= 1 { - self.cursor = marker; - return None; - } - - // -1 is OK here since integers are ASCII - let last_char = self.text[self.cursor - 1..].chars().next().unwrap(); - - // look ahead a bit - match self.text[self.cursor..].chars().next() { - Some(c) if !unicode_whitespace(c) && !is_hard_boundary(last_char, c) => { - self.cursor = marker; - return None; - } - _ => (), - } - - Some((self.text[marker..self.cursor].into(), Type::Set)) - } - - fn is_dom(&mut self) -> Option<(String, Type)> { - let marker = self.cursor; - - // rc. ... - if self.is_literal("rc.", false, false) && self.is_word().is_some() { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } else { - self.cursor = marker; - } - - // Literals - if self.is_one_of( - &vec![ - "tw.syncneeded", - "tw.program", - "tw.args", - "tw.width", - "tw.height", - "tw.version", - "context.program", - "context.args", - "context.width", - "context.height", - "system.version", - "system.os", - ], - false, - true, - ) { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } - - // Optional: - // . - // . - if self.is_uuid(false).is_some() || self.is_integer().is_some() { - if !self.is_literal(".", false, false) { - self.cursor = marker; - return None; - } - } - - // Any failure after this line should rollback to the checkpoint. - let checkpoint = self.cursor; - - // [prefix]tags. - if self.is_literal("tags", false, false) - && self.is_literal(".", false, false) - && self.is_word().is_some() - { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } else { - self.cursor = checkpoint; - } - - // [prefix]attribute (bounded) - // (have to clone here to avoid double-borrowing self - let attributes = self.attributes.clone(); - if self.is_one_of(&attributes, false, true) { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } - - // [prefix]attribute. (unbounded) - if self.is_one_of(&attributes, false, false) { - if self.is_literal(".", false, false) { - let attribute = &self.text[checkpoint..self.cursor - 1]; - // if attribute type is 'date', then it has sub-elements. - if attribute == "date" && self.is_one_of(&DATE_SUBELEMENTS, false, true) { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } - self.cursor = checkpoint; - } - // Lookahead: ! - else if !self.text[marker..] - .chars() - .next() - .map_or(false, |c| unicode_latin_alpha(c)) - { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } - self.cursor = checkpoint; - } - - // [prefix]annotations. - if self.is_literal("annotations", true, false) && self.is_literal(".", false, false) { - if self.is_literal("count", false, false) { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } - - if self.is_integer().is_some() { - if self.is_literal(".", false, false) { - if self.is_literal("description", false, true) { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } else if self.is_literal("entry", false, true) { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } else if self.is_literal("entry", false, false) - && self.is_literal(".", false, false) - && self.is_one_of(&DATE_SUBELEMENTS, false, true) - { - return Some((self.text[marker..self.cursor].into(), Type::DOM)); - } - } - } else { - self.cursor = checkpoint; - } - } - - self.cursor = marker; - None - } - - fn is_hexnumber(&mut self) -> Option<(String, Type)> { - let remainder = &self.text[self.cursor..]; - - if !remainder.starts_with("0x") { - return None; - } - let mut end = 2; - for (i, c) in remainder[2..].char_indices() { - if is_unicode_hex_digit(c) { - end = 2 + i + c.len_utf8(); - } else { - break; - } - } - if end > 2 { - self.cursor += end; - Some((remainder[..end].into(), Type::Hex)) - } else { - None - } - } - - fn is_number(&mut self) -> Option<(String, Type)> { - let remainder = &self.text[self.cursor..]; - let mut chars = remainder.char_indices().peekable(); - let mut marker = 0; - - // A hand-rolled regexp. States are as follows: - // \d \d* (. \d \d*)? ([eE] [+-]? \d \d* (. \d \d*)?)? - // 0 1 2 3 4 5 6 7 8 9 10 11 12 - let mut state = 0; - - loop { - let c = match chars.peek() { - Some((i, c)) => { - marker = *i; - Some(*c) - } - None => None, - }; - match (state, c) { - (0, Some(c)) if unicode_latin_digit(c) => state = 1, - - (1, Some(c)) if unicode_latin_digit(c) => state = 2, - (1, Some(c)) if c == '.' => state = 3, - (1, Some(c)) if c == 'e' || c == 'E' => state = 6, - (1, _) => break, - - (2, Some(c)) if unicode_latin_digit(c) => state = 2, - (2, Some(c)) if c == '.' => state = 3, - (2, Some(c)) if c == 'e' || c == 'E' => state = 6, - (2, _) => break, - - (3, Some(c)) if unicode_latin_digit(c) => state = 4, - (3, Some(c)) if c == 'e' || c == 'E' => state = 6, - (3, _) => break, - - (4, Some(c)) if unicode_latin_digit(c) => state = 5, - (4, Some(c)) if c == 'e' || c == 'E' => state = 6, - (4, _) => break, - - (5, Some(c)) if unicode_latin_digit(c) => state = 5, - (5, Some(c)) if c == 'e' || c == 'E' => state = 6, - (5, _) => break, - - (6, Some(c)) if unicode_latin_digit(c) => state = 8, - (6, Some(c)) if c == '-' || c == '+' => state = 7, - (6, _) => break, - - (7, Some(c)) if unicode_latin_digit(c) => state = 8, - (7, _) => break, - - (8, Some(c)) if unicode_latin_digit(c) => state = 9, - (8, Some(c)) if c == '.' => state = 10, - (8, _) => break, - - (9, Some(c)) if unicode_latin_digit(c) => state = 9, - (9, Some(c)) if c == '.' => state = 10, - (9, _) => break, - - (10, Some(c)) if unicode_latin_digit(c) => state = 11, - (10, _) => break, - - (11, Some(c)) if unicode_latin_digit(c) => state = 11, - (11, _) => break, - - _ => return None, - }; - if let Some((i, c)) = chars.next() { - marker = i + c.len_utf8(); - } - } - // lookahead - if let Some((_, c)) = chars.peek() { - if !unicode_whitespace(*c) && !is_single_char_operator(*c) { - return None; - } - } - self.cursor += marker; - Some((remainder[..marker].into(), Type::Number)) - } - - fn is_separator(&mut self) -> Option<(String, Type)> { - let next_chars = self - .text - .get(self.cursor..self.cursor + 2)? - .chars() - .collect::>(); - if &next_chars[..] == &['-', '-'] { - self.cursor += 2; - return Some(("--".into(), Type::Separator)); - } - None - } - - fn is_tag(&mut self) -> Option<(String, Type)> { - let mut marker = self.cursor; - - // Lookbehind: Assert ^ or preceded by whitespace, (, or ). - if marker > 0 { - // if the previous byte is not a valid character, then it's - // not ( or ) - if let Some(lookbehind) = self.text.get(self.cursor - 1..) { - if let Some(c) = lookbehind.chars().next() { - if !unicode_whitespace(c) && c != '(' && c != ')' { - return None; - } - } - } else { - return None; - } - } - - let mut chars = self.text[marker..].chars(); - if let Some(c) = chars.next() { - if c == '+' || c == '-' { - marker += c.len_utf8(); - if let Some(c) = chars.next() { - if is_identifier_start(c) { - marker += c.len_utf8(); - while let Some(c) = chars.next() { - if !is_identifier_next(c) { - break; - } - marker += c.len_utf8(); - } - let token = self.text[self.cursor..marker].into(); - self.cursor = marker; - return Some((token, Type::Tag)); - } - } - } - } - - None - } - - fn is_path(&mut self) -> Option<(String, Type)> { - let mut marker = self.cursor; - let mut slash_count = 0; - let mut chars = self.text[self.cursor..].chars().peekable(); - - loop { - if let Some('/') = chars.next() { - marker += 1; - slash_count += 1; - } else { - break; - } - - if let Some(c) = chars.next() { - if !unicode_whitespace(c) && c != '/' { - marker += 1; - while let Some(c) = chars.peek() { - if !unicode_whitespace(*c) && *c != '/' { - marker += 1; - chars.next(); - } else { - break; - } - } - } else { - break; - } - } else { - break; - } - } - - if marker > self.cursor && slash_count > 3 { - let token = self.text[self.cursor..marker].into(); - self.cursor = marker; - return Some((token, Type::Path)); - } - - None - } - - fn is_substitution(&mut self) -> Option<(String, Type)> { - let marker = self.cursor; - - if let Some((_, end)) = read_word_quoted(&self.text, "/", self.cursor) { - // end-1 to step back over the middle `/` - if let Some((_, end)) = read_word_quoted(&self.text, "/", end - 1) { - let mut remainder = self.text[end..].chars(); - return match remainder.next() { - None => { - self.cursor = end; - Some((self.text[marker..self.cursor].into(), Type::Substitution)) - } - Some('g') => match remainder.next() { - None => { - self.cursor = end + 1; - Some((self.text[marker..self.cursor].into(), Type::Substitution)) - } - Some(c) if unicode_whitespace(c) => { - self.cursor = end + 1; - Some((self.text[marker..self.cursor].into(), Type::Substitution)) - } - _ => None, - }, - Some(c) if unicode_whitespace(c) => { - self.cursor = end; - Some((self.text[marker..self.cursor].into(), Type::Substitution)) - } - _ => None, - }; - } - } - - None - } - - fn is_pattern(&mut self) -> Option<(String, Type)> { - let marker = self.cursor; - if let Some((_, end)) = read_word_quoted(&self.text, "/", self.cursor) { - if end == self.eos || unicode_whitespace(self.text[end..].chars().next().unwrap()) { - self.cursor = end; - return Some((self.text[marker..self.cursor].into(), Type::Pattern)); - } - } - None - } - - fn is_operator(&mut self) -> Option<(String, Type)> { - let remainder = &self.text[self.cursor..]; - - // operators that do not require a boundary afterward - for strop in &[ - // custom stuff - "_hastag_", "_notag_", "_neg_", "_pos_", - // triple-char - "!==", // and, xor below - // double-char - "==", "!=", "<=", ">=", "||", "&&", "!~", // or below - // single-char - "+", "-", "*", "/", "(", ")", "<", ">", "^", "!", "%", "=", "~", - ] { - if remainder.starts_with(strop) { - self.cursor += strop.len(); - return Some((remainder[..strop.len()].into(), Type::Op)); - } - } - - // operators that require a boundary afterward - for strop in &["and", "xor", "!==", "or"] { - if remainder.starts_with(strop) { - if self.cursor + strop.len() == self.eos - || is_boundary( - remainder[strop.len() - 1..].chars().next().unwrap(), - remainder[strop.len()..].chars().next().unwrap(), - ) - { - self.cursor += strop.len(); - return Some((remainder[..strop.len()].into(), Type::Op)); - } - } - } - None - } - - fn is_identifier(&mut self) -> Option<(String, Type)> { - let mut chars = self.text.get(self.cursor..)?.chars(); - let start = self.cursor; - let mut len = 0; - - if let Some(c) = chars.next() { - if is_identifier_start(c) { - len += c.len_utf8(); - for c in chars { - if !is_identifier_next(c) { - break; - } - len += c.len_utf8(); - } - self.cursor += len; - return Some((self.text.get(start..self.cursor)?.into(), Type::Identifier)); - } - } - - None - } - - fn is_word(&mut self) -> Option<(String, Type)> { - let mut marker = self.cursor; - for c in self.text[self.cursor..].chars() { - if unicode_whitespace(c) || is_single_char_operator(c) { - break; - } - marker += c.len_utf8(); - } - - if marker > self.cursor { - let token = self.text[self.cursor..marker].into(); - self.cursor = marker; - return Some((token, Type::Word)); - } - - None - } - - // utilities that may modify self - - fn is_one_of>( - &mut self, - options: &[S], - allow_abbreviations: bool, - end_boundary: bool, - ) -> bool { - for option in options { - if self.is_literal(option.as_ref(), allow_abbreviations, end_boundary) { - return true; - } - } - false - } - - fn is_literal(&mut self, literal: &str, allow_abbreviations: bool, end_boundary: bool) -> bool { - // calculate the number of common characters between the literal and the string being - // parsed - let common = common_length(literal, &self.text[self.cursor..]); - - // Without abbreviations, common must equal literal length. - if !allow_abbreviations && common < literal.len() { - return false; - } - - if allow_abbreviations && common < MINIMUM_MATCH_LEN { - return false; - } - - if end_boundary { - let c = self.text[self.cursor + common..].chars().next(); - if let Some(c) = c { - if !unicode_whitespace(c) && !is_single_char_operator(c) { - return false; - } - } - } - - self.cursor += common; - - true - } - - fn is_integer(&mut self) -> Option<(String, Type)> { - let mut marker = self.cursor; - for c in self.text[self.cursor..].chars() { - if !unicode_latin_digit(c) { - break; - } - marker += c.len_utf8(); - } - - if marker > self.cursor { - let token = self.text[self.cursor..marker].into(); - self.cursor = marker; - return Some((token, Type::Number)); - } - - None - } -} - -pub(crate) struct LexerIterator(Lexer); - -impl Iterator for LexerIterator { - type Item = (String, Type); - - fn next(&mut self) -> Option { - self.0.token() - } -} - -impl IntoIterator for Lexer { - type Item = (String, Type); - type IntoIter = LexerIterator; - - fn into_iter(self) -> Self::IntoIter { - LexerIterator(self) - } -} - -#[cfg(test)] -mod test { - use super::*; - const NONE: Option<(String, Type)> = None; - - #[test] - fn test_is_punctuation_comma() { - assert!(is_punctuation(',')); - } - - #[test] - fn test_is_punctuation_slash() { - assert!(is_punctuation('/')); - } - - #[test] - fn test_is_punctuation_at() { - assert!(!is_punctuation('@')); - } - - #[test] - fn test_is_punctuation_hash() { - assert!(!is_punctuation('#')); - } - - #[test] - fn test_is_punctuation_dollar() { - assert!(!is_punctuation('$')); - } - - #[test] - fn test_is_punctuation_underscore() { - assert!(!is_punctuation('_')); - } - - #[test] - fn test_is_punctuation_space() { - assert!(!is_punctuation(' ')); - } - - #[test] - fn test_is_punctuation_a() { - assert!(!is_punctuation('a')); - } - - #[test] - fn test_is_punctuation_9() { - assert!(!is_punctuation('9')); - } - - #[test] - fn test_is_punctuation_latin() { - assert!(!is_punctuation('é')); - } - - #[test] - fn test_is_punctuation_euro() { - assert!(!is_punctuation('€')); - } - - #[test] - fn test_is_punctuation_smile() { - assert!(!is_punctuation('☺')); - } - - #[test] - fn test_is_punctuation_numeric() { - assert!(!is_punctuation('¾')); - } - - #[test] - fn test_is_boundary() { - assert!(is_boundary(' ', 'a')); - assert!(is_boundary('a', ' ')); - assert!(is_boundary(' ', '+')); - assert!(is_boundary(' ', ',')); - assert!(!is_boundary('3', '4')); - assert!(is_boundary('(', '(')); - assert!(!is_boundary('r', 'd')); - } - - #[test] - fn test_was_quoted() { - assert!(!was_quoted("")); - assert!(!was_quoted("foo")); - assert!(was_quoted("a b")); - assert!(was_quoted("(a)")); - } - - #[test] - fn test_dequote() { - assert_eq!(dequote("foo", "'\""), "foo"); - assert_eq!(dequote("'foo'", "'\""), "foo"); - assert_eq!(dequote("\"foo\"", "'\""), "foo"); - assert_eq!(dequote("'o\\'clock'", "'\""), "o\\'clock"); - // single quote char - assert_eq!(dequote("'", "'\""), ""); - // multibyte quote char - assert_eq!(dequote("éo\\'clocké", "é"), "o\\'clock"); - } - - #[test] - fn test_token_empty() { - let mut l = Lexer::new(""); - assert_eq!(l.token(), NONE); - assert!(l.is_eos()); - } - - #[test] - fn test_token_tokens() { - let mut l = Lexer::new( - " one 'two \\'three\\''+456-(1.3*2 - 0x12) 1.2e-3.4 foo.bar and '\\u20ac'", - ); - assert!(!l.is_eos()); - assert_eq!(l.token(), Some((String::from("one"), Type::Identifier))); - assert_eq!( - l.token(), - Some((String::from("'two 'three''"), Type::String)) - ); - assert_eq!(l.token(), Some((String::from("+"), Type::Op))); - assert_eq!(l.token(), Some((String::from("456"), Type::Number))); - assert_eq!(l.token(), Some((String::from("-"), Type::Op))); - assert_eq!(l.token(), Some((String::from("("), Type::Op))); - assert_eq!(l.token(), Some((String::from("1.3"), Type::Number))); - assert_eq!(l.token(), Some((String::from("*"), Type::Op))); - assert_eq!(l.token(), Some((String::from("2"), Type::Number))); - assert_eq!(l.token(), Some((String::from("-"), Type::Op))); - assert_eq!(l.token(), Some((String::from("0x12"), Type::Hex))); - assert_eq!(l.token(), Some((String::from(")"), Type::Op))); - assert_eq!(l.token(), Some((String::from("1.2e-3.4"), Type::Number))); - assert_eq!(l.token(), Some((String::from("foo.bar"), Type::Identifier))); - assert_eq!(l.token(), Some((String::from("and"), Type::Op))); - assert_eq!(l.token(), Some((String::from("'€'"), Type::String))); - assert_eq!(l.token(), None); - assert!(l.is_eos()); - } - - #[test] - fn test_token_short_numbers() { - let mut l = Lexer::new("1 12 123 1234 12345 123456 1234567 123.45e 12.34e+"); - assert_eq!(l.token(), Some((String::from("1"), Type::Number))); - assert_eq!(l.token(), Some((String::from("12"), Type::Number))); - assert_eq!(l.token(), Some((String::from("123"), Type::Number))); - assert_eq!(l.token(), Some((String::from("1234"), Type::Number))); - assert_eq!(l.token(), Some((String::from("12345"), Type::Number))); - assert_eq!(l.token(), Some((String::from("123456"), Type::Number))); - assert_eq!(l.token(), Some((String::from("1234567"), Type::Number))); - assert_eq!(l.token(), Some((String::from("123.45e"), Type::Number))); - assert_eq!(l.token(), Some((String::from("12.34e+"), Type::Number))); - assert_eq!(l.token(), None); - } - - #[test] - fn test_read_word_quoted_simple() { - assert_eq!( - read_word_quoted("'one two'", "'\"", 0), - Some((String::from("'one two'"), 9)) - ); - } - - #[test] - fn test_read_word_quoted_unterminated() { - assert_eq!( - read_word_quoted("'one two", "'\"", 0), - None as Option<(String, usize)> - ); - } - - #[test] - fn test_read_word_quoted_backslash_u() { - assert_eq!( - read_word_quoted("'pay \\u20a43'", "'\"", 0), - Some((String::from("'pay ₤3'"), 13)) - ); - } - - #[test] - fn test_read_word_quoted_u_plus() { - assert_eq!( - read_word_quoted("\"pay U+20AC5\"", "'\"", 0), - Some((String::from("\"pay €5\""), 13)) - ); - } - - #[test] - fn test_read_word_unquoted_simple() { - assert_eq!( - read_word_unquoted("input", 0), - Some((String::from("input"), 5)) - ); - } - - #[test] - fn test_read_word_unquoted_escaped_space() { - assert_eq!( - read_word_unquoted("one\\ two", 0), - Some((String::from("one two"), 8)) - ); - } - - #[test] - fn test_read_word_unquoted_escaped_quote() { - assert_eq!( - read_word_unquoted("one\\\"two", 0), - Some((String::from("one\"two"), 8)) - ); - } - - #[test] - fn test_read_word_unquoted_escaped_newline() { - assert_eq!( - read_word_unquoted("one\\ntwo", 0), - Some((String::from("one\x0atwo"), 8)) - ); - } - - #[test] - fn test_read_word_unquoted_escaped_backslash_u() { - assert_eq!( - read_word_unquoted("pay\\u20a43", 0), - Some((String::from("pay₤3"), 10)) - ); - } - - #[test] - fn test_read_word_unquoted_incomplete_escaped_backslash_u() { - assert_eq!( - read_word_unquoted("\\u203", 0), - Some((String::from("u203"), 5)) - ); - } - - #[test] - fn test_read_word_unquoted_nonhex_escaped_backslash_u() { - assert_eq!( - read_word_unquoted("\\u2fghk", 0), - Some((String::from("u2fghk"), 7)) - ); - } - - #[test] - fn test_read_word_unquoted_escaped_u_plus() { - assert_eq!( - read_word_unquoted("payU+20AC4", 0), - Some((String::from("pay€4"), 10)) - ); - } - - #[test] - fn test_read_word_unquoted_incomplete_u_plus() { - assert_eq!( - read_word_unquoted("U+20A", 0), - Some((String::from("U+20A"), 5)) - ); - } - - #[test] - fn test_read_word_trailing_whitespace() { - assert_eq!( - read_word_unquoted("one ", 0), - Some((String::from("one"), 3)) - ); - } - - #[test] - fn test_read_word_unquoted_several_words() { - let text = "one 'two' three\\ four"; - assert_eq!(read_word_unquoted(text, 0), Some((String::from("one"), 3))); - assert_eq!( - read_word_unquoted(text, 4), - Some((String::from("'two'"), 9)) - ); - assert_eq!( - read_word_unquoted(text, 10), - Some((String::from("three four"), 21)) - ); - } - - #[test] - fn test_common_length_empty() { - assert_eq!(common_length("", ""), 0); - } - - #[test] - fn test_common_length_match_one() { - assert_eq!(common_length("a", "a"), 1); - } - - #[test] - fn test_common_length_match_longer() { - assert_eq!(common_length("abcde", "abcde"), 5); - } - - #[test] - fn test_common_length_match_s2_short() { - assert_eq!(common_length("abc", ""), 0); - } - - #[test] - fn test_common_length_match_differ() { - assert_eq!(common_length("abc", "def"), 0); - } - - #[test] - fn test_common_length_match_s2_prefix() { - assert_eq!(common_length("foobar", "foo"), 3); - } - - #[test] - fn test_common_length_match_s1_prefix() { - assert_eq!(common_length("foo", "foobar"), 3); - } - - #[test] - fn test_is_string() { - let mut l = Lexer::new("'one'"); - assert_eq!(l.is_string("'\""), Some(("'one'".into(), Type::String))); - assert_eq!(l.cursor, 5); - } - - #[test] - fn test_is_string_negative() { - let mut l = Lexer::new("one"); - assert_eq!(l.is_string("'\""), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_string_empty() { - let mut l = Lexer::new("''"); - assert_eq!(l.is_string("'\""), Some(("''".into(), Type::String))); - assert_eq!(l.cursor, 2); - } - - #[test] - fn test_is_string_escape() { - let mut l = Lexer::new("'one\ttwo'"); - assert_eq!( - l.is_string("'\""), - Some(("'one\ttwo'".into(), Type::String)) - ); - assert_eq!(l.cursor, 9); - } - - #[test] - fn test_is_date_year_eos() { - let mut l = Lexer::new("2015"); - assert_eq!(l.is_date(), Some(("2015".into(), Type::Date))); - assert_eq!(l.cursor, 4); - } - - #[test] - fn test_is_date_epoch() { - let mut l = Lexer::new("315532800"); - assert_eq!(l.is_date(), Some(("315532800".into(), Type::Date))); - assert_eq!(l.cursor, 9); - } - - #[test] - fn test_is_date_year_ws() { - let mut l = Lexer::new("2015 "); - assert_eq!(l.is_date(), Some(("2015".into(), Type::Date))); - assert_eq!(l.cursor, 4); - } - - #[test] - fn test_is_date_year_ident() { - let mut l = Lexer::new("2015abc"); - assert_eq!(l.is_date(), Some(("2015".into(), Type::Date))); - assert_eq!(l.cursor, 4); - } - - #[test] - fn test_is_date_year_plus() { - let mut l = Lexer::new("2015+"); - assert_eq!(l.is_date(), Some(("2015".into(), Type::Date))); - assert_eq!(l.cursor, 4); - } - - #[test] - fn test_is_date_year_minus() { - let mut l = Lexer::new("2015-xyz"); - assert_eq!(l.is_date(), Some(("2015-".into(), Type::Date))); - assert_eq!(l.cursor, 5); - } - - #[test] - fn test_is_duration_1w() { - let mut l = Lexer::new("1w"); - assert_eq!(l.is_duration(), Some(("1w".into(), Type::Duration))); - assert_eq!(l.cursor, 2); - } - - #[test] - fn test_is_duration_op() { - let mut l = Lexer::new("!!"); - assert_eq!(l.is_duration(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_number_digit() { - let mut l = Lexer::new("3"); - assert_eq!(l.is_number(), Some(("3".into(), Type::Number))); - assert_eq!(l.cursor, 1); - } - - #[test] - fn test_is_number_integer() { - let mut l = Lexer::new("13"); - assert_eq!(l.is_number(), Some(("13".into(), Type::Number))); - assert_eq!(l.cursor, 2); - } - - #[test] - fn test_is_number_trailing_minus() { - let mut l = Lexer::new("13-"); - assert_eq!(l.is_number(), Some(("13".into(), Type::Number))); - assert_eq!(l.cursor, 2); - } - - #[test] - fn test_is_number_decimal() { - let mut l = Lexer::new("1.3"); - assert_eq!(l.is_number(), Some(("1.3".into(), Type::Number))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_number_multiple_decimal() { - let mut l = Lexer::new("1.3.4"); - assert_eq!(l.is_number(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_number_decimal_no_digits() { - let mut l = Lexer::new("1."); - assert_eq!(l.is_number(), Some(("1.".into(), Type::Number))); - assert_eq!(l.cursor, 2); - } - - #[test] - fn test_is_number_decimal_multi_digit() { - let mut l = Lexer::new("12.32"); - assert_eq!(l.is_number(), Some(("12.32".into(), Type::Number))); - assert_eq!(l.cursor, 5); - } - - #[test] - fn test_is_number_decimal_e_no_exponent() { - let mut l = Lexer::new("12.32e"); - assert_eq!(l.is_number(), Some(("12.32e".into(), Type::Number))); - assert_eq!(l.cursor, 6); - } - - #[test] - fn test_is_number_decimal_e_plus_no_exponent() { - let mut l = Lexer::new("12.32e+"); - assert_eq!(l.is_number(), Some(("12.32e+".into(), Type::Number))); - assert_eq!(l.cursor, 7); - } - - #[test] - fn test_is_number_decimal_e_integer_exponent() { - let mut l = Lexer::new("12.32e-12"); - assert_eq!(l.is_number(), Some(("12.32e-12".into(), Type::Number))); - assert_eq!(l.cursor, 9); - } - - #[test] - fn test_is_number_decimal_e_decimal_exponent() { - let mut l = Lexer::new("12.32e12.34"); - assert_eq!(l.is_number(), Some(("12.32e12.34".into(), Type::Number))); - assert_eq!(l.cursor, 11); - } - - #[test] - fn test_is_number_integer_invalid_lookahead() { - let mut l = Lexer::new("13a"); - assert_eq!(l.is_number(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_set_singletons() { - let mut l = Lexer::new("12,13"); - assert_eq!(l.is_set(), Some(("12,13".into(), Type::Set))); - assert_eq!(l.cursor, 5); - } - - #[test] - fn test_is_set_ranges() { - let mut l = Lexer::new("12-13,19-200"); - assert_eq!(l.is_set(), Some(("12-13,19-200".into(), Type::Set))); - assert_eq!(l.cursor, 12); - } - - #[test] - fn test_is_set_double_comma() { - let mut l = Lexer::new("12-13,,19-200"); - assert_eq!(l.is_set(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_set_trailing_comma() { - let mut l = Lexer::new("12-13,"); - assert_eq!(l.is_set(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_set_trailing_ws() { - let mut l = Lexer::new("12-13 "); - assert_eq!(l.is_set(), Some(("12-13".into(), Type::Set))); - assert_eq!(l.cursor, 5); - } - - #[test] - fn test_is_set_trailing_non_hard_boundary() { - let mut l = Lexer::new("12-13abc"); - assert_eq!(l.is_set(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_separator() { - let mut l = Lexer::new(" -- "); - l.cursor = 2; - assert_eq!(l.is_separator(), Some(("--".into(), Type::Separator))); - assert_eq!(l.cursor, 4); - } - - #[test] - fn test_is_separator_negative() { - let mut l = Lexer::new("- "); - assert_eq!(l.is_separator(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_tag_plus() { - let mut l = Lexer::new("+foo"); - assert_eq!(l.is_tag(), Some(("+foo".into(), Type::Tag))); - assert_eq!(l.cursor, 4); - } - - #[test] - fn test_is_tag_not_after_whitespace() { - let mut l = Lexer::new("x+y"); - l.cursor = 1; - assert_eq!(l.is_tag(), NONE); - assert_eq!(l.cursor, 1); - } - - #[test] - fn test_is_tag_after_whitespace() { - let mut l = Lexer::new(" +y"); - l.cursor = 1; - assert_eq!(l.is_tag(), Some(("+y".into(), Type::Tag))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_tag_after_lparen() { - let mut l = Lexer::new("(+y"); - l.cursor = 1; - assert_eq!(l.is_tag(), Some(("+y".into(), Type::Tag))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_tag_after_rparen() { - let mut l = Lexer::new(")+y"); - l.cursor = 1; - assert_eq!(l.is_tag(), Some(("+y".into(), Type::Tag))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_tag_after_multibyte_char() { - let mut l = Lexer::new("€+y"); - l.cursor = 3; - assert_eq!(l.is_tag(), NONE); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_url_http() { - let mut l = Lexer::new("http://foo.com/bar"); - assert_eq!(l.is_url(), Some(("http://foo.com/bar".into(), Type::URL))); - assert_eq!(l.cursor, 18); - } - - #[test] - fn test_is_url_https() { - let mut l = Lexer::new("https://foo.com/bar"); - assert_eq!(l.is_url(), Some(("https://foo.com/bar".into(), Type::URL))); - assert_eq!(l.cursor, 19); - } - - #[test] - fn test_is_url_ws() { - let mut l = Lexer::new("https://foo.com/bar "); - assert_eq!(l.is_url(), Some(("https://foo.com/bar".into(), Type::URL))); - assert_eq!(l.cursor, 19); - } - - #[test] - fn test_is_url_with_ops() { - let mut l = Lexer::new("https://foo.com/bar()+-~"); - assert_eq!( - l.is_url(), - Some(("https://foo.com/bar()+-~".into(), Type::URL)) - ); - assert_eq!(l.cursor, 24); - } - - #[test] - fn test_is_url_negative() { - let mut l = Lexer::new("file://foo.com/bar"); - assert_eq!(l.is_url(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_pair_double_colon() { - let mut l = Lexer::new("foo::bar "); - assert_eq!(l.is_pair(), Some(("foo::bar".into(), Type::Pair))); - assert_eq!(l.cursor, 8); - } - - #[test] - fn test_is_pair_colon_eq() { - let mut l = Lexer::new("foo:=bar "); - assert_eq!(l.is_pair(), Some(("foo:=bar".into(), Type::Pair))); - assert_eq!(l.cursor, 8); - } - - #[test] - fn test_is_pair_colon() { - let mut l = Lexer::new("foo:bar "); - assert_eq!(l.is_pair(), Some(("foo:bar".into(), Type::Pair))); - assert_eq!(l.cursor, 7); - } - - #[test] - fn test_is_pair_equal() { - let mut l = Lexer::new("foo=bar"); - assert_eq!(l.is_pair(), Some(("foo=bar".into(), Type::Pair))); - assert_eq!(l.cursor, 7); - } - - #[test] - fn test_is_pair_quoted() { - let mut l = Lexer::new("foo='abc def'"); - assert_eq!(l.is_pair(), Some(("foo='abc def'".into(), Type::Pair))); - assert_eq!(l.cursor, 13); - } - - #[test] - fn test_is_pair_quoted_escapes() { - let mut l = Lexer::new("foo='abc\\u20acdef'"); - assert_eq!(l.is_pair(), Some(("foo='abc€def'".into(), Type::Pair))); - assert_eq!(l.cursor, 18); - } - - #[test] - fn test_is_uuid_long_eof() { - let u = "ffffffff-ffff-ffff-ffff-ffffffffff"; - let mut l = Lexer::new(u); - assert_eq!(l.is_uuid(true), Some((u.into(), Type::Uuid))); - assert_eq!(l.cursor, 34); - } - - #[test] - fn test_is_uuid_long_ws() { - let u = "ffffffff-ffff-ffff-ffff-ffffffffff kjdf"; - let mut l = Lexer::new(u); - assert_eq!(l.is_uuid(true), Some((u[..34].into(), Type::Uuid))); - assert_eq!(l.cursor, 34); - } - - #[test] - fn test_is_uuid_long_op() { - let u = "ffffffff-ffff-ffff-ffff-ffffffffff+"; - let mut l = Lexer::new(u); - assert_eq!(l.is_uuid(true), Some((u[..34].into(), Type::Uuid))); - assert_eq!(l.cursor, 34); - } - - #[test] - fn test_is_uuid_long_bad_boundary() { - let u = "ffffffff-ffff-ffff-ffff-ffffffffff_"; - let mut l = Lexer::new(u); - assert_eq!(l.is_uuid(true), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_uuid_long_bad_boundary_ignored() { - let u = "ffffffff-ffff-ffff-ffff-ffffffffff_"; - let mut l = Lexer::new(u); - assert_eq!(l.is_uuid(false), Some((u[..34].into(), Type::Uuid))); - assert_eq!(l.cursor, 34); - } - - #[test] - fn test_is_uuid_too_short() { - let u = "ffffff"; - let mut l = Lexer::new(u); - assert_eq!(l.is_uuid(true), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_path_simple() { - let mut l = Lexer::new("/path/to/a/file"); - assert_eq!(l.is_path(), Some(("/path/to/a/file".into(), Type::Path))); - assert_eq!(l.cursor, 15); - } - - #[test] - fn test_is_path_too_short() { - let mut l = Lexer::new("/a/file"); - assert_eq!(l.is_path(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_path_trailing_slash() { - let mut l = Lexer::new("/path/to/a/dir/"); - assert_eq!(l.is_path(), Some(("/path/to/a/dir/".into(), Type::Path))); - assert_eq!(l.cursor, 15); - } - - #[test] - fn test_is_path_double_slash() { - let mut l = Lexer::new("/a//file"); - assert_eq!(l.is_path(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_path_no_initial_slash() { - let mut l = Lexer::new("a/path/to/a/file"); - assert_eq!(l.is_path(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_substitution_simple() { - let mut l = Lexer::new("/foo/bar/"); - assert_eq!( - l.is_substitution(), - Some(("/foo/bar/".into(), Type::Substitution)) - ); - assert_eq!(l.cursor, 9); - } - - #[test] - fn test_is_substitution_simple_ws() { - let mut l = Lexer::new("/foo/bar/ "); - assert_eq!( - l.is_substitution(), - Some(("/foo/bar/".into(), Type::Substitution)) - ); - assert_eq!(l.cursor, 9); - } - - #[test] - fn test_is_substitution_simple_g() { - let mut l = Lexer::new("/foo/bar/g"); - assert_eq!( - l.is_substitution(), - Some(("/foo/bar/g".into(), Type::Substitution)) - ); - assert_eq!(l.cursor, 10); - } - - #[test] - fn test_is_substitution_simple_g_ws() { - let mut l = Lexer::new("/foo/bar/g "); - assert_eq!( - l.is_substitution(), - Some(("/foo/bar/g".into(), Type::Substitution)) - ); - assert_eq!(l.cursor, 10); - } - - #[test] - fn test_is_substitution_simple_not_g() { - let mut l = Lexer::new("/foo/bar/h"); - assert_eq!(l.is_substitution(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_substitution_simple_not_g_op() { - let mut l = Lexer::new("/foo/bar/+"); - assert_eq!(l.is_substitution(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_substitution_simple_g_but_not_ws() { - let mut l = Lexer::new("/foo/bar/ghi"); - assert_eq!(l.is_substitution(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_pattern_simple() { - let mut l = Lexer::new("/foo/"); - assert_eq!(l.is_pattern(), Some(("/foo/".into(), Type::Pattern))); - assert_eq!(l.cursor, 5); - } - - #[test] - fn test_is_pattern_escaped() { - let mut l = Lexer::new("/f\\u20A4o/"); - assert_eq!(l.is_pattern(), Some(("/f\\u20A4o/".into(), Type::Pattern))); - assert_eq!(l.cursor, 10); - } - - #[test] - fn test_is_pattern_simple_trailing_ws() { - let mut l = Lexer::new("/foo/\n\t"); - assert_eq!(l.is_pattern(), Some(("/foo/".into(), Type::Pattern))); - assert_eq!(l.cursor, 5); - } - - #[test] - fn test_is_operator_hastag() { - let mut l = Lexer::new("_hastag_"); - assert_eq!(l.is_operator(), Some(("_hastag_".into(), Type::Op))); - } - - #[test] - fn test_is_operator_notag() { - let mut l = Lexer::new("_notag_"); - assert_eq!(l.is_operator(), Some(("_notag_".into(), Type::Op))); - } - - #[test] - fn test_is_operator_neg() { - let mut l = Lexer::new("_neg_"); - assert_eq!(l.is_operator(), Some(("_neg_".into(), Type::Op))); - } - - #[test] - fn test_is_operator_xor() { - let mut l = Lexer::new("xor"); - assert_eq!(l.is_operator(), Some(("xor".into(), Type::Op))); - } - - #[test] - fn test_is_identifier_empty() { - let mut l = Lexer::new(""); - assert_eq!(l.is_identifier(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_identifier_multibyte_nonpunct_first_char() { - let mut l = Lexer::new("☺"); - assert_eq!(l.is_identifier(), Some(("☺".into(), Type::Identifier))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_identifier_bad_first_char() { - let mut l = Lexer::new("1abc"); - assert_eq!(l.is_identifier(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_identifier_bad_next_char() { - let mut l = Lexer::new("a:bc"); - assert_eq!(l.is_identifier(), Some(("a".into(), Type::Identifier))); - assert_eq!(l.cursor, 1); - } - - #[test] - fn test_is_identifier_ok() { - let mut l = Lexer::new("abc"); - assert_eq!(l.is_identifier(), Some(("abc".into(), Type::Identifier))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_word_no() { - let mut l = Lexer::new("+"); - assert!(l.is_word().is_none()); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_word_pending() { - let mut l = Lexer::new("foo.PENDING"); - l.cursor = 4; - assert_eq!(l.is_word(), Some(("PENDING".into(), Type::Word))); - assert_eq!(l.cursor, 11); - } - - #[test] - fn test_is_word_to_eof() { - let mut l = Lexer::new("abc"); - assert_eq!(l.is_word(), Some(("abc".into(), Type::Word))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_word_nonzero_start() { - let mut l = Lexer::new("--abc"); - l.cursor = 2; - assert_eq!(l.is_word(), Some(("abc".into(), Type::Word))); - assert_eq!(l.cursor, 5); - } - - #[test] - fn test_is_word_to_ws() { - let mut l = Lexer::new("abc def"); - assert_eq!(l.is_word(), Some(("abc".into(), Type::Word))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_word_to_op() { - let mut l = Lexer::new("abc*def"); - assert_eq!(l.is_word(), Some(("abc".into(), Type::Word))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_split_simple() { - assert_eq!( - Lexer::split(" ( A or B ) "), - vec![ - String::from("("), - String::from("A"), - String::from("or"), - String::from("B"), - String::from(")"), - ] - ); - } - - #[test] - fn test_split_confusing() { - assert_eq!( - Lexer::split(" +-* a+b 12.3e4 'c d'"), - vec![ - String::from("+"), - String::from("-"), - String::from("*"), - String::from("a"), - String::from("+"), - String::from("b"), - String::from("12.3e4"), - String::from("'c d'"), - ] - ); - } - - #[test] - fn test_decompose_pair_combos() { - let name = "name"; - for modifier in ["", "mod"].iter() { - for separator in [":", "=", "::", ":="].iter() { - for value in ["", "value", "a:b", "a::b", "a=b", "a:=b"].iter() { - let input = format!( - "{}{}{}{}{}", - name, - if modifier.len() > 0 { "." } else { "" }, - modifier, - separator, - value - ); - assert_eq!( - decompose_pair(&input), - Some(DecomposedPair { - name: name.into(), - modifier: String::from(*modifier), - separator: String::from(*separator), - value: String::from(*value), - }) - ); - } - } - } - } - - #[test] - fn decompose_substitution_no_flags() { - assert_eq!( - decompose_substitution("/a/b/"), - Some(DecomposedSubstitution { - from: "a".into(), - to: "b".into(), - flags: "".into(), - }) - ); - } - - #[test] - fn decompose_substitution_flags() { - assert_eq!( - decompose_substitution("/a/b/g"), - Some(DecomposedSubstitution { - from: "a".into(), - to: "b".into(), - flags: "g".into(), - }) - ); - } - - #[test] - fn decompose_pattern_no_flags() { - assert_eq!( - decompose_pattern("/foober/"), - Some(DecomposedPattern { - pattern: "foober".into(), - flags: "".into(), - }) - ); - } - - #[test] - fn decompose_pattern_flags() { - assert_eq!( - decompose_pattern("/foober/g"), - Some(DecomposedPattern { - pattern: "foober".into(), - flags: "g".into(), - }) - ); - } - - #[test] - fn test_is_one_of() { - let mut l = Lexer::new("Grumpy."); - let dwarves = vec![ - "Sneezy", "Doc", "Bashful", "Grumpy", "Happy", "Sleepy", "Dopey", - ]; - assert!(!l.is_one_of(&dwarves, false, true)); - assert_eq!(l.cursor, 0); - assert!(l.is_one_of(&dwarves, false, false)); - assert_eq!(l.cursor, 6); - } - - #[test] - fn test_is_integer_negative() { - let mut l = Lexer::new("one"); - assert_eq!(l.is_integer(), NONE); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_integer_positive() { - let mut l = Lexer::new("123"); - assert_eq!(l.is_integer(), Some(("123".into(), Type::Number))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_integer_trailing_dot() { - let mut l = Lexer::new("123.foo"); - assert_eq!(l.is_integer(), Some(("123".into(), Type::Number))); - assert_eq!(l.cursor, 3); - } - - #[test] - fn test_is_integer_not_at_start() { - let mut l = Lexer::new("abc.123.foo"); - l.cursor = 4; - assert_eq!(l.is_integer(), Some(("123".into(), Type::Number))); - assert_eq!(l.cursor, 7); - } - - #[test] - fn test_is_literal_no_match() { - let mut l = Lexer::new("one.two"); - assert!(!l.is_literal("zero", false, false)); - assert_eq!(l.cursor, 0); - } - - #[test] - fn test_is_literal_multi() { - let mut l = Lexer::new("one.two"); - assert!(l.is_literal("one", false, false)); - assert_eq!(l.cursor, 3); - assert!(l.is_literal(".", false, false)); - assert_eq!(l.cursor, 4); - assert!(l.is_literal("two", false, true)); - assert_eq!(l.cursor, 7); - } - - #[test] - fn test_is_literal_abbrev() { - let mut l = Lexer::new("wonder"); - assert!(!l.is_literal("wonderful", false, false)); - assert_eq!(l.cursor, 0); - assert!(l.is_literal("wonderful", true, false)); - assert_eq!(l.cursor, 6); - } - - mod integ { - use super::super::*; - - fn lexer_test(input: &str, expected: Vec<(&str, Type)>) { - // isolated case.. - let mut lexer = Lexer::new(input); - lexer.add_attribute("due"); - lexer.add_attribute("tags"); - lexer.add_attribute("description"); - let got: Vec<_> = lexer.into_iter().collect(); - let got_strs: Vec<_> = got.iter().map(|(s, t)| (s.as_ref(), *t)).collect(); - assert_eq!(got_strs, expected); - - // embedded case.. - let mut lexer = Lexer::new(format!(" {} ", input)); - lexer.add_attribute("due"); - lexer.add_attribute("tags"); - lexer.add_attribute("description"); - let got: Vec<_> = lexer.into_iter().collect(); - let got_strs: Vec<_> = got.iter().map(|(s, t)| (s.as_ref(), *t)).collect(); - assert_eq!(got_strs, expected); - } - - #[test] - fn test_pattern_foo() { - lexer_test("/foo/", vec![("/foo/", Type::Pattern)]); - } - - #[test] - fn test_pattern_escaped_slash() { - lexer_test("/a\\/b/", vec![("/a\\/b/", Type::Pattern)]); - } - - #[test] - fn test_pattern_quote() { - lexer_test("/'/", vec![("/'/", Type::Pattern)]); - } - - // Substitution - // - #[test] - fn test_subst_g() { - lexer_test("/from/to/g", vec![("/from/to/g", Type::Substitution)]); - } - - #[test] - fn test_subst() { - lexer_test("/from/to/", vec![("/from/to/", Type::Substitution)]); - } - - // Tag - // - #[test] - fn test_tag_simple() { - lexer_test("+tag", vec![("+tag", Type::Tag)]); - } - - #[test] - fn test_tag_negative() { - lexer_test("-tag", vec![("-tag", Type::Tag)]); - } - - #[test] - fn test_tag_at() { - lexer_test("+@tag", vec![("+@tag", Type::Tag)]); - } - - // Path - // - #[test] - fn test_path() { - lexer_test( - "/long/path/to/file.txt", - vec![("/long/path/to/file.txt", Type::Path)], - ); - } - - #[test] - fn test_path_dir() { - lexer_test( - "/long/path/to/dir/", - vec![("/long/path/to/dir/", Type::Path)], - ); - } - - // Word - // - #[test] - fn test_1_foo_bar() { - lexer_test("1.foo.bar", vec![("1.foo.bar", Type::Word)]); - } - - // Identifier - // - #[test] - fn test_foo() { - lexer_test("foo", vec![("foo", Type::Identifier)]); - } - - #[test] - fn test_multibyte_ident() { - lexer_test("Çirçös", vec![("Çirçös", Type::Identifier)]); - } - - #[test] - fn test_multibyte_nonpunctuation_single_char() { - lexer_test("☺", vec![("☺", Type::Identifier)]); - } - - #[test] - fn test_name() { - lexer_test("name", vec![("name", Type::Identifier)]); - } - - #[test] - fn test_f1() { - lexer_test("f1", vec![("f1", Type::Identifier)]); - } - - #[test] - fn test_foo_dot_bar() { - lexer_test("foo.bar", vec![("foo.bar", Type::Identifier)]); - } - - #[test] - fn test_long_with_underscore() { - lexer_test( - "a1a1a1a1_a1a1_a1a1_a1a1_a1a1a1a1a1a1", - vec![("a1a1a1a1_a1a1_a1a1_a1a1_a1a1a1a1a1a1", Type::Identifier)], - ); - } - - // Word that starts wih 'or', which is an operator, but should be ignored. - // - #[test] - fn test_starts_with_or() { - lexer_test("ordinary", vec![("ordinary", Type::Identifier)]); - } - - // DOM - // - #[test] - fn test_due() { - lexer_test("due", vec![("due", Type::DOM)]); - } - - #[test] - fn test_123_tags() { - lexer_test("123.tags", vec![("123.tags", Type::DOM)]); - } - - #[test] - fn test_123_tags_pending() { - lexer_test("123.tags.PENDING", vec![("123.tags.PENDING", Type::DOM)]); - } - - #[test] - fn test_123_description() { - lexer_test("123.description", vec![("123.description", Type::DOM)]); - } - - #[test] - fn test_123_annotations_count() { - lexer_test( - "123.annotations.count", - vec![("123.annotations.count", Type::DOM)], - ); - } - - #[test] - fn test_123_annotations_1_description() { - lexer_test( - "123.annotations.1.description", - vec![("123.annotations.1.description", Type::DOM)], - ); - } - - #[test] - fn test_123_annotations_1_entry() { - lexer_test( - "123.annotations.1.entry", - vec![("123.annotations.1.entry", Type::DOM)], - ); - } - - #[test] - fn test_123_annotations_1_entry_year() { - lexer_test( - "123.annotations.1.entry.year", - vec![("123.annotations.1.entry.year", Type::DOM)], - ); - } - - #[test] - fn test_uuid_due() { - lexer_test( - "a360fc44-315c-4366-b70c-ea7e7520b749.due", - vec![("a360fc44-315c-4366-b70c-ea7e7520b749.due", Type::DOM)], - ); - } - - #[test] - fn test_numeric_uuid_due() { - lexer_test( - "12345678-1234-1234-1234-123456789012.due", - vec![("12345678-1234-1234-1234-123456789012.due", Type::DOM)], - ); - } - - #[test] - fn test_system_os() { - lexer_test("system.os", vec![("system.os", Type::DOM)]); - } - - #[test] - fn test_rc_foo() { - lexer_test("rc.foo", vec![("rc.foo", Type::DOM)]); - } - - // URL - // - #[test] - fn test_lexer_31() { - lexer_test( - "http://example.com", - vec![("http://example.com", Type::URL)], - ); - } - - #[test] - fn test_lexer_32() { - lexer_test( - "https://foo.example.com", - vec![("https://foo.example.com", Type::URL)], - ); - } - - // String - // - #[test] - fn test_quoted_string() { - lexer_test("'one two'", vec![("'one two'", Type::String)]); - } - - #[test] - fn test_double_quoted_string() { - lexer_test("\"three\"", vec![("\"three\"", Type::String)]); - } - - #[test] - fn test_string_quoted_with_escapes() { - lexer_test("'\\''", vec![("'''", Type::String)]); - } - - #[test] - fn test_string_quoted_quotes() { - lexer_test("\"\\\"\"", vec![("\"\"\"", Type::String)]); - } - - #[test] - fn test_quoted_tabs() { - lexer_test("\"\tfoo\t\"", vec![("\"\tfoo\t\"", Type::String)]); - } - - #[test] - fn test_multibyte_slash_u() { - lexer_test("\"\\u20A43\"", vec![("\"₤3\"", Type::String)]); - } - - #[test] - fn test_multibyte_u_plus() { - lexer_test("\"U+20AC4\"", vec![("\"€4\"", Type::String)]); - } - - // Number - // - #[test] - fn test_one() { - lexer_test("1", vec![("1", Type::Number)]); - } - - #[test] - fn test_pi() { - lexer_test("3.14", vec![("3.14", Type::Number)]); - } - - #[test] - fn test_avogadro() { - lexer_test("6.02217e23", vec![("6.02217e23", Type::Number)]); - } - - #[test] - fn test_expo() { - lexer_test("1.2e-3.4", vec![("1.2e-3.4", Type::Number)]); - } - - #[test] - fn test_hex() { - lexer_test("0x2f", vec![("0x2f", Type::Hex)]); - } - - // Set (1,2,4-7,9) - // - #[test] - fn test_set_pair() { - lexer_test("1,2", vec![("1,2", Type::Set)]); - } - - #[test] - fn test_set_range() { - lexer_test("1-2", vec![("1-2", Type::Set)]); - } - - #[test] - fn test_set_range_pair() { - lexer_test("1-2,4", vec![("1-2,4", Type::Set)]); - } - - #[test] - fn test_set_range_pair_ws() { - lexer_test("1-2,4 ", vec![("1-2,4", Type::Set)]); - } - - #[test] - fn test_set_range_pair_paren() { - lexer_test("1-2,4(", vec![("1-2,4", Type::Set), ("(", Type::Op)]); - } - - #[test] - fn test_ranges_and_singletons() { - lexer_test("1-2,4,6-8", vec![("1-2,4,6-8", Type::Set)]); - } - - #[test] - fn test_set_more_ranges_and_singletons() { - lexer_test("1-2,4,6-8,10-12", vec![("1-2,4,6-8,10-12", Type::Set)]); - } - - // Pair - // - #[test] - fn test_name_colon_value() { - lexer_test("name:value", vec![("name:value", Type::Pair)]); - } - - #[test] - fn test_name_eq_value() { - lexer_test("name=value", vec![("name=value", Type::Pair)]); - } - - #[test] - fn test_name_colon_eq_value() { - lexer_test("name:=value", vec![("name:=value", Type::Pair)]); - } - - #[test] - fn test_name_dot_mod_colon_value() { - lexer_test("name.mod:value", vec![("name.mod:value", Type::Pair)]); - } - - #[test] - fn test_name_dot_mod_eq_value() { - lexer_test("name.mod=value", vec![("name.mod=value", Type::Pair)]); - } - - #[test] - fn test_name_colon() { - lexer_test("name:", vec![("name:", Type::Pair)]); - } - - #[test] - fn test_name_eq() { - lexer_test("name=", vec![("name=", Type::Pair)]); - } - - #[test] - fn test_name_dot_mod_colon() { - lexer_test("name.mod:", vec![("name.mod:", Type::Pair)]); - } - - #[test] - fn test_name_dot_mod_equal() { - lexer_test("name.mod=", vec![("name.mod=", Type::Pair)]); - } - - #[test] - fn test_pro_quoted() { - lexer_test("pro:'P 1'", vec![("pro:'P 1'", Type::Pair)]); - } - - #[test] - fn test_rc_colon_x() { - lexer_test("rc:x", vec![("rc:x", Type::Pair)]); - } - - #[test] - fn test_rc_dot_name_colon_value() { - lexer_test("rc.name:value", vec![("rc.name:value", Type::Pair)]); - } - - #[test] - fn test_rc_dot_name_eq_value() { - lexer_test("rc.name=value", vec![("rc.name=value", Type::Pair)]); - } - - #[test] - fn test_rc_dot_name_colon_eq_value() { - lexer_test("rc.name:=value", vec![("rc.name:=value", Type::Pair)]); - } - - #[test] - fn test_due_colon_eq_quoted() { - lexer_test("due:='eow - 2d'", vec![("due:='eow - 2d'", Type::Pair)]); - } - - #[test] - fn test_name_colon_quoted_with_newline() { - lexer_test("name:'foo\nbar'", vec![("name:'foo\nbar'", Type::Pair)]); - } - - // Operator - complete set - // - #[test] - fn test_caret() { - lexer_test("^", vec![("^", Type::Op)]); - } - - #[test] - fn test_bang() { - lexer_test("!", vec![("!", Type::Op)]); - } - - #[test] - fn test_neg() { - lexer_test("_neg_", vec![("_neg_", Type::Op)]); - } - - #[test] - fn test_pos() { - lexer_test("_pos_", vec![("_pos_", Type::Op)]); - } - - #[test] - fn test_hastag() { - lexer_test("_hastag_", vec![("_hastag_", Type::Op)]); - } - - #[test] - fn test_notag() { - lexer_test("_notag_", vec![("_notag_", Type::Op)]); - } - - #[test] - fn test_star() { - lexer_test("*", vec![("*", Type::Op)]); - } - - #[test] - fn test_slash() { - lexer_test("/", vec![("/", Type::Op)]); - } - - #[test] - fn test_percent() { - lexer_test("%", vec![("%", Type::Op)]); - } - - #[test] - fn test_plus() { - lexer_test("+", vec![("+", Type::Op)]); - } - - #[test] - fn test_minus() { - lexer_test("-", vec![("-", Type::Op)]); - } - - #[test] - fn test_leq() { - lexer_test("<=", vec![("<=", Type::Op)]); - } - - #[test] - fn test_geq() { - lexer_test(">=", vec![(">=", Type::Op)]); - } - - #[test] - fn test_gt() { - lexer_test(">", vec![(">", Type::Op)]); - } - - #[test] - fn test_lt() { - lexer_test("<", vec![("<", Type::Op)]); - } - - #[test] - fn test_eq() { - lexer_test("=", vec![("=", Type::Op)]); - } - - #[test] - fn test_double_eq() { - lexer_test("==", vec![("==", Type::Op)]); - } - - #[test] - fn test_not_eq() { - lexer_test("!=", vec![("!=", Type::Op)]); - } - - #[test] - fn test_not_double_eq() { - lexer_test("!==", vec![("!==", Type::Op)]); - } - - #[test] - fn test_tilde() { - lexer_test("~", vec![("~", Type::Op)]); - } - - #[test] - fn test_not_tilde() { - lexer_test("!~", vec![("!~", Type::Op)]); - } - - #[test] - fn test_and() { - lexer_test("and", vec![("and", Type::Op)]); - } - - #[test] - fn test_or() { - lexer_test("or", vec![("or", Type::Op)]); - } - - #[test] - fn test_xor() { - lexer_test("xor", vec![("xor", Type::Op)]); - } - - #[test] - fn test_lparen() { - lexer_test("(", vec![("(", Type::Op)]); - } - - #[test] - fn test_rparen() { - lexer_test(")", vec![(")", Type::Op)]); - } - - // UUID - // - #[test] - fn test_uuid_ffs() { - lexer_test( - "ffffffff-ffff-ffff-ffff-ffffffffffff", - vec![("ffffffff-ffff-ffff-ffff-ffffffffffff", Type::Uuid)], - ); - } - - #[test] - fn test_uuid_00s() { - lexer_test( - "00000000-0000-0000-0000-0000000", - vec![("00000000-0000-0000-0000-0000000", Type::Uuid)], - ); - } - - #[test] - fn test_uuid_shorter() { - lexer_test( - "00000000-0000-0000-0000", - vec![("00000000-0000-0000-0000", Type::Uuid)], - ); - } - - #[test] - fn test_uuid_shorter_still() { - lexer_test( - "00000000-0000-0000", - vec![("00000000-0000-0000", Type::Uuid)], - ); - } - - #[test] - fn test_uuid_even_shorter() { - lexer_test("00000000-0000", vec![("00000000-0000", Type::Uuid)]); - } - - #[test] - fn test_uuid_only_first_bit() { - lexer_test("00000000", vec![("00000000", Type::Uuid)]); - } - - #[test] - fn test_real_uuid() { - lexer_test( - "a360fc44-315c-4366-b70c-ea7e7520b749", - vec![("a360fc44-315c-4366-b70c-ea7e7520b749", Type::Uuid)], - ); - } - - #[test] - fn test_real_uuid_shorter() { - lexer_test( - "a360fc44-315c-4366-b70c-ea7e752", - vec![("a360fc44-315c-4366-b70c-ea7e752", Type::Uuid)], - ); - } - - #[test] - fn test_real_uuid_shorter_still() { - lexer_test( - "a360fc44-315c-4366-b70c", - vec![("a360fc44-315c-4366-b70c", Type::Uuid)], - ); - } - - #[test] - fn test_real_uuid_even_shorter() { - lexer_test( - "a360fc44-315c-4366", - vec![("a360fc44-315c-4366", Type::Uuid)], - ); - } - - #[test] - fn test_real_uuid_naming_is_hard() { - lexer_test("a360fc44-315c", vec![("a360fc44-315c", Type::Uuid)]); - } - - #[test] - fn test_real_uuid_only_first_bit() { - lexer_test("a360fc44", vec![("a360fc44", Type::Uuid)]); - } - - // Date - // - #[test] - fn test_year_week() { - lexer_test("2015-W01", vec![("2015-W01", Type::Date)]); - } - - #[test] - fn test_year_month_day() { - lexer_test("2015-02-17", vec![("2015-02-17", Type::Date)]); - } - - #[test] - fn test_timestamp() { - lexer_test( - "2013-11-29T22:58:00Z", - vec![("2013-11-29T22:58:00Z", Type::Date)], - ); - } - - #[test] - fn test_abbrev_timestamp() { - lexer_test("20131129T225800Z", vec![("20131129T225800Z", Type::Date)]); - } - - #[test] - fn test_9thn() { - lexer_test("9th", vec![("9th", Type::Date)]); - } - - #[test] - fn test_10th() { - lexer_test("10th", vec![("10th", Type::Date)]); - } - - #[test] - fn test_today() { - lexer_test("today", vec![("today", Type::Date)]); - } - - // Duration - // - #[test] - fn test_year() { - lexer_test("year", vec![("year", Type::Duration)]); - } - - #[test] - fn test_4weeks() { - lexer_test("4weeks", vec![("4weeks", Type::Duration)]); - } - - #[test] - fn test_pt23h() { - lexer_test("PT23H", vec![("PT23H", Type::Duration)]); - } - - #[test] - fn test_1second() { - lexer_test("1second", vec![("1second", Type::Duration)]); - } - - #[test] - fn test_1s() { - lexer_test("1s", vec![("1s", Type::Duration)]); - } - - #[test] - fn test_1minute() { - lexer_test("1minute", vec![("1minute", Type::Duration)]); - } - - #[test] - fn test_2hour() { - lexer_test("2hour", vec![("2hour", Type::Duration)]); - } - - #[test] - fn test_3_days() { - lexer_test("3 days", vec![("3 days", Type::Duration)]); - } - - #[test] - fn test_4w() { - lexer_test("4w", vec![("4w", Type::Duration)]); - } - - #[test] - fn test_5mo() { - lexer_test("5mo", vec![("5mo", Type::Duration)]); - } - - #[test] - fn test_6_years() { - lexer_test("6 years", vec![("6 years", Type::Duration)]); - } - - #[test] - fn test_p1y() { - lexer_test("P1Y", vec![("P1Y", Type::Duration)]); - } - - #[test] - fn test_pt1h() { - lexer_test("PT1H", vec![("PT1H", Type::Duration)]); - } - - #[test] - fn test_p_full() { - lexer_test("P1Y1M1DT1H1M1S", vec![("P1Y1M1DT1H1M1S", Type::Duration)]); - } - - // Misc - // - #[test] - fn test_separator() { - lexer_test("--", vec![("--", Type::Separator)]); - } - - #[test] - fn test_separator_ws() { - lexer_test(" -- ", vec![("--", Type::Separator)]); - } - - #[test] - fn test_separator_boundaries() { - lexer_test( - "123--123 ", - vec![ - ("123", Type::Number), - ("--", Type::Separator), - ("123", Type::Number), - ], - ); - } - - // Expression - // due:eom-2w - // due < eom + 1w + 1d - // ( /pattern/ or 8ad2e3db-914d-4832-b0e6-72fa04f6e331,3b6218f9-726a-44fc-aa63-889ff52be442 ) - // - #[test] - fn test_expression() { - lexer_test( - "(1+2)", - vec![ - ("(", Type::Op), - ("1", Type::Number), - ("+", Type::Op), - ("2", Type::Number), - (")", Type::Op), - ], - ); - } - - #[test] - fn test_expression_dom_tilde() { - lexer_test( - "description~pattern", - vec![ - ("description", Type::DOM), - ("~", Type::Op), - ("pattern", Type::Identifier), - ], - ); - } - - #[test] - fn test_expression_paren_tag() { - lexer_test( - "(+tag)", - vec![("(", Type::Op), ("+tag", Type::Tag), (")", Type::Op)], - ); - } - - #[test] - fn test_expression_paren_name_value() { - lexer_test( - "(name:value)", - vec![("(", Type::Op), ("name:value", Type::Pair), (")", Type::Op)], - ); - } - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index a6465d5d7..000000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub(crate) mod datetime; -pub(crate) mod duration; -pub(crate) mod lexer; -pub(crate) mod pig; diff --git a/src/util/pig.rs b/src/util/pig.rs deleted file mode 100644 index 08a883639..000000000 --- a/src/util/pig.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! A minimal implementation of the "Pig" parsing utility from the Taskwarrior -//! source. This is just enough to parse FF4 lines. - -use failure::Fallible; - -pub(crate) struct Pig<'a> { - input: &'a [u8], - cursor: usize, -} - -impl<'a> Pig<'a> { - pub fn new(input: &'a [u8]) -> Self { - Pig { - input: input, - cursor: 0, - } - } - - pub fn get_until(&mut self, c: u8) -> Fallible<&'a [u8]> { - if self.cursor >= self.input.len() { - bail!("input truncated"); - } - - let mut i = self.cursor; - while i < self.input.len() { - if self.input[i] == c { - let rv = &self.input[self.cursor..i]; - self.cursor = i; - return Ok(rv); - } - i += 1; - } - let rv = &self.input[self.cursor..]; - self.cursor = self.input.len(); - Ok(rv) - } - - pub fn get_quoted(&mut self, c: u8) -> Fallible<&'a [u8]> { - let length = self.input.len(); - if self.cursor >= length || self.input[self.cursor] != c { - bail!("quoted string does not begin with quote character"); - } - - let start = self.cursor + 1; - let mut i = start; - - while i < length { - while i < length && self.input[i] != c { - i += 1 - } - if i == length { - bail!("unclosed quote"); - } - if i == start { - return Ok(&self.input[i..i]); - } - - if self.input[i - 1] == b'\\' { - // work backward looking for escaped backslashes - let mut j = i - 2; - let mut quote = true; - while j >= start && self.input[j] == b'\\' { - quote = !quote; - j -= 1; - } - - if quote { - i += 1; - continue; - } - } - - // none of the above matched, so we are at the end - self.cursor = i + 1; - return Ok(&self.input[start..i]); - } - - unreachable!(); - } - - pub fn skip(&mut self, c: u8) -> Fallible<()> { - if self.cursor < self.input.len() && self.input[self.cursor] == c { - self.cursor += 1; - return Ok(()); - } - bail!( - "expected character `{}`", - String::from_utf8(vec![c]).unwrap() - ); - } - - pub fn depleted(&self) -> bool { - self.cursor >= self.input.len() - } -} - -#[cfg(test)] -mod test { - use super::Pig; - - #[test] - fn test_get_until() { - let s = b"abc:123"; - let mut pig = Pig::new(s); - assert_eq!(pig.get_until(b':').unwrap(), &s[..3]); - } - - #[test] - fn test_get_until_empty() { - let s = b"abc:123"; - let mut pig = Pig::new(s); - assert_eq!(pig.get_until(b'a').unwrap(), &s[..0]); - } - - #[test] - fn test_get_until_not_found() { - let s = b"abc:123"; - let mut pig = Pig::new(s); - assert_eq!(pig.get_until(b'/').unwrap(), &s[..]); - } - - #[test] - fn test_get_quoted() { - let s = b"'abcd'efg"; - let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..5]); - assert_eq!(pig.cursor, 6); - } - - #[test] - fn test_get_quoted_unopened() { - let s = b"abcd'efg"; - let mut pig = Pig::new(s); - assert!(pig.get_quoted(b'\'').is_err()); - assert_eq!(pig.cursor, 0); // nothing consumed - } - - #[test] - fn test_get_quoted_unclosed() { - let s = b"'abcdefg"; - let mut pig = Pig::new(s); - assert!(pig.get_quoted(b'\'').is_err()); - assert_eq!(pig.cursor, 0); - } - - #[test] - fn test_get_quoted_escaped() { - let s = b"'abc\\'de'fg"; - let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..8]); - assert_eq!(pig.cursor, 9); - } - - #[test] - fn test_get_quoted_double_escaped() { - let s = b"'abc\\\\'de'fg"; - let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..6]); - assert_eq!(pig.cursor, 7); - } - - #[test] - fn test_get_quoted_triple_escaped() { - let s = b"'abc\\\\\\'de'fg"; - let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..10]); - assert_eq!(pig.cursor, 11); - } - - #[test] - fn test_get_quoted_all_escapes() { - let s = b"'\\\\\\'\\\\'fg"; - let mut pig = Pig::new(s); - assert_eq!(pig.get_quoted(b'\'').unwrap(), &s[1..7]); - assert_eq!(pig.cursor, 8); - } - - #[test] - fn test_skip_match() { - let s = b"foo"; - let mut pig = Pig::new(s); - assert!(pig.skip(b'f').is_ok()); - assert_eq!(pig.cursor, 1); - } - - #[test] - fn test_skip_no_match() { - let s = b"foo"; - let mut pig = Pig::new(s); - assert!(pig.skip(b'x').is_err()); - assert_eq!(pig.cursor, 0); // nothing consumed - } - - #[test] - fn test_skip_eos() { - let s = b"f"; - let mut pig = Pig::new(s); - assert!(pig.skip(b'f').is_ok()); - assert!(pig.skip(b'f').is_err()); - } -} From 39a0dfe7989056e3d3b002076ed7d5bd93b0938c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 22 Nov 2020 17:28:28 -0500 Subject: [PATCH 052/548] revise and expand docs about storage / taskdb / replica --- README.md | 11 +++- docs/src/SUMMARY.md | 8 ++- docs/src/data-model.md | 43 +----------- docs/src/development-notes.md | 93 -------------------------- docs/src/internals.md | 4 -- docs/src/plans.md | 35 ++++++++++ docs/src/storage.md | 73 +++++++++++++++++++++ docs/src/sync.md | 120 ++++++++++++++++++++++++++++++++++ docs/src/taskdb.md | 28 ++++++++ src/taskstorage/mod.rs | 14 ---- 10 files changed, 274 insertions(+), 155 deletions(-) delete mode 100644 docs/src/development-notes.md delete mode 100644 docs/src/internals.md create mode 100644 docs/src/plans.md create mode 100644 docs/src/storage.md create mode 100644 docs/src/sync.md create mode 100644 docs/src/taskdb.md diff --git a/README.md b/README.md index 2ced65b38..1a33e6b87 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,16 @@ TaskChampion is an open-source personal task-tracking application. Use it to keep track of what you need to do, with a quick command-line interface and flexible sorting and filtering. It is modeled on [TaskWarrior](https://taskwarrior.org), but not a drop-in replacement for that application. +Goals: + + * Feature parity with TaskWarrior (but not compatibility) + * Aproachable, maintainable codebase + * Active development community + * Reasonable privacy: user's task details not visible on server + * Reliable concurrency - clients do not diverge + * Storage performance O(n) with n number of tasks + See: - * [Development Notes](docs/development-notes.md) + * [Documentation](docs/src/SUMMARY.md) (will be published as an mdbook eventually) * [Progress on the first version](https://github.com/djmitche/taskwarrior-rust/projects/1) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 106400d47..57c42fe10 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -2,7 +2,9 @@ - [Installation](./installation.md) - [Usage](./usage.md) -- [Internal Details](./internals.md) - - [Data Model](./data-model.md) --- -- [Development Notes](./development-notes.md) +- [Data Model](./data-model.md) + - [Replica Storage](./storage.md) + - [Task Database](./taskdb.md) +- [Synchronization](./sync.md) + - [Planned Functionality](./plans.md) diff --git a/docs/src/data-model.md b/docs/src/data-model.md index 908ba5fe1..2a43df62b 100644 --- a/docs/src/data-model.md +++ b/docs/src/data-model.md @@ -1,42 +1,5 @@ # Data Model -A client manages a single offline instance of a single user's task list. -The data model is only seen from the clients' perspective. - -## Task Database - -The task database is composed of an un-ordered collection of tasks, each keyed by a UUID. -Each task in the database has an arbitrary-sized set of key/value properties, with string values. - -Tasks are only created and modified; "deleted" tasks continue to stick around and can be modified and even un-deleted. -Tasks have an expiration time, after which they may be purged from the database. - -## Task Fields - -Each task can have any of the following fields. -Timestamps are stored as UNIX epoch timestamps, in the form of an integer expressed in decimal notation. -Note that it is possible for any field to be omitted. - -NOTE: This structure is based on https://taskwarrior.org/docs/design/task.html, but will diverge from that -model over time. - -* `status` - one of `Pending`, `Completed`, `Deleted`, `Recurring`, or `Waiting` -* `entry` (timestamp) - time that the task was created -* `description` - the one-line summary of the task -* `start` (timestamp) - if set, the task is active and this field gives the time the task was started -* `end` (timestamp) - the time at which the task was deleted or completed -* `due` (timestamp) - the time at which the task is due -* `until` (timestamp) - the time after which recurrent child tasks should not be created -* `wait` (timestamp) - the time before which this task is considered waiting and should not be shown -* `modified` (timestamp) - time that the task was last modified -* `scheduled` (timestamp) - time that the task is available to start -* `recur` - recurrence frequency -* `mask` - recurrence history -* `imask` - for children of recurring tasks, the index into the `mask` property on the parent -* `parent` - for children of recurring tasks, the uuid of the parent task -* `project` - the task's project (usually a short identifier) -* `priority` - the task's priority, one of `L`, `M`, or `H`. -* `depends` - a comma (`,`) separated list of uuids of tasks on which this task depends -* `tags` - a comma (`,`) separated list of tags for this task -* `annotation_` - an annotation for this task, with the timestamp as part of the key -* `udas` - user-defined attributes +A client manages a single offline instance of a single user's task list, called a replica. +This section covers the structure of that data. +Note that this data model is visible only on the client; the server does not have access to client data. diff --git a/docs/src/development-notes.md b/docs/src/development-notes.md deleted file mode 100644 index ffdbb6314..000000000 --- a/docs/src/development-notes.md +++ /dev/null @@ -1,93 +0,0 @@ -Goals: - - * Reasonable privacy: user's task details not visible on server - * Reliable concurrency - clients do not diverge - * Storage O(n) with n number of tasks - -# Operations - -Every change to the task database is captured as an operation. -Each operation has one of the forms - * `Create(uuid)` - * `Delete(uuid)` - * `Update(uuid, property, value, timestamp)` - -The Create form creates a new task. -It is invalid to create a task that already exists. - -Similarly, the Delete form deletes an existing task. -It is invalid to delete a task that does not exist. - -The Update form updates the given property of the given task, where property and value are both strings. -Value can also be `None` to indicate deletion of a property. -It is invalid to update a task that does not exist. -The timestamp on updates serves as additional metadata and is used to resolve conflicts. - -Operations act as deltas between database states. - -## Versions and Synchronization - -Occasionally, database states are named with an integer, called a version. -The system as a whole (server and clients) constructs a monotonic sequence of versions and the operations that separate each version from the next. -No gaps are allowed in the verison numbering. -Version 0 is implicitly the empty database. - -The server stores the operations for each version, and provides them as needed to clients. -Clients use this information to update their local task databases, and to generate new versions to send to the server. - -Clients generate a new version to transmit changes made locally to the server. -The changes are represented as a sequence of operations with the final operation being tagged as the version. -In order to keep the gap-free monotonic numbering, the server will only accept a proposed version from a client if its number is one greater that the latest version on the server. -When this is not the case, the client must "rebase" the local changes onto the latest version from the server and try again. -This operation is performed using operational transformation (OT). -The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the client can apply to its local task database to "catch up" to the version on the server. - -## Snapshots - -As designed, storage required on the server would grow with time, as would the time required for new clients to update to the latest version. -As an optimization, the server also stores "snapshots" containing a full copy of the task database at a given version. -Based on configurable heuristics, it may delete older operations and snapshots, as long as enough data remains for active clients to synchronize and for new clients to initialize. - -Since snapshots must be computed by clients, the server may "request" a snapshot when providing the latest version to a client. -This request comes with a number indicating how much it 'wants" the snapshot. -Clients which can easily generate and transmit a snapshot should be generous to the server, while clients with more limited resources can wait until the server's requests are more desperate. -The intent is, where possible, to request snapshots created on well-connected desktop clients over mobile and low-power clients. - -## Encryption and Signing - -From the server's perspective, all data except for version numbers are opaque binary blobs. -Clients encrypt and sign these blobs using a symmetric key known only to the clients. -This secures the data at-rest on the server. -Note that privacy is not complete, as the server still has some information about users, including source and frequency of synchronization transactions and size of those transactions. - -## Backups - -In this design, the server is little more than an authenticated storage for encrypted blobs provided by the client. -To allow for failure or data loss on the server, clients are expected to cache these blobs locally for a short time (a week), along with a server-provided HMAC signature. -When data loss is detected -- such as when a client expects the server to have a version N or higher, and the server only has N-1, the client can send those blobs to the server. -The server can validate the HMAC and, if successful, add the blobs to its datastore. - -## Expiration - -TBD - -.. conditions on flushing to allow consistent handling - -# Implementation Notes - -## Client / Server Protocol - -TBD - -.. using HTTP -.. user auth -.. user setup process - -## Batching Operations - -TBD - -## Recurrence - -TBD - diff --git a/docs/src/internals.md b/docs/src/internals.md deleted file mode 100644 index d845d59a9..000000000 --- a/docs/src/internals.md +++ /dev/null @@ -1,4 +0,0 @@ -# Internal Details - -This section describes some of the internal details of TaskChampion. -While this section is not required to use TaskChampion, understanding some of these details may help to understand how TaskChampion behaves. diff --git a/docs/src/plans.md b/docs/src/plans.md new file mode 100644 index 000000000..4ee20c5d0 --- /dev/null +++ b/docs/src/plans.md @@ -0,0 +1,35 @@ +# Planned Functionality + +This section is a bit of a to-do list for additional functionality to add to the synchronzation system. +Each feature has some discussion of how it might be implemented. + +## Snapshots + +As designed, storage required on the server would grow with time, as would the time required for new clients to update to the latest version. +As an optimization, the server also stores "snapshots" containing a full copy of the task database at a given version. +Based on configurable heuristics, it may delete older operations and snapshots, as long as enough data remains for active clients to synchronize and for new clients to initialize. + +Since snapshots must be computed by clients, the server may "request" a snapshot when providing the latest version to a client. +This request comes with a number indicating how much it 'wants" the snapshot. +Clients which can easily generate and transmit a snapshot should be generous to the server, while clients with more limited resources can wait until the server's requests are more desperate. +The intent is, where possible, to request snapshots created on well-connected desktop clients over mobile and low-power clients. + +## Encryption and Signing + +From the server's perspective, all data except for version numbers are opaque binary blobs. +Clients encrypt and sign these blobs using a symmetric key known only to the clients. +This secures the data at-rest on the server. +Note that privacy is not complete, as the server still has some information about users, including source and frequency of synchronization transactions and size of those transactions. + +## Backups + +In this design, the server is little more than an authenticated storage for encrypted blobs provided by the client. +To allow for failure or data loss on the server, clients are expected to cache these blobs locally for a short time (a week), along with a server-provided HMAC signature. +When data loss is detected -- such as when a client expects the server to have a version N or higher, and the server only has N-1, the client can send those blobs to the server. +The server can validate the HMAC and, if successful, add the blobs to its datastore. + +## Expiration + +Deleted tasks remain in the task database, and are simply hidden in most views. +All tasks have an expiration time after which they may be flushed, preventing unbounded increase in task database size. +However, purging of a task does not satisfy the necessary OT guarantees, so some further formal design work is required before this is implemented. diff --git a/docs/src/storage.md b/docs/src/storage.md new file mode 100644 index 000000000..a4f967fc4 --- /dev/null +++ b/docs/src/storage.md @@ -0,0 +1,73 @@ +# Replica Storage + +Each replica has a storage backend. +The interface for this backend is given in `crate::taskstorage::TaskStorage` and `TaskStorageTxn`. + +The storage is transaction-protected, with the expectation of a serializable isolation level. +The storage contains the following information: + +- `tasks`: a set of tasks, indexed by UUID +- `base_version`: the number of the last version sync'd from the server +- `operations`: all operations performed since base_version +- `working_set`: a mapping from integer -> UUID, used to keep stable small-integer indexes into the tasks for users' convenience. This data is not synchronized with the server and does not affect any consistency guarantees. + +## Tasks + +The tasks are stored as an un-ordered collection, keyed by task UUID. +Each task in the database has an arbitrary-sized set of key/value properties, with string values. + +Tasks are only created and modified; "deleted" tasks continue to stick around and can be modified and even un-deleted. +Tasks have an expiration time, after which they may be purged from the database. + +### Task Fields + +Each task can have any of the following fields. +Timestamps are stored as UNIX epoch timestamps, in the form of an integer expressed in decimal notation. +Note that it is possible, in task storage, for any field to be omitted. + +NOTE: This structure is based on https://taskwarrior.org/docs/design/task.html, but will diverge from that +model over time. + +* `status` - one of `Pending`, `Completed`, `Deleted`, `Recurring`, or `Waiting` +* `entry` (timestamp) - time that the task was created +* `description` - the one-line summary of the task +* `start` (timestamp) - if set, the task is active and this field gives the time the task was started +* `end` (timestamp) - the time at which the task was deleted or completed +* `due` (timestamp) - the time at which the task is due +* `until` (timestamp) - the time after which recurrent child tasks should not be created +* `wait` (timestamp) - the time before which this task is considered waiting and should not be shown +* `modified` (timestamp) - time that the task was last modified +* `scheduled` (timestamp) - time that the task is available to start +* `recur` - recurrence frequency +* `mask` - recurrence history +* `imask` - for children of recurring tasks, the index into the `mask` property on the parent +* `parent` - for children of recurring tasks, the uuid of the parent task +* `project` - the task's project (usually a short identifier) +* `priority` - the task's priority, one of `L`, `M`, or `H`. +* `depends` - a comma (`,`) separated list of uuids of tasks on which this task depends +* `tags` - a comma (`,`) separated list of tags for this task +* `annotation_` - an annotation for this task, with the timestamp as part of the key +* `udas` - user-defined attributes + +## Operations + +Every change to the task database is captured as an operation. +In other words, operations act as deltas between database states. +Operations are crucial to synchronization of replicas, using a technique known as Operational Transforms. + +Each operation has one of the forms + + * `Create(uuid)` + * `Delete(uuid)` + * `Update(uuid, property, value, timestamp)` + +The Create form creates a new task. +It is invalid to create a task that already exists. + +Similarly, the Delete form deletes an existing task. +It is invalid to delete a task that does not exist. + +The Update form updates the given property of the given task, where property and value are both strings. +Value can also be `None` to indicate deletion of a property. +It is invalid to update a task that does not exist. +The timestamp on updates serves as additional metadata and is used to resolve conflicts. diff --git a/docs/src/sync.md b/docs/src/sync.md new file mode 100644 index 000000000..3f664542e --- /dev/null +++ b/docs/src/sync.md @@ -0,0 +1,120 @@ +# Synchronization + +The [task database](./taskdb.md) also implements synchronization. +Synchronization occurs between disconnected replicas, mediated by a server. +The replicas never communicate directly with one another. +The server does not have access to the task data; it sees only opaque blobs of data with a small amount of metadata. + +The synchronization process is a critical part of the task database's functionality, and it cannot function efficiently without occasional synchronization operations + +## Operational Transformations + +Synchronization is based on [operational transformation](https://en.wikipedia.org/wiki/Operational_transformation). +This section will assume some familiarity with the concept. + +## State and Operations + +At a given time, the set of tasks in a replica's storage is the essential "state" of that replica. +All modifications to that state occur via operations, as defined in [Replica Storage](./storage.md). +We can draw a network, or graph, with the nodes representing states and the edges representing operations. +For example: + +```text + o -- State: {abc-d123: 'get groceries', priority L} + | + | -- Operation: set abc-d123 priority to H + | + o -- State: {abc-d123: 'get groceries', priority H} +``` + +For those familiar with distributed version control systems, a state is analogous to a revision, while an operation is analogous to a commit. + +Fundamentally, synchronization involves all replicas agreeing on a single, linear sequence of operations and the state that those operations create. +Since the replicas are not connected, each may have additional operations that have been applied locally, but which have not yet been agreed on. +The synchronization process uses operational transformation to "linearize" those operations. +This process is analogous (vaguely) to rebasing a sequence of Git commits. + +### Versions + +Occasionally, database states are named with an integer, called a version. +The system as a whole (all replicas) constructs a monotonic sequence of versions and the operations that separate each version from the next. +No gaps are allowed in the version numbering. +Version 0 is implicitly the empty database. + +The server stores the operations to change a state from a version N to a version N+1, and provides that information as needed to replicas. +Replicas use this information to update their local task databases, and to generate new versions to send to the server. + +Replicas generate a new version to transmit changes made locally to the server. +The changes are represented as a sequence of operations with the state resulting from the final operation corresponding to the version. +In order to keep the gap-free monotonic numbering, the server will only accept a proposed version from a replica if its number is one greater that the latest version on the server. + +In the non-conflict case (such as with a single replica), then, a replica's synchronization process involves gathering up the operations it has accumulated since its last synchronization; bundling those operations into version N+1; and sending that version to the server. + +### Transformation + +When the latest version on the server contains operations that are not present in the replica, then the states have diverged. +For example (with lower-case letters designating operations): + +```text + o -- version N + w|\a + o o + x| \b + o o + y| \c + o o -- replica's local state + z| + o -- version N+1 +``` + +In this situation, the replica must "rebase" the local operations onto the latest version from the server and try again. +This process is performed using operational transformation (OT). +The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the replica can apply to its local task database to reach the same state +Continuing the example above, the resulting operations are shown with `'`: + +```text + o -- version N + w|\a + o o + x| \b + o o + y| \c + o o -- replica's intermediate local state + z| |w' + o-N+1 o + a'\ |x' + o o + b'\ |y' + o o + c'\|z' + o -- version N+2 +``` + +The replica applies w' through z' locally, and sends a' through c' to the server as the operations to generate version N+2. +Either path through this graph, a-b-c-w'-x'-y'-z' or a'-b'-c'-w-x-y-z, must generate *precisely* the same final state at version N+2. +Careful selection of the operations and the transformation function ensure this. + +See the comments in the source code for the details of how this transformation process is implemented. + +## Replica Implementation + +The replica's [storage](./storage.md) contains the current state in `tasks`, the as-yet un-synchronized operations in `operations`, and the last version at which synchronization occurred in `base_version`. + +To perform a synchronization, the replica first requests any versions greater than `base_version` from the server, and rebases any local operations on top of those new versions, updating `base_version`. +If there are no un-synchronized local operations, the process is complete. +Otherwise, the replica creates a new version containing those local operations and uploads that to the server. +In most cases, this will succeed, but if another replica has created a new version in the interim, then the new version will conflict with that other replica's new version. +In this case, the process repeats. + +The replica's un-synchronized operations are already reflected in `tasks`, so the following invariant holds: + +> Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical +> to `tasks`. + +## Server Implementation + +The server implementation is simple. +It supports fetching versions keyed by number, and adding a new version. +In adding a new version, the version number must be one greater than the greatest existing version. + +Critically, the server operates on nothing more than numbered, opaque blobs of data. diff --git a/docs/src/taskdb.md b/docs/src/taskdb.md new file mode 100644 index 000000000..40ee74b9c --- /dev/null +++ b/docs/src/taskdb.md @@ -0,0 +1,28 @@ +# Task Database + +The task database is a layer of abstraction above the replica storage layer, responsible for maintaining some important invariants. +While the storage is pluggable, there is only one implementation of the task database. + +## Reading Data + +The task database provides read access to the data in the replica's storage through a variety of methods on the struct. +Each read operation is executed in a transaction, so data may not be consistent between read operations. +In practice, this is not an issue for TaskChampion's purposes. + +## Working Set + +The task database maintains the working set. +The working set maps small integers to current tasks, for easy reference by command-line users. +This is done in such a way that the task numbers remain stable until the working set is rebuilt, at which point gaps in the numbering, such as for completed tasks, are removed by shifting all higher-numbered tasks downward. + +The working set is not replicated, and is not considered a part of any consistency guarantees in the task database. + +## Modifying Data + +Modifications to the data set are made by applying operations. +Operations are described in [Replica Storage](./storage.md). + +Each operation is added to the list of operations in the storage, and simultaneously applied to the tasks in that storage. +Operations are checked for validity as they are applied. + + diff --git a/src/taskstorage/mod.rs b/src/taskstorage/mod.rs index 5c2d879bf..92f0216c3 100644 --- a/src/taskstorage/mod.rs +++ b/src/taskstorage/mod.rs @@ -86,20 +86,6 @@ pub trait TaskStorageTxn { /// A trait for objects able to act as backing storage for a DB. This API is optimized to be /// easy to implement, with all of the semantic meaning of the data located in the DB /// implementation, which is the sole consumer of this trait. -/// -/// Conceptually, task storage contains the following: -/// -/// - tasks: a set of tasks indexed by uuid -/// - base_version: the number of the last version sync'd from the server -/// - operations: all operations performed since base_version -/// - working_set: a mapping from integer -> uuid, used to keep stable small-integer indexes -/// into the tasks. The replica maintains this list. It is not covered by operations. -/// -/// The `operations` are already reflected in `tasks`, so the following invariant holds: -/// > Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical -/// > to `tasks`. -/// -/// It is up to the caller (DB) to maintain this invariant. pub trait TaskStorage { /// Begin a transaction fn txn<'a>(&'a mut self) -> Fallible>; From 03e4fc7cee84a27c1e4a95581345ee47452228e2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 22 Nov 2020 18:18:53 -0500 Subject: [PATCH 053/548] Refactor working-set support, add pending tasks This refactors the working-set support so that taskdb knows how to rebuild the working set (in a single transaction) but replica knows what tasks should be in that set. This also adds support for automatically adding tasks to the working set when they are marked pending. Note that tasks are not *removed* from the working set automatically, but only on a gc operation. --- src/replica.rs | 56 ++++++++++++++++++++++++++++++++--- src/taskdb.rs | 58 +++++++++++++++++++++++++++---------- src/taskstorage/inmemory.rs | 24 +++++++-------- src/taskstorage/kv.rs | 24 +++++++-------- src/taskstorage/mod.rs | 2 +- 5 files changed, 120 insertions(+), 44 deletions(-) diff --git a/src/replica.rs b/src/replica.rs index 4d4a0e96e..de23f89b0 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -35,6 +35,21 @@ impl Replica { }) } + /// Return true if this status string is such that the task should be included in + /// the working set. + fn is_working_set_status(status: Option<&String>) -> bool { + if let Some(status) = status { + status == "pending" + } else { + false + } + } + + /// Add the given uuid to the working set, returning its index. + fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + self.taskdb.add_to_working_set(uuid) + } + /// Get all tasks represented as a map keyed by UUID pub fn all_tasks<'a>(&'a mut self) -> Fallible> { Ok(self @@ -72,6 +87,17 @@ impl Replica { Ok(self.taskdb.get_task(&uuid)?.map(|t| (&t).into())) } + /// Get an existing task by its working set index + pub fn get_working_set_task(&mut self, i: u64) -> Fallible> { + let working_set = self.taskdb.working_set()?; + if (i as usize) < working_set.len() { + if let Some(uuid) = working_set[i as usize] { + return Ok(self.taskdb.get_task(&uuid)?.map(|t| (&t).into())); + } + } + return Ok(None); + } + /// Create a new task. The task must not already exist. pub fn new_task( &mut self, @@ -115,9 +141,10 @@ impl Replica { } /// Perform "garbage collection" on this replica. In particular, this renumbers the working - /// set. + /// set to contain only pending tasks. pub fn gc(&mut self) -> Fallible<()> { - self.taskdb.rebuild_working_set()?; + self.taskdb + .rebuild_working_set(|t| Replica::is_working_set_status(t.get("status")))?; Ok(()) } } @@ -177,9 +204,14 @@ impl<'a> TaskMut<'a> { ) } - /// Set the task's status + /// Set the task's status. This also adds the task to the working set if the + /// new status puts it in that set. pub fn status(&mut self, status: Status) -> Fallible<()> { - self.set_string("status", Some(String::from(status.as_ref()))) + let status = String::from(status.as_ref()); + if Replica::is_working_set_status(Some(&status)) { + self.replica.add_to_working_set(&self.uuid)?; + } + self.set_string("status", Some(status)) } /// Set the task's description @@ -326,6 +358,22 @@ mod tests { assert_eq!(t.project, Some("work".into())); } + #[test] + fn set_pending_adds_to_working_set() { + let mut rep = Replica::new(DB::new_inmemory().into()); + let uuid = Uuid::new_v4(); + + rep.new_task(uuid.clone(), Status::Pending, "to-be-pending".into()) + .unwrap(); + + let mut tm = rep.get_task_mut(&uuid).unwrap().unwrap(); + tm.status(Status::Pending).unwrap(); + + let t = rep.get_working_set_task(1).unwrap().unwrap(); + assert_eq!(t.status, Status::Pending); + assert_eq!(t.description, String::from("to-be-pending")); + } + #[test] fn get_does_not_exist() { let mut rep = Replica::new(DB::new_inmemory().into()); diff --git a/src/taskdb.rs b/src/taskdb.rs index 2613cc39e..33306f359 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -104,24 +104,27 @@ impl DB { txn.get_task(uuid) } - /// Rebuild the working set. This renumbers the pending tasks to eliminate gaps, and also - /// finds any tasks whose statuses changed without being noticed. - pub fn rebuild_working_set(&mut self) -> Fallible<()> { - // TODO: this logic belongs in Replica - // TODO: it's every status but Completed and Deleted, I think? + /// Rebuild the working set using a function to identify tasks that should be in the set. This + /// renumbers the existing working-set tasks to eliminate gaps, and also adds any tasks that + /// are not already in the working set but should be. The rebuild occurs in a single + /// trasnsaction against the storage backend. + pub fn rebuild_working_set(&mut self, in_working_set: F) -> Fallible<()> + where + F: Fn(&TaskMap) -> bool, + { let mut txn = self.storage.txn()?; let mut new_ws = vec![]; let mut seen = HashSet::new(); - let pending = String::from("pending"); - // The goal here is for existing working-set items to be "compressed' down to index - // 1, so we begin by scanning the current working set and inserting any still-pending - // tasks into the new list + // The goal here is for existing working-set items to be "compressed' down to index 1, so + // we begin by scanning the current working set and inserting any tasks that should still + // be in the set into new_ws, implicitly dropping any tasks that are no longer in the + // working set. for elt in txn.get_working_set()? { if let Some(uuid) = elt { if let Some(task) = txn.get_task(&uuid)? { - if task.get("status") == Some(&pending) { + if in_working_set(&task) { new_ws.push(uuid.clone()); seen.insert(uuid); } @@ -129,24 +132,43 @@ impl DB { } } - // Now go hunting for tasks that are pending and are not already in this list + // Now go hunting for tasks that should be in this list but are not, adding them at the + // end of the list. for (uuid, task) in txn.all_tasks()? { if !seen.contains(&uuid) { - if task.get("status") == Some(&pending) { + if in_working_set(&task) { new_ws.push(uuid.clone()); } } } + // clear and re-write the entire working set, in order txn.clear_working_set()?; for uuid in new_ws.drain(0..new_ws.len()) { - txn.add_to_working_set(uuid)?; + txn.add_to_working_set(&uuid)?; } txn.commit()?; Ok(()) } + /// Add the given uuid to the working set and return its index; if it is already in the working + /// set, its index is returned. This does *not* renumber any existing tasks. + pub fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + let mut txn = self.storage.txn()?; + // search for an existing entry for this task.. + for (i, elt) in txn.get_working_set()?.iter().enumerate() { + if *elt == Some(*uuid) { + // (note that this drops the transaction with no changes made) + return Ok(i as u64); + } + } + // and if not found, add one + let i = txn.add_to_working_set(uuid)?; + txn.commit()?; + Ok(i) + } + /// Sync to the given server, pulling remote changes and pushing local changes. pub fn sync(&mut self, username: &str, server: &mut Server) -> Fallible<()> { let mut txn = self.storage.txn()?; @@ -448,7 +470,7 @@ mod tests { txn.clear_working_set()?; for i in &[1usize, 3, 4] { - txn.add_to_working_set(uuids[*i])?; + txn.add_to_working_set(&uuids[*i])?; } txn.commit()?; @@ -464,7 +486,13 @@ mod tests { ] ); - db.rebuild_working_set()?; + db.rebuild_working_set(|t| { + if let Some(status) = t.get("status") { + status == "pending" + } else { + false + } + })?; // uuids[1] and uuids[4] are already in the working set, so are compressed // to the top, and then uuids[0] is added. diff --git a/src/taskstorage/inmemory.rs b/src/taskstorage/inmemory.rs index e89d31bc7..6b272874c 100644 --- a/src/taskstorage/inmemory.rs +++ b/src/taskstorage/inmemory.rs @@ -109,9 +109,9 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(self.data_ref().working_set.clone()) } - fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { + fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { let working_set = &mut self.mut_data_ref().working_set; - working_set.push(Some(uuid)); + working_set.push(Some(uuid.clone())); Ok(working_set.len() as u64) } @@ -194,8 +194,8 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1.clone())?; - txn.add_to_working_set(uuid2.clone())?; + txn.add_to_working_set(&uuid1)?; + txn.add_to_working_set(&uuid2)?; txn.commit()?; } @@ -216,15 +216,15 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1.clone())?; - txn.add_to_working_set(uuid2.clone())?; + txn.add_to_working_set(&uuid1)?; + txn.add_to_working_set(&uuid2)?; txn.commit()?; } { let mut txn = storage.txn()?; txn.remove_from_working_set(1)?; - txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(&uuid1)?; txn.commit()?; } @@ -244,7 +244,7 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(&uuid1)?; txn.commit()?; } @@ -267,16 +267,16 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1.clone())?; - txn.add_to_working_set(uuid2.clone())?; + txn.add_to_working_set(&uuid1)?; + txn.add_to_working_set(&uuid2)?; txn.commit()?; } { let mut txn = storage.txn()?; txn.clear_working_set()?; - txn.add_to_working_set(uuid2.clone())?; - txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(&uuid2)?; + txn.add_to_working_set(&uuid1)?; txn.commit()?; } diff --git a/src/taskstorage/kv.rs b/src/taskstorage/kv.rs index c8a6d85d5..08f2ade01 100644 --- a/src/taskstorage/kv.rs +++ b/src/taskstorage/kv.rs @@ -307,7 +307,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(res) } - fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { + fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); let kvtxn = self.kvtxn(); @@ -321,7 +321,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { kvtxn.set( working_set_bucket, next_index.into(), - Msgpack::to_value_buf(uuid)?, + Msgpack::to_value_buf(uuid.clone())?, )?; kvtxn.set( numbers_bucket, @@ -666,8 +666,8 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1.clone())?; - txn.add_to_working_set(uuid2.clone())?; + txn.add_to_working_set(&uuid1)?; + txn.add_to_working_set(&uuid2)?; txn.commit()?; } @@ -689,15 +689,15 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1.clone())?; - txn.add_to_working_set(uuid2.clone())?; + txn.add_to_working_set(&uuid1)?; + txn.add_to_working_set(&uuid2)?; txn.commit()?; } { let mut txn = storage.txn()?; txn.remove_from_working_set(1)?; - txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(&uuid1)?; txn.commit()?; } @@ -718,7 +718,7 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(&uuid1)?; txn.commit()?; } @@ -742,16 +742,16 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1.clone())?; - txn.add_to_working_set(uuid2.clone())?; + txn.add_to_working_set(&uuid1)?; + txn.add_to_working_set(&uuid2)?; txn.commit()?; } { let mut txn = storage.txn()?; txn.clear_working_set()?; - txn.add_to_working_set(uuid2.clone())?; - txn.add_to_working_set(uuid1.clone())?; + txn.add_to_working_set(&uuid2)?; + txn.add_to_working_set(&uuid1)?; txn.commit()?; } diff --git a/src/taskstorage/mod.rs b/src/taskstorage/mod.rs index 92f0216c3..b5681f4de 100644 --- a/src/taskstorage/mod.rs +++ b/src/taskstorage/mod.rs @@ -70,7 +70,7 @@ pub trait TaskStorageTxn { /// Add a task to the working set and return its (one-based) index. This index will be one greater /// than the highest used index. - fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible; + fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible; /// Remove a task from the working set. Other tasks' indexes are not affected. fn remove_from_working_set(&mut self, index: u64) -> Fallible<()>; From 634aaadb7397d743d1360d9e7aa886488736f433 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 12:38:32 -0500 Subject: [PATCH 054/548] Reorganize handling of task data This abandons field-by-field compatibility with the TaskWarrior TDB2 format, which wasn't a sustainable strategy anyway. Instead, tasks are represented as a TaskMap with custom key formats. In this commit, there are only a few allowed keys, with room to grow. Replica returns convenience wrappers Task (read-only) and TaskMut (read-write) with getters and setters to make modifying tasks easier. --- Cargo.lock | 16 --- Cargo.toml | 1 - docs/src/SUMMARY.md | 1 + docs/src/storage.md | 38 +---- docs/src/tasks.md | 38 +++++ src/bin/task.rs | 4 +- src/replica.rs | 299 +++++++++++----------------------------- src/task/mod.rs | 2 - src/task/task.rs | 198 ++++++++++++++++++++------ src/task/taskbuilder.rs | 169 ----------------------- 10 files changed, 276 insertions(+), 490 deletions(-) create mode 100644 docs/src/tasks.md delete mode 100644 src/task/taskbuilder.rs diff --git a/Cargo.lock b/Cargo.lock index 8f78fcdfb..61a97dabe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,12 +131,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - [[package]] name = "failure" version = "0.1.6" @@ -182,15 +176,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "0.4.4" @@ -622,7 +607,6 @@ dependencies = [ "chrono", "clap", "failure", - "itertools", "kv", "lmdb-rkv", "proptest", diff --git a/Cargo.toml b/Cargo.toml index e1f5ba545..8e5c07492 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ failure = {version = "0.1.5", features = ["derive"] } clap = "~2.33.0" kv = {version = "0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "0.12.3"} -itertools = "0.9.0" [dev-dependencies] proptest = "0.9.4" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 57c42fe10..4555c20c6 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -6,5 +6,6 @@ - [Data Model](./data-model.md) - [Replica Storage](./storage.md) - [Task Database](./taskdb.md) + - [Tasks](./tasks.md) - [Synchronization](./sync.md) - [Planned Functionality](./plans.md) diff --git a/docs/src/storage.md b/docs/src/storage.md index a4f967fc4..977aace74 100644 --- a/docs/src/storage.md +++ b/docs/src/storage.md @@ -7,47 +7,15 @@ The storage is transaction-protected, with the expectation of a serializable iso The storage contains the following information: - `tasks`: a set of tasks, indexed by UUID -- `base_version`: the number of the last version sync'd from the server +- `base_version`: the number of the last version sync'd from the server (a single integer) - `operations`: all operations performed since base_version - `working_set`: a mapping from integer -> UUID, used to keep stable small-integer indexes into the tasks for users' convenience. This data is not synchronized with the server and does not affect any consistency guarantees. ## Tasks The tasks are stored as an un-ordered collection, keyed by task UUID. -Each task in the database has an arbitrary-sized set of key/value properties, with string values. - -Tasks are only created and modified; "deleted" tasks continue to stick around and can be modified and even un-deleted. -Tasks have an expiration time, after which they may be purged from the database. - -### Task Fields - -Each task can have any of the following fields. -Timestamps are stored as UNIX epoch timestamps, in the form of an integer expressed in decimal notation. -Note that it is possible, in task storage, for any field to be omitted. - -NOTE: This structure is based on https://taskwarrior.org/docs/design/task.html, but will diverge from that -model over time. - -* `status` - one of `Pending`, `Completed`, `Deleted`, `Recurring`, or `Waiting` -* `entry` (timestamp) - time that the task was created -* `description` - the one-line summary of the task -* `start` (timestamp) - if set, the task is active and this field gives the time the task was started -* `end` (timestamp) - the time at which the task was deleted or completed -* `due` (timestamp) - the time at which the task is due -* `until` (timestamp) - the time after which recurrent child tasks should not be created -* `wait` (timestamp) - the time before which this task is considered waiting and should not be shown -* `modified` (timestamp) - time that the task was last modified -* `scheduled` (timestamp) - time that the task is available to start -* `recur` - recurrence frequency -* `mask` - recurrence history -* `imask` - for children of recurring tasks, the index into the `mask` property on the parent -* `parent` - for children of recurring tasks, the uuid of the parent task -* `project` - the task's project (usually a short identifier) -* `priority` - the task's priority, one of `L`, `M`, or `H`. -* `depends` - a comma (`,`) separated list of uuids of tasks on which this task depends -* `tags` - a comma (`,`) separated list of tags for this task -* `annotation_` - an annotation for this task, with the timestamp as part of the key -* `udas` - user-defined attributes +Each task in the database has represented by a key-value map. +See [Tasks](./tasks.md) for details on the content of that map. ## Operations diff --git a/docs/src/tasks.md b/docs/src/tasks.md new file mode 100644 index 000000000..5727237dc --- /dev/null +++ b/docs/src/tasks.md @@ -0,0 +1,38 @@ +# Tasks + +Tasks are stored internally as a key/value map with string keys and values. +All fields are optional: the `Create` operation creates an empty task. +Display layers should apply appropriate defaults where necessary. + +## Atomicity + +The synchronization process does not support read-modify-write operations. +For example, suppose tags are updated by reading a list of tags, adding a tag, and writing the result back. +This would be captured as an `Update` operation containing the amended list of tags. +Suppose two such `Update` operations are made in different replicas and must be reconciled: + * `Update("d394be59-60e6-499e-b7e7-ca0142648409", "tags", "oldtag,newtag1", "2020-11-23T14:21:22Z")` + * `Update("d394be59-60e6-499e-b7e7-ca0142648409", "tags", "oldtag,newtag2", "2020-11-23T15:08:57Z")` + +The result of this reconciliation will be `oldtag,newtag2`, while the user almost certainly intended `oldtag,newtag1,newtag2`. + +The key names given below avoid this issue, allowing user updates such as adding a tag or deleting a dependency to be represented in a single `Update` operation. + +## Representations + +Integers are stored in decimal notation. + +Timestamps are stored as UNIX epoch timestamps, in the form of an integer. + +## Keys + +The following keys, and key formats, are defined: + +* `status` - one of `P` for a pending task (the default), `C` for completed or `D` for deleted +* `description` - the one-line summary of the task +* `modified` - the time of the last modification of this task + +The following are not yet implemented: + +* `dep.` - indicates this task depends on `` (value is an empty string) +* `tag.` - indicates this task has tag `` (value is an empty string) +* `annotation.` - value is an annotation created at the given time diff --git a/src/bin/task.rs b/src/bin/task.rs index b816bcc3c..0362eb83f 100644 --- a/src/bin/task.rs +++ b/src/bin/task.rs @@ -47,8 +47,8 @@ fn main() { ("pending", _) => { let working_set = replica.working_set().unwrap(); for i in 1..working_set.len() { - if let Some((ref uuid, ref task)) = working_set[i] { - println!("{}: {} - {:?}", i, uuid, task); + if let Some(ref task) = working_set[i] { + println!("{}: {} - {:?}", i, task.get_uuid(), task); } } } diff --git a/src/replica.rs b/src/replica.rs index de23f89b0..507ea9291 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -1,11 +1,10 @@ use crate::errors::Error; use crate::operation::Operation; -use crate::task::{Priority, Status, Task, TaskBuilder}; +use crate::task::{Status, Task}; use crate::taskdb::DB; use crate::taskstorage::TaskMap; -use chrono::{DateTime, Utc}; +use chrono::Utc; use failure::Fallible; -use itertools::join; use std::collections::HashMap; use uuid::Uuid; @@ -22,7 +21,12 @@ impl Replica { /// Update an existing task. If the value is Some, the property is added or updated. If the /// value is None, the property is deleted. It is not an error to delete a nonexistent /// property. - fn update_task(&mut self, uuid: Uuid, property: S1, value: Option) -> Fallible<()> + pub(crate) fn update_task( + &mut self, + uuid: Uuid, + property: S1, + value: Option, + ) -> Fallible<()> where S1: Into, S2: Into, @@ -35,29 +39,18 @@ impl Replica { }) } - /// Return true if this status string is such that the task should be included in - /// the working set. - fn is_working_set_status(status: Option<&String>) -> bool { - if let Some(status) = status { - status == "pending" - } else { - false - } - } - /// Add the given uuid to the working set, returning its index. - fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + pub(crate) fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { self.taskdb.add_to_working_set(uuid) } /// Get all tasks represented as a map keyed by UUID pub fn all_tasks<'a>(&'a mut self) -> Fallible> { - Ok(self - .taskdb - .all_tasks()? - .iter() - .map(|(k, v)| (k.clone(), v.into())) - .collect()) + let mut res = HashMap::new(); + for (uuid, tm) in self.taskdb.all_tasks()?.drain(..) { + res.insert(uuid.clone(), Task::new(uuid.clone(), tm)); + } + Ok(res) } /// Get the UUIDs of all tasks @@ -67,13 +60,13 @@ impl Replica { /// Get the "working set" for this replica -- the set of pending tasks, as indexed by small /// integers - pub fn working_set(&mut self) -> Fallible>> { + pub fn working_set(&mut self) -> Fallible>> { let working_set = self.taskdb.working_set()?; let mut res = Vec::with_capacity(working_set.len()); for i in 0..working_set.len() { res.push(match working_set[i] { Some(u) => match self.taskdb.get_task(&u)? { - Some(task) => Some((u, (&task).into())), + Some(tm) => Some(Task::new(u, tm)), None => None, }, None => None, @@ -84,7 +77,10 @@ impl Replica { /// Get an existing task by its UUID pub fn get_task(&mut self, uuid: &Uuid) -> Fallible> { - Ok(self.taskdb.get_task(&uuid)?.map(|t| (&t).into())) + Ok(self + .taskdb + .get_task(uuid)? + .map(move |tm| Task::new(uuid.clone(), tm))) } /// Get an existing task by its working set index @@ -92,19 +88,17 @@ impl Replica { let working_set = self.taskdb.working_set()?; if (i as usize) < working_set.len() { if let Some(uuid) = working_set[i as usize] { - return Ok(self.taskdb.get_task(&uuid)?.map(|t| (&t).into())); + return Ok(self + .taskdb + .get_task(&uuid)? + .map(move |tm| Task::new(uuid, tm))); } } return Ok(None); } /// Create a new task. The task must not already exist. - pub fn new_task( - &mut self, - uuid: Uuid, - status: Status, - description: String, - ) -> Fallible { + pub fn new_task(&mut self, uuid: Uuid, status: Status, description: String) -> Fallible { // check that it doesn't exist; this is a convenience check, as the task // may already exist when this Create operation is finally sync'd with // operations from other replicas @@ -113,15 +107,14 @@ impl Replica { } self.taskdb .apply(Operation::Create { uuid: uuid.clone() })?; - self.update_task(uuid.clone(), "status", Some(String::from(status.as_ref())))?; - self.update_task(uuid.clone(), "description", Some(description))?; - let now = format!("{}", Utc::now().timestamp()); - self.update_task(uuid.clone(), "entry", Some(now.clone()))?; - self.update_task(uuid.clone(), "modified", Some(now))?; - Ok(TaskMut::new(self, uuid)) + let mut task = Task::new(uuid, TaskMap::new()).into_mut(self); + task.set_description(description)?; + task.set_status(status)?; + Ok(task.into_immut()) } - /// Delete a task. The task must exist. + /// Delete a task. The task must exist. Note that this is different from setting status to + /// Deleted; this is the final purge of the task. pub fn delete_task(&mut self, uuid: Uuid) -> Fallible<()> { // check that it already exists; this is a convenience check, as the task may already exist // when this Create operation is finally sync'd with operations from other replicas @@ -131,196 +124,61 @@ impl Replica { self.taskdb.apply(Operation::Delete { uuid }) } - /// Get an existing task by its UUID, suitable for modification - pub fn get_task_mut<'a>(&'a mut self, uuid: &Uuid) -> Fallible>> { - // the call to get_task is to ensure the task exists locally - Ok(self - .taskdb - .get_task(&uuid)? - .map(move |_| TaskMut::new(self, uuid.clone()))) - } - /// Perform "garbage collection" on this replica. In particular, this renumbers the working /// set to contain only pending tasks. pub fn gc(&mut self) -> Fallible<()> { + let pending = String::from(Status::Pending.to_taskmap()); self.taskdb - .rebuild_working_set(|t| Replica::is_working_set_status(t.get("status")))?; + .rebuild_working_set(|t| t.get("status") == Some(&pending))?; Ok(()) } } -impl From<&TaskMap> for Task { - fn from(taskmap: &TaskMap) -> Task { - let mut bldr = TaskBuilder::new(); - for (k, v) in taskmap.iter() { - bldr = bldr.set(k, v.into()); - } - bldr.finish() - } -} - -// TODO: move this struct to crate::task, with a trait for update_task, since it is the reverse -// of TaskBuilder::set -/// TaskMut allows changes to a task. It is intended for short-term use, such as changing a few -/// properties, and should not be held for long periods of wall-clock time. -pub struct TaskMut<'a> { - replica: &'a mut Replica, - uuid: Uuid, - // if true, then this TaskMut has already updated the `modified` property and need not do so - // again. - updated_modified: bool, -} - -impl<'a> TaskMut<'a> { - fn new(replica: &'a mut Replica, uuid: Uuid) -> TaskMut { - TaskMut { - replica, - uuid, - updated_modified: false, - } - } - - fn lastmod(&mut self) -> Fallible<()> { - if !self.updated_modified { - let now = format!("{}", Utc::now().timestamp()); - self.replica - .update_task(self.uuid.clone(), "modified", Some(now))?; - self.updated_modified = true; - } - Ok(()) - } - - fn set_string(&mut self, property: &str, value: Option) -> Fallible<()> { - self.lastmod()?; - self.replica.update_task(self.uuid.clone(), property, value) - } - - fn set_timestamp(&mut self, property: &str, value: Option>) -> Fallible<()> { - self.lastmod()?; - self.replica.update_task( - self.uuid.clone(), - property, - value.map(|v| format!("{}", v.timestamp())), - ) - } - - /// Set the task's status. This also adds the task to the working set if the - /// new status puts it in that set. - pub fn status(&mut self, status: Status) -> Fallible<()> { - let status = String::from(status.as_ref()); - if Replica::is_working_set_status(Some(&status)) { - self.replica.add_to_working_set(&self.uuid)?; - } - self.set_string("status", Some(status)) - } - - /// Set the task's description - pub fn description(&mut self, description: String) -> Fallible<()> { - self.set_string("description", Some(description)) - } - - /// Set the task's start time - pub fn start(&mut self, time: Option>) -> Fallible<()> { - self.set_timestamp("start", time) - } - - /// Set the task's end time - pub fn end(&mut self, time: Option>) -> Fallible<()> { - self.set_timestamp("end", time) - } - - /// Set the task's due time - pub fn due(&mut self, time: Option>) -> Fallible<()> { - self.set_timestamp("due", time) - } - - /// Set the task's until time - pub fn until(&mut self, time: Option>) -> Fallible<()> { - self.set_timestamp("until", time) - } - - /// Set the task's wait time - pub fn wait(&mut self, time: Option>) -> Fallible<()> { - self.set_timestamp("wait", time) - } - - /// Set the task's scheduled time - pub fn scheduled(&mut self, time: Option>) -> Fallible<()> { - self.set_timestamp("scheduled", time) - } - - /// Set the task's recur value - pub fn recur(&mut self, recur: Option) -> Fallible<()> { - self.set_string("recur", recur) - } - - /// Set the task's mask value - pub fn mask(&mut self, mask: Option) -> Fallible<()> { - self.set_string("mask", mask) - } - - /// Set the task's imask value - pub fn imask(&mut self, imask: Option) -> Fallible<()> { - self.set_string("imask", imask.map(|v| format!("{}", v))) - } - - /// Set the task's parent task - pub fn parent(&mut self, parent: Option) -> Fallible<()> { - self.set_string("parent", parent.map(|v| format!("{}", v))) - } - - /// Set the task's project - pub fn project(&mut self, project: Option) -> Fallible<()> { - self.set_string("project", project) - } - - /// Set the task's priority - pub fn priority(&mut self, priority: Option) -> Fallible<()> { - self.set_string("priority", priority.map(|v| String::from(v.as_ref()))) - } - - /// Set the task's depends; note that this completely replaces the list of tasks on which this - /// one depends. - pub fn depends(&mut self, depends: Vec) -> Fallible<()> { - self.set_string( - "depends", - if depends.len() > 0 { - Some(join(depends.iter(), ",")) - } else { - None - }, - ) - } - - /// Set the task's tags; note that this completely replaces the list of tags - pub fn tags(&mut self, tags: Vec) -> Fallible<()> { - self.set_string("tags", Some(join(tags.iter(), ","))) - } - - // TODO: annotations - // TODO: udas -} - #[cfg(test)] mod tests { use super::*; + use crate::task::Status; use crate::taskdb::DB; use uuid::Uuid; #[test] - fn new_task_and_modify() { + fn new_task() { let mut rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); - let mut tm = rep + let t = rep .new_task(uuid.clone(), Status::Pending, "a task".into()) .unwrap(); - tm.priority(Some(Priority::L)).unwrap(); + assert_eq!(t.get_description(), String::from("a task")); + assert_eq!(t.get_status(), Status::Pending); + assert!(t.get_modified().is_some()); + } + #[test] + fn modify_task() { + let mut rep = Replica::new(DB::new_inmemory().into()); + let uuid = Uuid::new_v4(); + + let t = rep + .new_task(uuid.clone(), Status::Pending, "a task".into()) + .unwrap(); + + let mut t = t.into_mut(&mut rep); + t.set_description(String::from("past tense")).unwrap(); + t.set_status(Status::Completed).unwrap(); + // check that values have changed on the TaskMut + assert_eq!(t.get_description(), "past tense"); + assert_eq!(t.get_status(), Status::Completed); + + // check that values have changed after into_immut + let t = t.into_immut(); + assert_eq!(t.get_description(), "past tense"); + assert_eq!(t.get_status(), Status::Completed); + + // check tha values have changed in storage, too let t = rep.get_task(&uuid).unwrap().unwrap(); - assert_eq!(t.description, String::from("a task")); - assert_eq!(t.status, Status::Pending); - assert_eq!(t.priority, Some(Priority::L)); + assert_eq!(t.get_description(), "past tense"); + assert_eq!(t.get_status(), Status::Completed); } #[test] @@ -344,34 +202,33 @@ mod tests { .unwrap(); let t = rep.get_task(&uuid).unwrap().unwrap(); - assert_eq!(t.description, String::from("another task")); + assert_eq!(t.get_description(), String::from("another task")); - let mut tm = rep.get_task_mut(&uuid).unwrap().unwrap(); - tm.status(Status::Completed).unwrap(); - tm.description("another task, updated".into()).unwrap(); - tm.priority(Some(Priority::L)).unwrap(); - tm.project(Some("work".into())).unwrap(); + let mut t = t.into_mut(&mut rep); + t.set_status(Status::Deleted).unwrap(); + t.set_description("gone".into()).unwrap(); let t = rep.get_task(&uuid).unwrap().unwrap(); - assert_eq!(t.status, Status::Completed); - assert_eq!(t.description, String::from("another task, updated")); - assert_eq!(t.project, Some("work".into())); + assert_eq!(t.get_status(), Status::Deleted); + assert_eq!(t.get_description(), "gone"); } #[test] - fn set_pending_adds_to_working_set() { + fn new_pending_adds_to_working_set() { let mut rep = Replica::new(DB::new_inmemory().into()); let uuid = Uuid::new_v4(); rep.new_task(uuid.clone(), Status::Pending, "to-be-pending".into()) .unwrap(); - let mut tm = rep.get_task_mut(&uuid).unwrap().unwrap(); - tm.status(Status::Pending).unwrap(); - let t = rep.get_working_set_task(1).unwrap().unwrap(); - assert_eq!(t.status, Status::Pending); - assert_eq!(t.description, String::from("to-be-pending")); + assert_eq!(t.get_status(), Status::Pending); + assert_eq!(t.get_description(), "to-be-pending"); + + let ws = rep.working_set().unwrap(); + assert_eq!(ws.len(), 2); + assert!(ws[0].is_none()); + assert_eq!(ws[1].as_ref().unwrap().get_uuid(), &uuid); } #[test] diff --git a/src/task/mod.rs b/src/task/mod.rs index f22b30c70..5e6b67770 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -1,7 +1,5 @@ mod task; -mod taskbuilder; pub use self::task::Priority::*; pub use self::task::Status::*; pub use self::task::{Annotation, Priority, Status, Task, Timestamp}; -pub use self::taskbuilder::TaskBuilder; diff --git a/src/task/task.rs b/src/task/task.rs index e6945f65a..a8b95989b 100644 --- a/src/task/task.rs +++ b/src/task/task.rs @@ -1,5 +1,7 @@ +use crate::replica::Replica; +use crate::taskstorage::TaskMap; use chrono::prelude::*; -use std::collections::HashMap; +use failure::Fallible; use std::convert::TryFrom; use uuid::Uuid; @@ -39,33 +41,26 @@ pub enum Status { Pending, Completed, Deleted, - Recurring, - Waiting, } -impl TryFrom<&str> for Status { - type Error = failure::Error; - - fn try_from(s: &str) -> Result { +impl Status { + /// Get a Status from the 1-character value in a TaskMap, + /// defaulting to Pending + pub(crate) fn from_taskmap(s: &str) -> Status { match s { - "pending" => Ok(Status::Pending), - "completed" => Ok(Status::Completed), - "deleted" => Ok(Status::Deleted), - "recurring" => Ok(Status::Recurring), - "waiting" => Ok(Status::Waiting), - _ => Err(format_err!("invalid status {}", s)), + "P" => Status::Pending, + "C" => Status::Completed, + "D" => Status::Deleted, + _ => Status::Pending, } } -} -impl AsRef for Status { - fn as_ref(&self) -> &str { + /// Get the 1-character value for this status to use in the TaskMap. + pub(crate) fn to_taskmap(&self) -> &str { match self { - Status::Pending => "pending", - Status::Completed => "completed", - Status::Deleted => "deleted", - Status::Recurring => "recurring", - Status::Waiting => "waiting", + Status::Pending => "P", + Status::Completed => "C", + Status::Deleted => "D", } } } @@ -76,30 +71,145 @@ pub struct Annotation { pub description: String, } -/// A task, the fundamental business object of this tool. +/// A task, as publicly exposed by this crate. /// -/// This structure is based on https://taskwarrior.org/docs/design/task.html with the -/// exception that the uuid property is omitted. +/// Note that Task objects represent a snapshot of the task at a moment in time, and are not +/// protected by the atomicity of the backend storage. Concurrent modifications are safe, +/// but a Task that is cached for more than a few seconds may cause the user to see stale +/// data. Fetch, use, and drop Tasks quickly. +/// +/// This struct contains only getters for various values on the task. The `into_mut` method returns +/// a TaskMut which can be used to modify the task. #[derive(Debug, PartialEq)] pub struct Task { - pub status: Status, - pub entry: Timestamp, - pub description: String, - pub start: Option, - pub end: Option, - pub due: Option, - pub until: Option, - pub wait: Option, - pub modified: Timestamp, - pub scheduled: Option, - pub recur: Option, - pub mask: Option, - pub imask: Option, - pub parent: Option, - pub project: Option, - pub priority: Option, - pub depends: Vec, - pub tags: Vec, - pub annotations: Vec, - pub udas: HashMap, + uuid: Uuid, + taskmap: TaskMap, +} + +/// A mutable task, with setter methods. Calling a setter will update the Replica, as well as the +/// included Task. +pub struct TaskMut<'r> { + task: Task, + replica: &'r mut Replica, + updated_modified: bool, +} + +impl Task { + pub(crate) fn new(uuid: Uuid, taskmap: TaskMap) -> Task { + Task { uuid, taskmap } + } + + pub fn get_uuid(&self) -> &Uuid { + &self.uuid + } + + /// Prepare to mutate this task, requiring a mutable Replica + /// in order to update the data it contains. + pub fn into_mut(self, replica: &mut Replica) -> TaskMut { + TaskMut { + task: self, + replica: replica, + updated_modified: false, + } + } + + pub fn get_status(&self) -> Status { + self.taskmap + .get("status") + .map(|s| Status::from_taskmap(s)) + .unwrap_or(Status::Pending) + } + + pub fn get_description(&self) -> &str { + self.taskmap + .get("description") + .map(|s| s.as_ref()) + .unwrap_or("") + } + + pub fn get_modified(&self) -> Option> { + self.get_timestamp("modified") + } + + // -- utility functions + + pub fn get_timestamp(&self, property: &str) -> Option> { + if let Some(ts) = self.taskmap.get(property) { + if let Ok(ts) = ts.parse() { + return Some(Utc.timestamp(ts, 0)); + } + // if the value does not parse as an integer, default to None + } + None + } +} + +impl<'r> TaskMut<'r> { + /// Get the immutable task + pub fn into_immut(self) -> Task { + self.task + } + + /// Set the task's status. This also adds the task to the working set if the + /// new status puts it in that set. + pub fn set_status(&mut self, status: Status) -> Fallible<()> { + if status == Status::Pending { + let uuid = self.uuid.clone(); + self.replica.add_to_working_set(&uuid)?; + } + self.set_string("status", Some(String::from(status.to_taskmap()))) + } + + /// Set the task's description + pub fn set_description(&mut self, description: String) -> Fallible<()> { + self.set_string("description", Some(description)) + } + + /// Set the task's description + pub fn set_modified(&mut self, modified: DateTime) -> Fallible<()> { + self.set_timestamp("modified", Some(modified)) + } + + // -- utility functions + + fn lastmod(&mut self) -> Fallible<()> { + if !self.updated_modified { + let now = format!("{}", Utc::now().timestamp()); + self.replica + .update_task(self.task.uuid.clone(), "modified", Some(now.clone()))?; + self.task.taskmap.insert(String::from("modified"), now); + self.updated_modified = true; + } + Ok(()) + } + + fn set_string(&mut self, property: &str, value: Option) -> Fallible<()> { + self.lastmod()?; + self.replica + .update_task(self.task.uuid.clone(), property, value.as_ref())?; + + if let Some(v) = value { + self.task.taskmap.insert(property.to_string(), v); + } else { + self.task.taskmap.remove(property); + } + Ok(()) + } + + fn set_timestamp(&mut self, property: &str, value: Option>) -> Fallible<()> { + self.lastmod()?; + self.replica.update_task( + self.task.uuid.clone(), + property, + value.map(|v| format!("{}", v.timestamp())), + ) + } +} + +impl<'r> std::ops::Deref for TaskMut<'r> { + type Target = Task; + + fn deref(&self) -> &Self::Target { + &self.task + } } diff --git a/src/task/taskbuilder.rs b/src/task/taskbuilder.rs deleted file mode 100644 index 6d5bc0d92..000000000 --- a/src/task/taskbuilder.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::task::{Annotation, Priority, Status, Task, Timestamp}; -use chrono::prelude::*; -use std::collections::HashMap; -use std::convert::TryFrom; -use std::str; -use uuid::Uuid; - -#[derive(Default)] -pub struct TaskBuilder { - status: Option, - entry: Option, - description: Option, - start: Option, - end: Option, - due: Option, - until: Option, - wait: Option, - modified: Option, - scheduled: Option, - recur: Option, - mask: Option, - imask: Option, - parent: Option, - project: Option, - priority: Option, - depends: Vec, - tags: Vec, - annotations: Vec, - udas: HashMap, -} - -/// Parse an "integer", allowing for occasional integers with trailing decimal zeroes -fn parse_int(value: &str) -> Result::Err> -where - T: str::FromStr, -{ - // some integers are rendered with following decimal zeroes - if let Some(i) = value.find('.') { - let mut nonzero = false; - for c in value[i + 1..].chars() { - if c != '0' { - nonzero = true; - break; - } - } - if !nonzero { - return value[..i].parse(); - } - } - value.parse() -} - -/// Parse a UNIX timestamp into a UTC DateTime -fn parse_timestamp(value: &str) -> Result::Err> { - Ok(Utc.timestamp(parse_int::(value)?, 0)) -} - -/// Parse depends, as a list of ,-separated UUIDs -fn parse_depends(value: &str) -> Result, uuid::Error> { - value.split(',').map(|s| Uuid::parse_str(s)).collect() -} - -/// Parse tags, as a list of ,-separated strings -fn parse_tags(value: &str) -> Vec { - value.split(',').map(|s| s.to_string()).collect() -} - -impl TaskBuilder { - pub fn new() -> Self { - Default::default() - } - - // TODO: fallible - pub fn set(mut self, name: &str, value: String) -> Self { - const ANNOTATION_PREFIX: &str = "annotation_"; - if name.starts_with(ANNOTATION_PREFIX) { - let entry = parse_timestamp(&name[ANNOTATION_PREFIX.len()..]).unwrap(); - // TODO: sort by entry time - self.annotations.push(Annotation { - entry, - description: value.to_string(), - }); - return self; - } - match name { - "status" => self.status = Some(Status::try_from(value.as_ref()).unwrap()), - "entry" => self.entry = Some(parse_timestamp(&value).unwrap()), - "description" => self.description = Some(value), - "start" => self.start = Some(parse_timestamp(&value).unwrap()), - "end" => self.end = Some(parse_timestamp(&value).unwrap()), - "due" => self.due = Some(parse_timestamp(&value).unwrap()), - "until" => self.until = Some(parse_timestamp(&value).unwrap()), - "wait" => self.wait = Some(parse_timestamp(&value).unwrap()), - "modified" => self.modified = Some(parse_timestamp(&value).unwrap()), - "scheduled" => self.scheduled = Some(parse_timestamp(&value).unwrap()), - "recur" => self.recur = Some(value), - "mask" => self.mask = Some(value), - "imask" => self.imask = Some(parse_int::(&value).unwrap()), - "parent" => self.parent = Some(Uuid::parse_str(&value).unwrap()), - "project" => self.project = Some(value), - "priority" => self.priority = Some(Priority::try_from(value.as_ref()).unwrap()), - "depends" => self.depends = parse_depends(&value).unwrap(), - "tags" => self.tags = parse_tags(&value), - _ => { - self.udas.insert(name.to_string(), value); - } - } - self - } - - pub fn finish(self) -> Task { - Task { - status: self.status.unwrap(), - description: self.description.unwrap(), - entry: self.entry.unwrap(), - start: self.start, - end: self.end, - due: self.due, - until: self.until, - wait: self.wait, - modified: self.modified.unwrap(), - scheduled: self.scheduled, - recur: self.recur, - mask: self.mask, - imask: self.imask, - parent: self.parent, - project: self.project, - priority: self.priority, - depends: self.depends, - tags: self.tags, - annotations: self.annotations, - udas: self.udas, - } - } -} - -#[cfg(test)] -mod test { - use super::{parse_depends, parse_int}; - use uuid::Uuid; - - #[test] - fn test_parse_int() { - assert_eq!(parse_int::("123").unwrap(), 123u8); - assert_eq!(parse_int::("123000000").unwrap(), 123000000u32); - assert_eq!(parse_int::("-123000000").unwrap(), -123000000i32); - } - - #[test] - fn test_parse_int_decimals() { - assert_eq!(parse_int::("123.00").unwrap(), 123u8); - assert_eq!(parse_int::("123.0000").unwrap(), 123u32); - assert_eq!(parse_int::("-123.").unwrap(), -123i32); - } - - #[test] - fn test_parse_depends() { - let u1 = "123e4567-e89b-12d3-a456-426655440000"; - let u2 = "123e4567-e89b-12d3-a456-999999990000"; - assert_eq!( - parse_depends(u1).unwrap(), - vec![Uuid::parse_str(u1).unwrap()] - ); - assert_eq!( - parse_depends(&format!("{},{}", u1, u2)).unwrap(), - vec![Uuid::parse_str(u1).unwrap(), Uuid::parse_str(u2).unwrap()] - ); - } -} From 245969e390676621a4ee73c55c7bb02e254a273c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 12:45:29 -0500 Subject: [PATCH 055/548] make task a simple top-level module --- src/{task => }/task.rs | 0 src/task/mod.rs | 5 ----- 2 files changed, 5 deletions(-) rename src/{task => }/task.rs (100%) delete mode 100644 src/task/mod.rs diff --git a/src/task/task.rs b/src/task.rs similarity index 100% rename from src/task/task.rs rename to src/task.rs diff --git a/src/task/mod.rs b/src/task/mod.rs deleted file mode 100644 index 5e6b67770..000000000 --- a/src/task/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod task; - -pub use self::task::Priority::*; -pub use self::task::Status::*; -pub use self::task::{Annotation, Priority, Status, Task, Timestamp}; From 779a3310034db1761c113fa2d8a9dba7ea05b0ae Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 14:08:42 -0500 Subject: [PATCH 056/548] reorganize into separate crates - taskchampion -- core implementation of a replica - taskchampion-cli -- command-line interface - taskchampion-sync-server -- server implementation (not much yet!) --- Cargo.lock | 316 +++++++++++------- Cargo.toml | 24 +- cli/Cargo.toml | 10 + src/bin/task.rs => cli/src/main.rs | 1 - sync-server/Cargo.toml | 9 + src/server.rs => sync-server/src/lib.rs | 0 taskchampion/Cargo.toml | 18 + README.md => taskchampion/README.md | 0 {docs => taskchampion/docs}/.gitignore | 0 {docs => taskchampion/docs}/README.md | 0 {docs => taskchampion/docs}/book.toml | 0 {docs => taskchampion/docs}/src/SUMMARY.md | 0 {docs => taskchampion/docs}/src/data-model.md | 0 .../docs}/src/installation.md | 0 {docs => taskchampion/docs}/src/plans.md | 0 {docs => taskchampion/docs}/src/storage.md | 0 {docs => taskchampion/docs}/src/sync.md | 0 {docs => taskchampion/docs}/src/taskdb.md | 0 {docs => taskchampion/docs}/src/tasks.md | 0 {docs => taskchampion/docs}/src/usage.md | 0 {src => taskchampion/src}/errors.rs | 0 {src => taskchampion/src}/lib.rs | 3 +- {src => taskchampion/src}/operation.rs | 0 {src => taskchampion/src}/replica.rs | 0 taskchampion/src/server.rs | 20 ++ {src => taskchampion/src}/task.rs | 0 {src => taskchampion/src}/taskdb.rs | 2 +- .../src}/taskstorage/inmemory.rs | 0 {src => taskchampion/src}/taskstorage/kv.rs | 0 {src => taskchampion/src}/taskstorage/mod.rs | 0 .../tests}/data/tdb2-test.data | 0 .../tests}/operation_transform_invariant.rs | 0 taskchampion/tests/shared/mod.rs | 3 + taskchampion/tests/shared/testserver.rs | 81 +++++ {tests => taskchampion/tests}/sync.rs | 9 +- .../tests}/sync_action_sequences.rs | 7 +- 36 files changed, 349 insertions(+), 154 deletions(-) create mode 100644 cli/Cargo.toml rename src/bin/task.rs => cli/src/main.rs (99%) create mode 100644 sync-server/Cargo.toml rename src/server.rs => sync-server/src/lib.rs (100%) create mode 100644 taskchampion/Cargo.toml rename README.md => taskchampion/README.md (100%) rename {docs => taskchampion/docs}/.gitignore (100%) rename {docs => taskchampion/docs}/README.md (100%) rename {docs => taskchampion/docs}/book.toml (100%) rename {docs => taskchampion/docs}/src/SUMMARY.md (100%) rename {docs => taskchampion/docs}/src/data-model.md (100%) rename {docs => taskchampion/docs}/src/installation.md (100%) rename {docs => taskchampion/docs}/src/plans.md (100%) rename {docs => taskchampion/docs}/src/storage.md (100%) rename {docs => taskchampion/docs}/src/sync.md (100%) rename {docs => taskchampion/docs}/src/taskdb.md (100%) rename {docs => taskchampion/docs}/src/tasks.md (100%) rename {docs => taskchampion/docs}/src/usage.md (100%) rename {src => taskchampion/src}/errors.rs (100%) rename {src => taskchampion/src}/lib.rs (91%) rename {src => taskchampion/src}/operation.rs (100%) rename {src => taskchampion/src}/replica.rs (100%) create mode 100644 taskchampion/src/server.rs rename {src => taskchampion/src}/task.rs (100%) rename {src => taskchampion/src}/taskdb.rs (99%) rename {src => taskchampion/src}/taskstorage/inmemory.rs (100%) rename {src => taskchampion/src}/taskstorage/kv.rs (100%) rename {src => taskchampion/src}/taskstorage/mod.rs (100%) rename {tests => taskchampion/tests}/data/tdb2-test.data (100%) rename {tests => taskchampion/tests}/operation_transform_invariant.rs (100%) create mode 100644 taskchampion/tests/shared/mod.rs create mode 100644 taskchampion/tests/shared/testserver.rs rename {tests => taskchampion/tests}/sync.rs (95%) rename {tests => taskchampion/tests}/sync_action_sequences.rs (94%) diff --git a/Cargo.lock b/Cargo.lock index 61a97dabe..e20743939 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,20 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + [[package]] name = "ansi_term" version = "0.11.0" @@ -11,10 +26,11 @@ dependencies = [ [[package]] name = "atty" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ + "hermit-abi", "libc", "winapi", ] @@ -26,41 +42,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] -name = "backtrace" -version = "0.3.40" +name = "autocfg" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598" dependencies = [ - "backtrace-sys", - "cfg-if", + "addr2line", + "cfg-if 1.0.0", "libc", + "miniz_oxide", + "object", "rustc-demangle", ] -[[package]] -name = "backtrace-sys" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "bit-set" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" +checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" [[package]] name = "bitflags" @@ -70,24 +84,15 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "byteorder" -version = "1.3.2" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" - -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -dependencies = [ - "ppv-lite86", -] +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "cc" -version = "1.0.48" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" +checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" [[package]] name = "cfg-if" @@ -96,22 +101,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] -name = "chrono" -version = "0.4.10" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ + "libc", "num-integer", "num-traits", "serde", "time", + "winapi", ] [[package]] name = "clap" -version = "2.33.0" +version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "ansi_term", "atty", @@ -133,9 +146,9 @@ dependencies = [ [[package]] name = "failure" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" dependencies = [ "backtrace", "failure_derive", @@ -143,9 +156,9 @@ dependencies = [ [[package]] name = "failure_derive" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2", "quote", @@ -155,9 +168,9 @@ dependencies = [ [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fuchsia-cprng" @@ -167,20 +180,35 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "getrandom" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" + +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ - "cfg-if", "libc", - "wasi", ] [[package]] name = "itoa" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "kv" @@ -203,9 +231,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.66" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" [[package]] name = "lmdb-rkv" @@ -231,50 +259,66 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.41" +name = "miniz_oxide" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" dependencies = [ - "autocfg", + "adler", + "autocfg 1.0.1", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg 1.0.1", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg", + "autocfg 1.0.1", ] [[package]] -name = "pkg-config" -version = "0.3.17" +name = "object" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "ppv-lite86" -version = "0.2.6" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro2" -version = "1.0.7" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] [[package]] name = "proptest" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f" +checksum = "01c477819b845fe023d33583ebf10c9f62518c8d79a0960ba5c36d6ac8a55a5b" dependencies = [ "bit-set", "bitflags", @@ -292,15 +336,15 @@ dependencies = [ [[package]] name = "quick-error" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ "proc-macro2", ] @@ -324,7 +368,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ - "autocfg", + "autocfg 0.1.7", "libc", "rand_chacha 0.1.1", "rand_core 0.4.2", @@ -339,13 +383,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", - "rand_chacha 0.2.1", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", ] @@ -356,17 +400,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" dependencies = [ - "autocfg", + "autocfg 0.1.7", "rand_core 0.3.1", ] [[package]] name = "rand_chacha" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "c2-chacha", + "ppv-lite86", "rand_core 0.5.1", ] @@ -452,7 +496,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" dependencies = [ - "autocfg", + "autocfg 0.1.7", "rand_core 0.4.2", ] @@ -476,21 +520,21 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "regex-syntax" -version = "0.6.12" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" [[package]] name = "remove_dir_all" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] @@ -507,9 +551,9 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "0.14.0" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a31c0798045f039ace94e0166f76478b3ba83116ec7c9d4bc934c5b13b8df21" +checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" dependencies = [ "byteorder", "rmp", @@ -518,9 +562,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" [[package]] name = "rusty-fork" @@ -536,24 +580,24 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "serde" -version = "1.0.104" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.104" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" dependencies = [ "proc-macro2", "quote", @@ -562,9 +606,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.44" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ "itoa", "ryu", @@ -579,20 +623,24 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.12" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc157159e2a7df58cd67b1cace10b8ed256a404fb0070593f137d8ba6bef4de" +checksum = "443b4178719c5a851e1bde36ce12da21d74a0e60b4d982ec3385a933c812f0f6" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "sync-server" +version = "0.1.0" + [[package]] name = "synstructure" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2", "quote", @@ -605,7 +653,6 @@ name = "taskchampion" version = "0.1.0" dependencies = [ "chrono", - "clap", "failure", "kv", "lmdb-rkv", @@ -616,6 +663,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "taskchampion-cli" +version = "0.1.0" +dependencies = [ + "clap", + "taskchampion", + "uuid", +] + [[package]] name = "tempdir" version = "0.3.7" @@ -632,9 +688,9 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", - "rand 0.7.2", + "rand 0.7.3", "redox_syscall", "remove_dir_all", "winapi", @@ -651,18 +707,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.9" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" +checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.9" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" +checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" dependencies = [ "proc-macro2", "quote", @@ -671,35 +727,35 @@ dependencies = [ [[package]] name = "time" -version = "0.1.42" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "redox_syscall", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] name = "toml" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ "serde", ] [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "uuid" @@ -707,15 +763,15 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ - "rand 0.7.2", + "rand 0.7.3", "serde", ] [[package]] name = "vec_map" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "wait-timeout" @@ -728,15 +784,21 @@ dependencies = [ [[package]] name = "wasi" -version = "0.7.0" +version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", diff --git a/Cargo.toml b/Cargo.toml index 8e5c07492..cd6520de1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,7 @@ -[package] -name = "taskchampion" -version = "0.1.0" -authors = ["Dustin J. Mitchell "] -edition = "2018" +[workspace] -[dependencies] -uuid = { version = "0.8.1", features = ["serde", "v4"] } -serde = "1.0.104" -serde_json = "1.0" -chrono = { version = "0.4.10", features = ["serde"] } -failure = {version = "0.1.5", features = ["derive"] } -clap = "~2.33.0" -kv = {version = "0.10.0", features = ["msgpack-value"]} -lmdb-rkv = {version = "0.12.3"} - -[dev-dependencies] -proptest = "0.9.4" -tempdir = "0.3.7" +members = [ + "taskchampion", + "cli", + "sync-server" +] diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 000000000..948b05eb7 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "taskchampion-cli" +version = "0.1.0" +authors = ["Dustin J. Mitchell "] +edition = "2018" + +[dependencies] +clap = "~2.33.0" +uuid = { version = "0.8.1", features = ["serde", "v4"] } +taskchampion = { path = "../taskchampion" } diff --git a/src/bin/task.rs b/cli/src/main.rs similarity index 99% rename from src/bin/task.rs rename to cli/src/main.rs index 0362eb83f..058b1c940 100644 --- a/src/bin/task.rs +++ b/cli/src/main.rs @@ -1,4 +1,3 @@ -extern crate clap; use clap::{App, Arg, SubCommand}; use std::path::Path; use taskchampion::{taskstorage, Replica, Status, DB}; diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml new file mode 100644 index 000000000..e2df075ed --- /dev/null +++ b/sync-server/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "sync-server" +version = "0.1.0" +authors = ["Dustin J. Mitchell "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/server.rs b/sync-server/src/lib.rs similarity index 100% rename from src/server.rs rename to sync-server/src/lib.rs diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml new file mode 100644 index 000000000..1f6681454 --- /dev/null +++ b/taskchampion/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "taskchampion" +version = "0.1.0" +authors = ["Dustin J. Mitchell "] +edition = "2018" + +[dependencies] +uuid = { version = "0.8.1", features = ["serde", "v4"] } +serde = "1.0.104" +serde_json = "1.0" +chrono = { version = "0.4.10", features = ["serde"] } +failure = {version = "0.1.5", features = ["derive"] } +kv = {version = "0.10.0", features = ["msgpack-value"]} +lmdb-rkv = {version = "0.12.3"} + +[dev-dependencies] +proptest = "0.9.4" +tempdir = "0.3.7" diff --git a/README.md b/taskchampion/README.md similarity index 100% rename from README.md rename to taskchampion/README.md diff --git a/docs/.gitignore b/taskchampion/docs/.gitignore similarity index 100% rename from docs/.gitignore rename to taskchampion/docs/.gitignore diff --git a/docs/README.md b/taskchampion/docs/README.md similarity index 100% rename from docs/README.md rename to taskchampion/docs/README.md diff --git a/docs/book.toml b/taskchampion/docs/book.toml similarity index 100% rename from docs/book.toml rename to taskchampion/docs/book.toml diff --git a/docs/src/SUMMARY.md b/taskchampion/docs/src/SUMMARY.md similarity index 100% rename from docs/src/SUMMARY.md rename to taskchampion/docs/src/SUMMARY.md diff --git a/docs/src/data-model.md b/taskchampion/docs/src/data-model.md similarity index 100% rename from docs/src/data-model.md rename to taskchampion/docs/src/data-model.md diff --git a/docs/src/installation.md b/taskchampion/docs/src/installation.md similarity index 100% rename from docs/src/installation.md rename to taskchampion/docs/src/installation.md diff --git a/docs/src/plans.md b/taskchampion/docs/src/plans.md similarity index 100% rename from docs/src/plans.md rename to taskchampion/docs/src/plans.md diff --git a/docs/src/storage.md b/taskchampion/docs/src/storage.md similarity index 100% rename from docs/src/storage.md rename to taskchampion/docs/src/storage.md diff --git a/docs/src/sync.md b/taskchampion/docs/src/sync.md similarity index 100% rename from docs/src/sync.md rename to taskchampion/docs/src/sync.md diff --git a/docs/src/taskdb.md b/taskchampion/docs/src/taskdb.md similarity index 100% rename from docs/src/taskdb.md rename to taskchampion/docs/src/taskdb.md diff --git a/docs/src/tasks.md b/taskchampion/docs/src/tasks.md similarity index 100% rename from docs/src/tasks.md rename to taskchampion/docs/src/tasks.md diff --git a/docs/src/usage.md b/taskchampion/docs/src/usage.md similarity index 100% rename from docs/src/usage.md rename to taskchampion/docs/src/usage.md diff --git a/src/errors.rs b/taskchampion/src/errors.rs similarity index 100% rename from src/errors.rs rename to taskchampion/src/errors.rs diff --git a/src/lib.rs b/taskchampion/src/lib.rs similarity index 91% rename from src/lib.rs rename to taskchampion/src/lib.rs index 97b25b9a8..c9a242359 100644 --- a/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -8,14 +8,13 @@ extern crate failure; mod errors; mod operation; mod replica; -mod server; +pub mod server; mod task; mod taskdb; pub mod taskstorage; pub use operation::Operation; pub use replica::Replica; -pub use server::Server; pub use task::Priority; pub use task::Status; pub use task::Task; diff --git a/src/operation.rs b/taskchampion/src/operation.rs similarity index 100% rename from src/operation.rs rename to taskchampion/src/operation.rs diff --git a/src/replica.rs b/taskchampion/src/replica.rs similarity index 100% rename from src/replica.rs rename to taskchampion/src/replica.rs diff --git a/taskchampion/src/server.rs b/taskchampion/src/server.rs new file mode 100644 index 000000000..233838fa6 --- /dev/null +++ b/taskchampion/src/server.rs @@ -0,0 +1,20 @@ +pub type Blob = Vec; + +pub enum VersionAdd { + // OK, version added + Ok, + // Rejected, must be based on the the given version + ExpectedVersion(u64), +} + +/// A value implementing this trait can act as a server against which a replica can sync. +pub trait Server { + /// Get a vector of all versions after `since_version` + fn get_versions(&self, username: &str, since_version: u64) -> Vec; + + /// Add a new version. If the given version number is incorrect, this responds with the + /// appropriate version and expects the caller to try again. + fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> VersionAdd; + + fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob); +} diff --git a/src/task.rs b/taskchampion/src/task.rs similarity index 100% rename from src/task.rs rename to taskchampion/src/task.rs diff --git a/src/taskdb.rs b/taskchampion/src/taskdb.rs similarity index 99% rename from src/taskdb.rs rename to taskchampion/src/taskdb.rs index 33306f359..5850b1571 100644 --- a/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -170,7 +170,7 @@ impl DB { } /// Sync to the given server, pulling remote changes and pushing local changes. - pub fn sync(&mut self, username: &str, server: &mut Server) -> Fallible<()> { + pub fn sync(&mut self, username: &str, server: &mut dyn Server) -> Fallible<()> { let mut txn = self.storage.txn()?; // retry synchronizing until the server accepts our version (this allows for races between diff --git a/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs similarity index 100% rename from src/taskstorage/inmemory.rs rename to taskchampion/src/taskstorage/inmemory.rs diff --git a/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs similarity index 100% rename from src/taskstorage/kv.rs rename to taskchampion/src/taskstorage/kv.rs diff --git a/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs similarity index 100% rename from src/taskstorage/mod.rs rename to taskchampion/src/taskstorage/mod.rs diff --git a/tests/data/tdb2-test.data b/taskchampion/tests/data/tdb2-test.data similarity index 100% rename from tests/data/tdb2-test.data rename to taskchampion/tests/data/tdb2-test.data diff --git a/tests/operation_transform_invariant.rs b/taskchampion/tests/operation_transform_invariant.rs similarity index 100% rename from tests/operation_transform_invariant.rs rename to taskchampion/tests/operation_transform_invariant.rs diff --git a/taskchampion/tests/shared/mod.rs b/taskchampion/tests/shared/mod.rs new file mode 100644 index 000000000..8a5a5ee0c --- /dev/null +++ b/taskchampion/tests/shared/mod.rs @@ -0,0 +1,3 @@ +mod testserver; + +pub use testserver::TestServer; diff --git a/taskchampion/tests/shared/testserver.rs b/taskchampion/tests/shared/testserver.rs new file mode 100644 index 000000000..2cb1cde2e --- /dev/null +++ b/taskchampion/tests/shared/testserver.rs @@ -0,0 +1,81 @@ +use std::collections::HashMap; +use taskchampion::server::{Blob, Server, VersionAdd}; + +pub struct TestServer { + users: HashMap, +} + +struct User { + // versions, indexed at v-1 + versions: Vec, + snapshots: HashMap, +} + +impl TestServer { + pub fn new() -> TestServer { + TestServer { + users: HashMap::new(), + } + } + + fn get_user_mut(&mut self, username: &str) -> &mut User { + self.users + .entry(username.to_string()) + .or_insert_with(User::new) + } +} + +impl Server for TestServer { + /// Get a vector of all versions after `since_version` + fn get_versions(&self, username: &str, since_version: u64) -> Vec { + self.users + .get(username) + .map(|user| user.get_versions(since_version)) + .unwrap_or_else(|| vec![]) + } + + /// Add a new version. If the given version number is incorrect, this responds with the + /// appropriate version and expects the caller to try again. + fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> VersionAdd { + self.get_user_mut(username).add_version(version, blob) + } + + fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) { + self.get_user_mut(username).add_snapshot(version, blob); + } +} + +impl User { + fn new() -> User { + User { + versions: vec![], + snapshots: HashMap::new(), + } + } + + fn get_versions(&self, since_version: u64) -> Vec { + let last_version = self.versions.len(); + if last_version == since_version as usize { + return vec![]; + } + self.versions[since_version as usize..last_version] + .iter() + .map(|r| r.clone()) + .collect::>() + } + + fn add_version(&mut self, version: u64, blob: Blob) -> VersionAdd { + // of by one here: client wants to send version 1 first + let expected_version = self.versions.len() as u64 + 1; + if version != expected_version { + return VersionAdd::ExpectedVersion(expected_version); + } + self.versions.push(blob); + + VersionAdd::Ok + } + + fn add_snapshot(&mut self, version: u64, blob: Blob) { + self.snapshots.insert(version, blob); + } +} diff --git a/tests/sync.rs b/taskchampion/tests/sync.rs similarity index 95% rename from tests/sync.rs rename to taskchampion/tests/sync.rs index fc594bbd3..197708dee 100644 --- a/tests/sync.rs +++ b/taskchampion/tests/sync.rs @@ -1,14 +1,17 @@ use chrono::Utc; -use taskchampion::{taskstorage, Operation, Server, DB}; +use taskchampion::{taskstorage, Operation, DB}; use uuid::Uuid; +mod shared; +use shared::TestServer; + fn newdb() -> DB { DB::new(Box::new(taskstorage::InMemoryStorage::new())) } #[test] fn test_sync() { - let mut server = Server::new(); + let mut server = TestServer::new(); let mut db1 = newdb(); db1.sync("me", &mut server).unwrap(); @@ -68,7 +71,7 @@ fn test_sync() { #[test] fn test_sync_create_delete() { - let mut server = Server::new(); + let mut server = TestServer::new(); let mut db1 = newdb(); db1.sync("me", &mut server).unwrap(); diff --git a/tests/sync_action_sequences.rs b/taskchampion/tests/sync_action_sequences.rs similarity index 94% rename from tests/sync_action_sequences.rs rename to taskchampion/tests/sync_action_sequences.rs index 741988b11..2c94d6dd8 100644 --- a/tests/sync_action_sequences.rs +++ b/taskchampion/tests/sync_action_sequences.rs @@ -1,8 +1,11 @@ use chrono::Utc; use proptest::prelude::*; -use taskchampion::{taskstorage, Operation, Server, DB}; +use taskchampion::{taskstorage, Operation, DB}; use uuid::Uuid; +mod shared; +use shared::TestServer; + fn newdb() -> DB { DB::new(Box::new(taskstorage::InMemoryStorage::new())) } @@ -46,7 +49,7 @@ proptest! { // and delete operations that results in a task existing in one DB but not existing in // another. So, the generated sequences focus on a single task UUID. fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { - let mut server = Server::new(); + let mut server = TestServer::new(); let mut dbs = [newdb(), newdb(), newdb()]; for (action, db) in action_sequence { From ba55d298ceebfc6e661705e8f59a088cc9f7c285 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 14:29:51 -0500 Subject: [PATCH 057/548] stop ignoring dead code and unused variables --- taskchampion/src/errors.rs | 3 --- taskchampion/src/lib.rs | 4 ---- taskchampion/src/taskstorage/kv.rs | 4 ---- 3 files changed, 11 deletions(-) diff --git a/taskchampion/src/errors.rs b/taskchampion/src/errors.rs index 8a180936f..08884376f 100644 --- a/taskchampion/src/errors.rs +++ b/taskchampion/src/errors.rs @@ -4,7 +4,4 @@ use failure::Fail; pub enum Error { #[fail(display = "Task Database Error: {}", _0)] DBError(String), - - #[fail(display = "Replica Error: {}", _0)] - ReplicaError(String), } diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index c9a242359..9042dc2de 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -1,7 +1,3 @@ -// TODO: remove this eventually when there's an API -#![allow(dead_code)] -#![allow(unused_variables)] - #[macro_use] extern crate failure; diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index 08f2ade01..da275c90e 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -177,7 +177,6 @@ impl<'t> TaskStorageTxn for Txn<'t> { fn all_tasks(&mut self) -> Fallible> { let bucket = self.tasks_bucket(); let kvtxn = self.kvtxn(); - let curs = kvtxn.read_cursor(bucket)?; let all_tasks: Result, Error> = kvtxn .read_cursor(bucket)? .iter() @@ -189,7 +188,6 @@ impl<'t> TaskStorageTxn for Txn<'t> { fn all_task_uuids(&mut self) -> Fallible> { let bucket = self.tasks_bucket(); let kvtxn = self.kvtxn(); - let curs = kvtxn.read_cursor(bucket)?; Ok(kvtxn .read_cursor(bucket)? .iter() @@ -224,7 +222,6 @@ impl<'t> TaskStorageTxn for Txn<'t> { fn operations(&mut self) -> Fallible> { let bucket = self.operations_bucket(); let kvtxn = self.kvtxn(); - let curs = kvtxn.read_cursor(bucket)?; let all_ops: Result, Error> = kvtxn .read_cursor(bucket)? .iter() @@ -299,7 +296,6 @@ impl<'t> TaskStorageTxn for Txn<'t> { res.push(None) } - let curs = kvtxn.read_cursor(working_set_bucket)?; for (i, u) in kvtxn.read_cursor(working_set_bucket)?.iter() { let i: u64 = i.into(); res[i as usize] = Some(u.inner()?.to_serde()); From 8f4924f90316f9a28c60f9167d9407c94fb60fbd Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 15:02:37 -0500 Subject: [PATCH 058/548] remove unnecessary 'extern crate' --- taskchampion/src/lib.rs | 3 --- taskchampion/src/task.rs | 2 +- taskchampion/src/taskstorage/inmemory.rs | 2 +- taskchampion/src/taskstorage/kv.rs | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 9042dc2de..e3e4bc00d 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -1,6 +1,3 @@ -#[macro_use] -extern crate failure; - mod errors; mod operation; mod replica; diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index a8b95989b..75e852fda 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -1,7 +1,7 @@ use crate::replica::Replica; use crate::taskstorage::TaskMap; use chrono::prelude::*; -use failure::Fallible; +use failure::{format_err, Fallible}; use std::convert::TryFrom; use uuid::Uuid; diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs index 6b272874c..559a2a06f 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/taskstorage/inmemory.rs @@ -1,6 +1,6 @@ use crate::operation::Operation; use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn}; -use failure::Fallible; +use failure::{format_err, Fallible}; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index da275c90e..14198428c 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -1,6 +1,6 @@ use crate::operation::Operation; use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn}; -use failure::Fallible; +use failure::{format_err, Fallible}; use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; use std::convert::TryInto; From 8e2b4c3f6ccdc85bcf0834d53e2a6a5c62d30d2e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 15:59:37 -0500 Subject: [PATCH 059/548] Reorganize taskchampion crate for docs and tests The public API of the taskchampion crate now contains the expected parts and no more, and has some better documentation. This moves the crate's external `tests/` into internal tests, as the TaskDB is no longer exposed as part of the crate API. --- cli/src/main.rs | 11 +- taskchampion/src/lib.rs | 32 ++- taskchampion/src/replica.rs | 42 ++-- taskchampion/src/task.rs | 65 ++++-- taskchampion/src/taskdb.rs | 190 +++++++++++++++++- taskchampion/src/taskstorage/inmemory.rs | 5 +- taskchampion/src/taskstorage/kv.rs | 3 +- taskchampion/src/taskstorage/mod.rs | 25 ++- .../src/{ => taskstorage}/operation.rs | 79 ++++++++ taskchampion/src/testing/mod.rs | 1 + .../shared => src/testing}/testserver.rs | 4 +- taskchampion/tests/data/tdb2-test.data | 2 - .../tests/operation_transform_invariant.rs | 85 -------- taskchampion/tests/shared/mod.rs | 3 - taskchampion/tests/sync.rs | 123 ------------ taskchampion/tests/sync_action_sequences.rs | 72 ------- 16 files changed, 395 insertions(+), 347 deletions(-) rename taskchampion/src/{ => taskstorage}/operation.rs (73%) create mode 100644 taskchampion/src/testing/mod.rs rename taskchampion/{tests/shared => src/testing}/testserver.rs (96%) delete mode 100644 taskchampion/tests/data/tdb2-test.data delete mode 100644 taskchampion/tests/operation_transform_invariant.rs delete mode 100644 taskchampion/tests/shared/mod.rs delete mode 100644 taskchampion/tests/sync.rs delete mode 100644 taskchampion/tests/sync_action_sequences.rs diff --git a/cli/src/main.rs b/cli/src/main.rs index 058b1c940..1ae6b148e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,6 +1,6 @@ use clap::{App, Arg, SubCommand}; use std::path::Path; -use taskchampion::{taskstorage, Replica, Status, DB}; +use taskchampion::{taskstorage, Replica, Status}; use uuid::Uuid; fn main() { @@ -20,12 +20,9 @@ fn main() { .subcommand(SubCommand::with_name("gc").about("run garbage collection")) .get_matches(); - let mut replica = Replica::new( - DB::new(Box::new( - taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(), - )) - .into(), - ); + let mut replica = Replica::new(Box::new( + taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(), + )); match matches.subcommand() { ("add", Some(matches)) => { diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index e3e4bc00d..d5d1d13be 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -1,14 +1,38 @@ +/*! + +This crate implements the core of TaskChampion, the [replica](crate::Replica). + +A TaskChampion replica is a local copy of a user's task data. As the name suggests, several +replicas of the same data can exist (such as on a user's laptop and on their phone) and can +synchronize with one another. + +# Task Storage + +The [`taskstorage`](crate::taskstorage) module supports pluggable storage for a replica's data. +An implementation is provided, but users of this crate can provide their own implementation as well. + +# Server + +Replica synchronization takes place against a server. +The [`server`](crate::server) module defines the interface a server must meet. + +# See Also + +See the [TaskChampion Book](https://github.com/djmitche/taskchampion/blob/main/docs/src/SUMMARY.md) +for more information about the design and usage of the tool. + + */ mod errors; -mod operation; mod replica; pub mod server; mod task; mod taskdb; pub mod taskstorage; -pub use operation::Operation; pub use replica::Replica; pub use task::Priority; pub use task::Status; -pub use task::Task; -pub use taskdb::DB; +pub use task::{Task, TaskMut}; + +#[cfg(test)] +pub(crate) mod testing; diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 507ea9291..874e74eca 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -1,21 +1,29 @@ use crate::errors::Error; -use crate::operation::Operation; +use crate::server::Server; use crate::task::{Status, Task}; use crate::taskdb::DB; -use crate::taskstorage::TaskMap; +use crate::taskstorage::{Operation, TaskMap, TaskStorage}; use chrono::Utc; use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; -/// A replica represents an instance of a user's task data. +/// A replica represents an instance of a user's task data, providing an easy interface +/// for querying and modifying that data. pub struct Replica { - taskdb: Box, + taskdb: DB, } impl Replica { - pub fn new(taskdb: Box) -> Replica { - return Replica { taskdb }; + pub fn new(storage: Box) -> Replica { + return Replica { + taskdb: DB::new(storage), + }; + } + + #[cfg(test)] + pub fn new_inmemory() -> Replica { + Replica::new(Box::new(crate::taskstorage::InMemoryStorage::new())) } /// Update an existing task. If the value is Some, the property is added or updated. If the @@ -45,7 +53,7 @@ impl Replica { } /// Get all tasks represented as a map keyed by UUID - pub fn all_tasks<'a>(&'a mut self) -> Fallible> { + pub fn all_tasks(&mut self) -> Fallible> { let mut res = HashMap::new(); for (uuid, tm) in self.taskdb.all_tasks()?.drain(..) { res.insert(uuid.clone(), Task::new(uuid.clone(), tm)); @@ -54,7 +62,7 @@ impl Replica { } /// Get the UUIDs of all tasks - pub fn all_task_uuids<'a>(&'a mut self) -> Fallible> { + pub fn all_task_uuids(&mut self) -> Fallible> { self.taskdb.all_task_uuids() } @@ -124,6 +132,11 @@ impl Replica { self.taskdb.apply(Operation::Delete { uuid }) } + /// Synchronize this replica against the given server. + pub fn sync(&mut self, username: &str, server: &mut dyn Server) -> Fallible<()> { + self.taskdb.sync(username, server) + } + /// Perform "garbage collection" on this replica. In particular, this renumbers the working /// set to contain only pending tasks. pub fn gc(&mut self) -> Fallible<()> { @@ -138,12 +151,11 @@ impl Replica { mod tests { use super::*; use crate::task::Status; - use crate::taskdb::DB; use uuid::Uuid; #[test] fn new_task() { - let mut rep = Replica::new(DB::new_inmemory().into()); + let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); let t = rep @@ -156,7 +168,7 @@ mod tests { #[test] fn modify_task() { - let mut rep = Replica::new(DB::new_inmemory().into()); + let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); let t = rep @@ -183,7 +195,7 @@ mod tests { #[test] fn delete_task() { - let mut rep = Replica::new(DB::new_inmemory().into()); + let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); rep.new_task(uuid.clone(), Status::Pending, "a task".into()) @@ -195,7 +207,7 @@ mod tests { #[test] fn get_and_modify() { - let mut rep = Replica::new(DB::new_inmemory().into()); + let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); rep.new_task(uuid.clone(), Status::Pending, "another task".into()) @@ -215,7 +227,7 @@ mod tests { #[test] fn new_pending_adds_to_working_set() { - let mut rep = Replica::new(DB::new_inmemory().into()); + let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); rep.new_task(uuid.clone(), Status::Pending, "to-be-pending".into()) @@ -233,7 +245,7 @@ mod tests { #[test] fn get_does_not_exist() { - let mut rep = Replica::new(DB::new_inmemory().into()); + let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); assert_eq!(rep.get_task(&uuid).unwrap(), None); } diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 75e852fda..c0ccc1480 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -1,34 +1,37 @@ use crate::replica::Replica; use crate::taskstorage::TaskMap; use chrono::prelude::*; -use failure::{format_err, Fallible}; -use std::convert::TryFrom; +use failure::Fallible; use uuid::Uuid; pub type Timestamp = DateTime; +/// The priority of a task #[derive(Debug, PartialEq)] pub enum Priority { + /// Low L, + /// Medium M, + /// High H, } -impl TryFrom<&str> for Priority { - type Error = failure::Error; - - fn try_from(s: &str) -> Result { +#[allow(dead_code)] +impl Priority { + /// Get a Priority from the 1-character value in a TaskMap, + /// defaulting to M + pub(crate) fn from_taskmap(s: &str) -> Priority { match s { - "L" => Ok(Priority::L), - "M" => Ok(Priority::M), - "H" => Ok(Priority::H), - _ => Err(format_err!("invalid status {}", s)), + "L" => Priority::L, + "M" => Priority::M, + "H" => Priority::H, + _ => Priority::M, } } -} -impl AsRef for Priority { - fn as_ref(&self) -> &str { + /// Get the 1-character value for this priority to use in the TaskMap. + pub(crate) fn to_taskmap(&self) -> &str { match self { Priority::L => "L", Priority::M => "M", @@ -36,6 +39,8 @@ impl AsRef for Priority { } } } + +/// The status of a task. The default status in "Pending". #[derive(Debug, PartialEq)] pub enum Status { Pending, @@ -86,8 +91,8 @@ pub struct Task { taskmap: TaskMap, } -/// A mutable task, with setter methods. Calling a setter will update the Replica, as well as the -/// included Task. +/// A mutable task, with setter methods. Most methods are simple setters and not further +/// described. Calling a setter will update the Replica, as well as the included Task. pub struct TaskMut<'r> { task: Task, replica: &'r mut Replica, @@ -145,7 +150,8 @@ impl Task { } impl<'r> TaskMut<'r> { - /// Get the immutable task + /// Get the immutable version of this object. Note that TaskMut [`std::ops::Deref`]s to + /// [`crate::task::Task`], so all of that struct's getter methods can be used on TaskMut. pub fn into_immut(self) -> Task { self.task } @@ -160,12 +166,10 @@ impl<'r> TaskMut<'r> { self.set_string("status", Some(String::from(status.to_taskmap()))) } - /// Set the task's description pub fn set_description(&mut self, description: String) -> Fallible<()> { self.set_string("description", Some(description)) } - /// Set the task's description pub fn set_modified(&mut self, modified: DateTime) -> Fallible<()> { self.set_timestamp("modified", Some(modified)) } @@ -213,3 +217,28 @@ impl<'r> std::ops::Deref for TaskMut<'r> { &self.task } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_priority() { + assert_eq!(Priority::L.to_taskmap(), "L"); + assert_eq!(Priority::M.to_taskmap(), "M"); + assert_eq!(Priority::H.to_taskmap(), "H"); + assert_eq!(Priority::from_taskmap("L"), Priority::L); + assert_eq!(Priority::from_taskmap("M"), Priority::M); + assert_eq!(Priority::from_taskmap("H"), Priority::H); + } + + #[test] + fn test_status() { + assert_eq!(Status::Pending.to_taskmap(), "P"); + assert_eq!(Status::Completed.to_taskmap(), "C"); + assert_eq!(Status::Deleted.to_taskmap(), "D"); + assert_eq!(Status::from_taskmap("P"), Status::Pending); + assert_eq!(Status::from_taskmap("C"), Status::Completed); + assert_eq!(Status::from_taskmap("D"), Status::Deleted); + } +} diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 5850b1571..7fe9362fc 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -1,7 +1,6 @@ use crate::errors::Error; -use crate::operation::Operation; use crate::server::{Server, VersionAdd}; -use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn}; +use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; use failure::Fallible; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -268,7 +267,8 @@ impl DB { // functions for supporting tests - pub fn sorted_tasks(&mut self) -> Vec<(Uuid, Vec<(String, String)>)> { + #[cfg(test)] + pub(crate) fn sorted_tasks(&mut self) -> Vec<(Uuid, Vec<(String, String)>)> { let mut res: Vec<(Uuid, Vec<(String, String)>)> = self .all_tasks() .unwrap() @@ -286,7 +286,8 @@ impl DB { res } - pub fn operations(&mut self) -> Vec { + #[cfg(test)] + pub(crate) fn operations(&mut self) -> Vec { let mut txn = self.storage.txn().unwrap(); txn.operations() .unwrap() @@ -299,7 +300,10 @@ impl DB { #[cfg(test)] mod tests { use super::*; + use crate::taskstorage::InMemoryStorage; + use crate::testing::testserver::TestServer; use chrono::Utc; + use proptest::prelude::*; use std::collections::HashMap; use uuid::Uuid; @@ -508,4 +512,182 @@ mod tests { Ok(()) } + + fn newdb() -> DB { + DB::new(Box::new(InMemoryStorage::new())) + } + + #[test] + fn test_sync() { + let mut server = TestServer::new(); + + let mut db1 = newdb(); + db1.sync("me", &mut server).unwrap(); + + let mut db2 = newdb(); + db2.sync("me", &mut server).unwrap(); + + // make some changes in parallel to db1 and db2.. + let uuid1 = Uuid::new_v4(); + db1.apply(Operation::Create { uuid: uuid1 }).unwrap(); + db1.apply(Operation::Update { + uuid: uuid1, + property: "title".into(), + value: Some("my first task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + let uuid2 = Uuid::new_v4(); + db2.apply(Operation::Create { uuid: uuid2 }).unwrap(); + db2.apply(Operation::Update { + uuid: uuid2, + property: "title".into(), + value: Some("my second task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and synchronize those around + db1.sync("me", &mut server).unwrap(); + db2.sync("me", &mut server).unwrap(); + db1.sync("me", &mut server).unwrap(); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + + // now make updates to the same task on both sides + db1.apply(Operation::Update { + uuid: uuid2, + property: "priority".into(), + value: Some("H".into()), + timestamp: Utc::now(), + }) + .unwrap(); + db2.apply(Operation::Update { + uuid: uuid2, + property: "project".into(), + value: Some("personal".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and synchronize those around + db1.sync("me", &mut server).unwrap(); + db2.sync("me", &mut server).unwrap(); + db1.sync("me", &mut server).unwrap(); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + } + + #[test] + fn test_sync_create_delete() { + let mut server = TestServer::new(); + + let mut db1 = newdb(); + db1.sync("me", &mut server).unwrap(); + + let mut db2 = newdb(); + db2.sync("me", &mut server).unwrap(); + + // create and update a task.. + let uuid = Uuid::new_v4(); + db1.apply(Operation::Create { uuid }).unwrap(); + db1.apply(Operation::Update { + uuid: uuid, + property: "title".into(), + value: Some("my first task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and synchronize those around + db1.sync("me", &mut server).unwrap(); + db2.sync("me", &mut server).unwrap(); + db1.sync("me", &mut server).unwrap(); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + + // delete and re-create the task on db1 + db1.apply(Operation::Delete { uuid }).unwrap(); + db1.apply(Operation::Create { uuid }).unwrap(); + db1.apply(Operation::Update { + uuid: uuid, + property: "title".into(), + value: Some("my second task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and on db2, update a property of the task + db2.apply(Operation::Update { + uuid: uuid, + property: "project".into(), + value: Some("personal".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + db1.sync("me", &mut server).unwrap(); + db2.sync("me", &mut server).unwrap(); + db1.sync("me", &mut server).unwrap(); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + } + + #[derive(Debug)] + enum Action { + Op(Operation), + Sync, + } + + fn action_sequence_strategy() -> impl Strategy> { + // Create, Update, Delete, or Sync on client 1, 2, .., followed by a round of syncs + "([CUDS][123])*S1S2S3S1S2".prop_map(|seq| { + let uuid = Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap(); + seq.as_bytes() + .chunks(2) + .map(|action_on| { + let action = match action_on[0] { + b'C' => Action::Op(Operation::Create { uuid }), + b'U' => Action::Op(Operation::Update { + uuid, + property: "title".into(), + value: Some("foo".into()), + timestamp: Utc::now(), + }), + b'D' => Action::Op(Operation::Delete { uuid }), + b'S' => Action::Sync, + _ => unreachable!(), + }; + let acton = action_on[1] - b'1'; + (action, acton) + }) + .collect::>() + }) + } + + proptest! { + #[test] + // check that various sequences of operations on mulitple db's do not get the db's into an + // incompatible state. The main concern here is that there might be a sequence of create + // and delete operations that results in a task existing in one DB but not existing in + // another. So, the generated sequences focus on a single task UUID. + fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { + let mut server = TestServer::new(); + let mut dbs = [newdb(), newdb(), newdb()]; + + for (action, db) in action_sequence { + println!("{:?} on db {}", action, db); + + let db = &mut dbs[db as usize]; + match action { + Action::Op(op) => { + if let Err(e) = db.apply(op) { + println!(" {:?} (ignored)", e); + } + }, + Action::Sync => db.sync("me", &mut server).unwrap(), + } + } + + assert_eq!(dbs[0].sorted_tasks(), dbs[0].sorted_tasks()); + assert_eq!(dbs[1].sorted_tasks(), dbs[2].sorted_tasks()); + } + } } diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs index 559a2a06f..cdc376c5e 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/taskstorage/inmemory.rs @@ -1,5 +1,4 @@ -use crate::operation::Operation; -use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn}; +use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; use failure::{format_err, Fallible}; use std::collections::hash_map::Entry; use std::collections::HashMap; @@ -139,6 +138,8 @@ impl<'t> TaskStorageTxn for Txn<'t> { } } +/// InMemoryStorage is a simple in-memory task storage implementation. It is not useful for +/// production data, but is useful for testing purposes. #[derive(PartialEq, Debug, Clone)] pub struct InMemoryStorage { data: Data, diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index 14198428c..8cbe033b7 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -1,5 +1,4 @@ -use crate::operation::Operation; -use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn}; +use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; use failure::{format_err, Fallible}; use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index b5681f4de..bc0fb30a3 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -1,14 +1,16 @@ -use crate::Operation; use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; mod inmemory; mod kv; +mod operation; pub use self::kv::KVStorage; pub use inmemory::InMemoryStorage; +pub use operation::Operation; + /// An in-memory representation of a task as a simple hashmap pub type TaskMap = HashMap; @@ -22,10 +24,18 @@ fn taskmap_with(mut properties: Vec<(String, String)>) -> TaskMap { } /// A TaskStorage transaction, in which storage operations are performed. -/// Serializable consistency is maintained, and implementations do not optimize -/// for concurrent access so some may simply apply a mutex to limit access to -/// one transaction at a time. Transactions are aborted if they are dropped. -/// It's safe to drop transactions that did not modify any data. +/// +/// # Concurrency +/// +/// Serializable consistency must be maintained. Concurrent access is unusual +/// and some implementations may simply apply a mutex to limit access to +/// one transaction at a time. +/// +/// # Commiting and Aborting +/// +/// A transaction is not visible to other readers until it is committed with +/// [`crate::taskstorage::TaskStorageTxn::commit`]. Transactions are aborted if they are dropped. +/// It is safe and performant to drop transactions that did not modify any data without committing. pub trait TaskStorageTxn { /// Get an (immutable) task, if it is in the storage fn get_task(&mut self, uuid: &Uuid) -> Fallible>; @@ -83,9 +93,8 @@ pub trait TaskStorageTxn { fn commit(&mut self) -> Fallible<()>; } -/// A trait for objects able to act as backing storage for a DB. This API is optimized to be -/// easy to implement, with all of the semantic meaning of the data located in the DB -/// implementation, which is the sole consumer of this trait. +/// A trait for objects able to act as task storage. Most of the interesting behavior is in the +/// [`crate::taskstorage::TaskStorageTxn`] trait. pub trait TaskStorage { /// Begin a transaction fn txn<'a>(&'a mut self) -> Fallible>; diff --git a/taskchampion/src/operation.rs b/taskchampion/src/taskstorage/operation.rs similarity index 73% rename from taskchampion/src/operation.rs rename to taskchampion/src/taskstorage/operation.rs index cba88c808..f8927a133 100644 --- a/taskchampion/src/operation.rs +++ b/taskchampion/src/taskstorage/operation.rs @@ -129,7 +129,9 @@ impl Operation { mod test { use super::*; use crate::taskdb::DB; + use crate::taskstorage::InMemoryStorage; use chrono::{Duration, Utc}; + use proptest::prelude::*; // note that `tests/operation_transform_invariant.rs` tests the transform function quite // thoroughly, so this testing is light. @@ -273,4 +275,81 @@ mod test { None, ); } + + fn uuid_strategy() -> impl Strategy { + prop_oneof![ + Just(Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap()), + Just(Uuid::parse_str("56e0be07-c61f-494c-a54c-bdcfdd52d2a7").unwrap()), + Just(Uuid::parse_str("4b7ed904-f7b0-4293-8a10-ad452422c7b3").unwrap()), + Just(Uuid::parse_str("9bdd0546-07c8-4e1f-a9bc-9d6299f4773b").unwrap()), + ] + } + + fn operation_strategy() -> impl Strategy { + prop_oneof![ + uuid_strategy().prop_map(|uuid| Operation::Create { uuid }), + uuid_strategy().prop_map(|uuid| Operation::Delete { uuid }), + (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { + Operation::Update { + uuid, + property, + value: Some("true".into()), + timestamp: Utc::now(), + } + }), + ] + } + + proptest! { + #![proptest_config(ProptestConfig { + cases: 1024, .. ProptestConfig::default() + })] + #[test] + // check that the two operation sequences have the same effect, enforcing the invariant of + // the transform function. + fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { + let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); + + let mut db1 = DB::new(Box::new(InMemoryStorage::new())); + let mut db2 = DB::new(Box::new(InMemoryStorage::new())); + + // Ensure that any expected tasks already exist + if let Operation::Update{ ref uuid, .. } = o1 { + let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); + } + + if let Operation::Update{ ref uuid, .. } = o2 { + let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); + } + + if let Operation::Delete{ ref uuid } = o1 { + let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); + } + + if let Operation::Delete{ ref uuid } = o2 { + let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); + let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); + } + + // if applying the initial operations fail, that indicates the operation was invalid + // in the base state, so consider the case successful. + if let Err(_) = db1.apply(o1) { + return Ok(()); + } + if let Err(_) = db2.apply(o2) { + return Ok(()); + } + + if let Some(o) = o2p { + db1.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?; + } + if let Some(o) = o1p { + db2.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?; + } + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + } + } } diff --git a/taskchampion/src/testing/mod.rs b/taskchampion/src/testing/mod.rs new file mode 100644 index 000000000..766fbbda5 --- /dev/null +++ b/taskchampion/src/testing/mod.rs @@ -0,0 +1 @@ +pub mod testserver; diff --git a/taskchampion/tests/shared/testserver.rs b/taskchampion/src/testing/testserver.rs similarity index 96% rename from taskchampion/tests/shared/testserver.rs rename to taskchampion/src/testing/testserver.rs index 2cb1cde2e..b81063b1b 100644 --- a/taskchampion/tests/shared/testserver.rs +++ b/taskchampion/src/testing/testserver.rs @@ -1,7 +1,7 @@ +use crate::server::{Blob, Server, VersionAdd}; use std::collections::HashMap; -use taskchampion::server::{Blob, Server, VersionAdd}; -pub struct TestServer { +pub(crate) struct TestServer { users: HashMap, } diff --git a/taskchampion/tests/data/tdb2-test.data b/taskchampion/tests/data/tdb2-test.data deleted file mode 100644 index 60477651b..000000000 --- a/taskchampion/tests/data/tdb2-test.data +++ /dev/null @@ -1,2 +0,0 @@ -[description:"https:\/\/phabricator.services.example.com\/D7364 &open;taskgraph&close; Download debian packages" end:"1541705209" entry:"1538520624" modified:"1541705209" phabricatorid:"D7364" priority:"M" project:"moz" status:"completed" tags:"phabricator,respond" uuid:"ca33f6d6-1688-4503-90be-3b3526a32b5a" wait:"1570118809"] -[annotation_1541461824:"https:\/\/github.com\/taskcluster\/taskcluster-worker-manager\/pull\/3" description:"https:\/\/github.com\/taskcluster\/taskcluster-worker-manager\/pull\/3 More changes" end:"1541702602" entry:"1541451283" githubbody:"some notes:\n\n1. This is a huge PR, so I'm not expecting a quick turn around at all. If you have questions, let me know and I can hope on Vidyo.\n1. Data persistence is written in a semi-janky way. My intention is to use the time while this is under review to make progress on postgres stuff so that the next review cycle will be using new Postgres things. Which means.... There's a lot of bugs in the concurrency because there's no synchronisation at all in this janky-ish model.\n1. The API is the minimum api required to get provisioning-ish things working\n1. I intend to write a system for automatically testing provider and bidding strategy implementations, so that you can do instantiate a provider\/strategy, stub\/spy it as needed then run a test suite against it and have it do its thing. The idea is that each provider will need to mock their underlying api system in their own way, but the set of tests we run for Provider API conformance would be pretty standardized. This should make writing tests for new providers a lot easier.\n1. The provider\/strategy loading system is intentionally simple. The idea is that these aren't general purpose plugins, but rather special ones. The idea is that the config files would essentially declare instances and then provide constructor arguments to initialize them all... This would make enabling\/disabling providers\/strategies fairly trivial\n1. I decided to drop fake implementations of providers and strategies for testing the provisioning logic and instead opt for Sinon stubs, which I think give us a better testing story\n1. I still intend to have fake providers and bidding strategies for doing API testing.\n\nLet me know, and again, I don't expect or need a quick turn around on this PR.\n" githubcreatedon:"1541451283" githubnamespace:"djmitche" githubnumber:"3.000000" githubrepo:"taskcluster\/taskcluster-worker-manager" githubtitle:"More changes" githubtype:"pull_request" githubupdatedat:"1541699191" githuburl:"https:\/\/github.com\/taskcluster\/taskcluster-worker-manager\/pull\/3" githubuser:"jhford" modified:"1541702602" priority:"H" project:"moz" status:"completed" tags:"respond" uuid:"2186f981-d1f5-4642-b833-5b16b3a2d334"] diff --git a/taskchampion/tests/operation_transform_invariant.rs b/taskchampion/tests/operation_transform_invariant.rs deleted file mode 100644 index 461aba009..000000000 --- a/taskchampion/tests/operation_transform_invariant.rs +++ /dev/null @@ -1,85 +0,0 @@ -use chrono::Utc; -use proptest::prelude::*; -use taskchampion::{taskstorage, Operation, DB}; -use uuid::Uuid; - -fn newdb() -> DB { - DB::new(Box::new(taskstorage::InMemoryStorage::new())) -} - -fn uuid_strategy() -> impl Strategy { - prop_oneof![ - Just(Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap()), - Just(Uuid::parse_str("56e0be07-c61f-494c-a54c-bdcfdd52d2a7").unwrap()), - Just(Uuid::parse_str("4b7ed904-f7b0-4293-8a10-ad452422c7b3").unwrap()), - Just(Uuid::parse_str("9bdd0546-07c8-4e1f-a9bc-9d6299f4773b").unwrap()), - ] -} - -fn operation_strategy() -> impl Strategy { - prop_oneof![ - uuid_strategy().prop_map(|uuid| Operation::Create { uuid }), - uuid_strategy().prop_map(|uuid| Operation::Delete { uuid }), - (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { - Operation::Update { - uuid, - property, - value: Some("true".into()), - timestamp: Utc::now(), - } - }), - ] -} - -proptest! { - #![proptest_config(ProptestConfig { - cases: 1024, .. ProptestConfig::default() - })] - #[test] - // check that the two operation sequences have the same effect, enforcing the invariant of - // the transform function. - fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { - let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); - - let mut db1 = newdb(); - let mut db2 = newdb(); - - // Ensure that any expected tasks already exist - if let Operation::Update{ ref uuid, .. } = o1 { - let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); - let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); - } - - if let Operation::Update{ ref uuid, .. } = o2 { - let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); - let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); - } - - if let Operation::Delete{ ref uuid } = o1 { - let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); - let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); - } - - if let Operation::Delete{ ref uuid } = o2 { - let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); - let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); - } - - // if applying the initial operations fail, that indicates the operation was invalid - // in the base state, so consider the case successful. - if let Err(_) = db1.apply(o1) { - return Ok(()); - } - if let Err(_) = db2.apply(o2) { - return Ok(()); - } - - if let Some(o) = o2p { - db1.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?; - } - if let Some(o) = o1p { - db2.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?; - } - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - } -} diff --git a/taskchampion/tests/shared/mod.rs b/taskchampion/tests/shared/mod.rs deleted file mode 100644 index 8a5a5ee0c..000000000 --- a/taskchampion/tests/shared/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod testserver; - -pub use testserver::TestServer; diff --git a/taskchampion/tests/sync.rs b/taskchampion/tests/sync.rs deleted file mode 100644 index 197708dee..000000000 --- a/taskchampion/tests/sync.rs +++ /dev/null @@ -1,123 +0,0 @@ -use chrono::Utc; -use taskchampion::{taskstorage, Operation, DB}; -use uuid::Uuid; - -mod shared; -use shared::TestServer; - -fn newdb() -> DB { - DB::new(Box::new(taskstorage::InMemoryStorage::new())) -} - -#[test] -fn test_sync() { - let mut server = TestServer::new(); - - let mut db1 = newdb(); - db1.sync("me", &mut server).unwrap(); - - let mut db2 = newdb(); - db2.sync("me", &mut server).unwrap(); - - // make some changes in parallel to db1 and db2.. - let uuid1 = Uuid::new_v4(); - db1.apply(Operation::Create { uuid: uuid1 }).unwrap(); - db1.apply(Operation::Update { - uuid: uuid1, - property: "title".into(), - value: Some("my first task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - let uuid2 = Uuid::new_v4(); - db2.apply(Operation::Create { uuid: uuid2 }).unwrap(); - db2.apply(Operation::Update { - uuid: uuid2, - property: "title".into(), - value: Some("my second task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and synchronize those around - db1.sync("me", &mut server).unwrap(); - db2.sync("me", &mut server).unwrap(); - db1.sync("me", &mut server).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - - // now make updates to the same task on both sides - db1.apply(Operation::Update { - uuid: uuid2, - property: "priority".into(), - value: Some("H".into()), - timestamp: Utc::now(), - }) - .unwrap(); - db2.apply(Operation::Update { - uuid: uuid2, - property: "project".into(), - value: Some("personal".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and synchronize those around - db1.sync("me", &mut server).unwrap(); - db2.sync("me", &mut server).unwrap(); - db1.sync("me", &mut server).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); -} - -#[test] -fn test_sync_create_delete() { - let mut server = TestServer::new(); - - let mut db1 = newdb(); - db1.sync("me", &mut server).unwrap(); - - let mut db2 = newdb(); - db2.sync("me", &mut server).unwrap(); - - // create and update a task.. - let uuid = Uuid::new_v4(); - db1.apply(Operation::Create { uuid }).unwrap(); - db1.apply(Operation::Update { - uuid: uuid, - property: "title".into(), - value: Some("my first task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and synchronize those around - db1.sync("me", &mut server).unwrap(); - db2.sync("me", &mut server).unwrap(); - db1.sync("me", &mut server).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - - // delete and re-create the task on db1 - db1.apply(Operation::Delete { uuid }).unwrap(); - db1.apply(Operation::Create { uuid }).unwrap(); - db1.apply(Operation::Update { - uuid: uuid, - property: "title".into(), - value: Some("my second task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and on db2, update a property of the task - db2.apply(Operation::Update { - uuid: uuid, - property: "project".into(), - value: Some("personal".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - db1.sync("me", &mut server).unwrap(); - db2.sync("me", &mut server).unwrap(); - db1.sync("me", &mut server).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); -} diff --git a/taskchampion/tests/sync_action_sequences.rs b/taskchampion/tests/sync_action_sequences.rs deleted file mode 100644 index 2c94d6dd8..000000000 --- a/taskchampion/tests/sync_action_sequences.rs +++ /dev/null @@ -1,72 +0,0 @@ -use chrono::Utc; -use proptest::prelude::*; -use taskchampion::{taskstorage, Operation, DB}; -use uuid::Uuid; - -mod shared; -use shared::TestServer; - -fn newdb() -> DB { - DB::new(Box::new(taskstorage::InMemoryStorage::new())) -} - -#[derive(Debug)] -enum Action { - Op(Operation), - Sync, -} - -fn action_sequence_strategy() -> impl Strategy> { - // Create, Update, Delete, or Sync on client 1, 2, .., followed by a round of syncs - "([CUDS][123])*S1S2S3S1S2".prop_map(|seq| { - let uuid = Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap(); - seq.as_bytes() - .chunks(2) - .map(|action_on| { - let action = match action_on[0] { - b'C' => Action::Op(Operation::Create { uuid }), - b'U' => Action::Op(Operation::Update { - uuid, - property: "title".into(), - value: Some("foo".into()), - timestamp: Utc::now(), - }), - b'D' => Action::Op(Operation::Delete { uuid }), - b'S' => Action::Sync, - _ => unreachable!(), - }; - let acton = action_on[1] - b'1'; - (action, acton) - }) - .collect::>() - }) -} - -proptest! { - #[test] - // check that various sequences of operations on mulitple db's do not get the db's into an - // incompatible state. The main concern here is that there might be a sequence of create - // and delete operations that results in a task existing in one DB but not existing in - // another. So, the generated sequences focus on a single task UUID. - fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { - let mut server = TestServer::new(); - let mut dbs = [newdb(), newdb(), newdb()]; - - for (action, db) in action_sequence { - println!("{:?} on db {}", action, db); - - let db = &mut dbs[db as usize]; - match action { - Action::Op(op) => { - if let Err(e) = db.apply(op) { - println!(" {:?} (ignored)", e); - } - }, - Action::Sync => db.sync("me", &mut server).unwrap(), - } - } - - assert_eq!(dbs[0].sorted_tasks(), dbs[0].sorted_tasks()); - assert_eq!(dbs[1].sorted_tasks(), dbs[2].sorted_tasks()); - } -} From 2296d0fa3521853a939a256909004243013eccbc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 16:03:04 -0500 Subject: [PATCH 060/548] rename DB to TaskDB for consistency --- taskchampion/src/replica.rs | 6 +-- taskchampion/src/taskdb.rs | 50 +++++++++++------------ taskchampion/src/taskstorage/mod.rs | 2 +- taskchampion/src/taskstorage/operation.rs | 10 ++--- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 874e74eca..a3df48f7e 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -1,7 +1,7 @@ use crate::errors::Error; use crate::server::Server; use crate::task::{Status, Task}; -use crate::taskdb::DB; +use crate::taskdb::TaskDB; use crate::taskstorage::{Operation, TaskMap, TaskStorage}; use chrono::Utc; use failure::Fallible; @@ -11,13 +11,13 @@ use uuid::Uuid; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. pub struct Replica { - taskdb: DB, + taskdb: TaskDB, } impl Replica { pub fn new(storage: Box) -> Replica { return Replica { - taskdb: DB::new(storage), + taskdb: TaskDB::new(storage), }; } diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 7fe9362fc..2221326c7 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -7,7 +7,7 @@ use std::collections::HashSet; use std::str; use uuid::Uuid; -pub struct DB { +pub struct TaskDB { storage: Box, } @@ -17,24 +17,24 @@ struct Version { operations: Vec, } -impl DB { - /// Create a new DB with the given backend storage - pub fn new(storage: Box) -> DB { - DB { storage } +impl TaskDB { + /// Create a new TaskDB with the given backend storage + pub fn new(storage: Box) -> TaskDB { + TaskDB { storage } } #[cfg(test)] - pub fn new_inmemory() -> DB { - DB::new(Box::new(crate::taskstorage::InMemoryStorage::new())) + pub fn new_inmemory() -> TaskDB { + TaskDB::new(Box::new(crate::taskstorage::InMemoryStorage::new())) } - /// Apply an operation to the DB. Aside from synchronization operations, this is the only way - /// to modify the DB. In cases where an operation does not make sense, this function will do - /// nothing and return an error (but leave the DB in a consistent state). + /// Apply an operation to the TaskDB. Aside from synchronization operations, this is the only way + /// to modify the TaskDB. In cases where an operation does not make sense, this function will do + /// nothing and return an error (but leave the TaskDB in a consistent state). pub fn apply(&mut self, op: Operation) -> Fallible<()> { // TODO: differentiate error types here? let mut txn = self.storage.txn()?; - if let err @ Err(_) = DB::apply_op(txn.as_mut(), &op) { + if let err @ Err(_) = TaskDB::apply_op(txn.as_mut(), &op) { return err; } txn.add_operation(op)?; @@ -183,7 +183,7 @@ impl DB { assert_eq!(version.version, txn.base_version()? + 1); println!("applying version {:?} from server", version.version); - DB::apply_version(txn.as_mut(), version)?; + TaskDB::apply_version(txn.as_mut(), version)?; } let operations: Vec = txn.operations()?.iter().map(|o| o.clone()).collect(); @@ -254,7 +254,7 @@ impl DB { } } if let Some(o) = svr_op { - if let Err(e) = DB::apply_op(txn, &o) { + if let Err(e) = TaskDB::apply_op(txn, &o) { println!("Invalid operation when syncing: {} (ignored)", e); } } @@ -309,7 +309,7 @@ mod tests { #[test] fn test_apply_create() { - let mut db = DB::new_inmemory(); + let mut db = TaskDB::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; db.apply(op.clone()).unwrap(); @@ -320,7 +320,7 @@ mod tests { #[test] fn test_apply_create_exists() { - let mut db = DB::new_inmemory(); + let mut db = TaskDB::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; db.apply(op.clone()).unwrap(); @@ -335,7 +335,7 @@ mod tests { #[test] fn test_apply_create_update() { - let mut db = DB::new_inmemory(); + let mut db = TaskDB::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; db.apply(op1.clone()).unwrap(); @@ -356,7 +356,7 @@ mod tests { #[test] fn test_apply_create_update_delete_prop() { - let mut db = DB::new_inmemory(); + let mut db = TaskDB::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; db.apply(op1.clone()).unwrap(); @@ -398,7 +398,7 @@ mod tests { #[test] fn test_apply_update_does_not_exist() { - let mut db = DB::new_inmemory(); + let mut db = TaskDB::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Update { uuid, @@ -417,7 +417,7 @@ mod tests { #[test] fn test_apply_create_delete() { - let mut db = DB::new_inmemory(); + let mut db = TaskDB::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; db.apply(op1.clone()).unwrap(); @@ -431,7 +431,7 @@ mod tests { #[test] fn test_apply_delete_not_present() { - let mut db = DB::new_inmemory(); + let mut db = TaskDB::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Delete { uuid }; @@ -446,7 +446,7 @@ mod tests { #[test] fn rebuild_working_set() -> Fallible<()> { - let mut db = DB::new_inmemory(); + let mut db = TaskDB::new_inmemory(); let uuids = vec![ Uuid::new_v4(), // 0: pending, not already in working set Uuid::new_v4(), // 1: pending, already in working set @@ -455,7 +455,7 @@ mod tests { Uuid::new_v4(), // 4: pending, already in working set ]; - // add everything to the DB + // add everything to the TaskDB for uuid in &uuids { db.apply(Operation::Create { uuid: uuid.clone() })?; } @@ -513,8 +513,8 @@ mod tests { Ok(()) } - fn newdb() -> DB { - DB::new(Box::new(InMemoryStorage::new())) + fn newdb() -> TaskDB { + TaskDB::new(Box::new(InMemoryStorage::new())) } #[test] @@ -666,7 +666,7 @@ mod tests { #[test] // check that various sequences of operations on mulitple db's do not get the db's into an // incompatible state. The main concern here is that there might be a sequence of create - // and delete operations that results in a task existing in one DB but not existing in + // and delete operations that results in a task existing in one TaskDB but not existing in // another. So, the generated sequences focus on a single task UUID. fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { let mut server = TestServer::new(); diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index bc0fb30a3..699b59ff2 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -68,7 +68,7 @@ pub trait TaskStorageTxn { fn operations<'a>(&mut self) -> Fallible>; /// Add an operation to the end of the list of operations in the storage. Note that this - /// merely *stores* the operation; it is up to the DB to apply it. + /// merely *stores* the operation; it is up to the TaskDB to apply it. fn add_operation(&mut self, op: Operation) -> Fallible<()>; /// Replace the current list of operations with a new list. diff --git a/taskchampion/src/taskstorage/operation.rs b/taskchampion/src/taskstorage/operation.rs index f8927a133..52bc55d56 100644 --- a/taskchampion/src/taskstorage/operation.rs +++ b/taskchampion/src/taskstorage/operation.rs @@ -128,7 +128,7 @@ impl Operation { #[cfg(test)] mod test { use super::*; - use crate::taskdb::DB; + use crate::taskdb::TaskDB; use crate::taskstorage::InMemoryStorage; use chrono::{Duration, Utc}; use proptest::prelude::*; @@ -148,7 +148,7 @@ mod test { // check that the two operation sequences have the same effect, enforcing the invariant of // the transform function. - let mut db1 = DB::new_inmemory(); + let mut db1 = TaskDB::new_inmemory(); if let Some(ref o) = setup { db1.apply(o.clone()).unwrap(); } @@ -157,7 +157,7 @@ mod test { db1.apply(o).unwrap(); } - let mut db2 = DB::new_inmemory(); + let mut db2 = TaskDB::new_inmemory(); if let Some(ref o) = setup { db2.apply(o.clone()).unwrap(); } @@ -310,8 +310,8 @@ mod test { fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); - let mut db1 = DB::new(Box::new(InMemoryStorage::new())); - let mut db2 = DB::new(Box::new(InMemoryStorage::new())); + let mut db1 = TaskDB::new(Box::new(InMemoryStorage::new())); + let mut db2 = TaskDB::new(Box::new(InMemoryStorage::new())); // Ensure that any expected tasks already exist if let Operation::Update{ ref uuid, .. } = o1 { From 084c978b31c03693c35321e834a896d2aa48e649 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 16:07:35 -0500 Subject: [PATCH 061/548] move docs back to top level --- {taskchampion/docs => docs}/.gitignore | 0 {taskchampion/docs => docs}/README.md | 0 {taskchampion/docs => docs}/book.toml | 0 {taskchampion/docs => docs}/src/SUMMARY.md | 0 {taskchampion/docs => docs}/src/data-model.md | 0 {taskchampion/docs => docs}/src/installation.md | 0 {taskchampion/docs => docs}/src/plans.md | 0 {taskchampion/docs => docs}/src/storage.md | 0 {taskchampion/docs => docs}/src/sync.md | 0 {taskchampion/docs => docs}/src/taskdb.md | 0 {taskchampion/docs => docs}/src/tasks.md | 0 {taskchampion/docs => docs}/src/usage.md | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename {taskchampion/docs => docs}/.gitignore (100%) rename {taskchampion/docs => docs}/README.md (100%) rename {taskchampion/docs => docs}/book.toml (100%) rename {taskchampion/docs => docs}/src/SUMMARY.md (100%) rename {taskchampion/docs => docs}/src/data-model.md (100%) rename {taskchampion/docs => docs}/src/installation.md (100%) rename {taskchampion/docs => docs}/src/plans.md (100%) rename {taskchampion/docs => docs}/src/storage.md (100%) rename {taskchampion/docs => docs}/src/sync.md (100%) rename {taskchampion/docs => docs}/src/taskdb.md (100%) rename {taskchampion/docs => docs}/src/tasks.md (100%) rename {taskchampion/docs => docs}/src/usage.md (100%) diff --git a/taskchampion/docs/.gitignore b/docs/.gitignore similarity index 100% rename from taskchampion/docs/.gitignore rename to docs/.gitignore diff --git a/taskchampion/docs/README.md b/docs/README.md similarity index 100% rename from taskchampion/docs/README.md rename to docs/README.md diff --git a/taskchampion/docs/book.toml b/docs/book.toml similarity index 100% rename from taskchampion/docs/book.toml rename to docs/book.toml diff --git a/taskchampion/docs/src/SUMMARY.md b/docs/src/SUMMARY.md similarity index 100% rename from taskchampion/docs/src/SUMMARY.md rename to docs/src/SUMMARY.md diff --git a/taskchampion/docs/src/data-model.md b/docs/src/data-model.md similarity index 100% rename from taskchampion/docs/src/data-model.md rename to docs/src/data-model.md diff --git a/taskchampion/docs/src/installation.md b/docs/src/installation.md similarity index 100% rename from taskchampion/docs/src/installation.md rename to docs/src/installation.md diff --git a/taskchampion/docs/src/plans.md b/docs/src/plans.md similarity index 100% rename from taskchampion/docs/src/plans.md rename to docs/src/plans.md diff --git a/taskchampion/docs/src/storage.md b/docs/src/storage.md similarity index 100% rename from taskchampion/docs/src/storage.md rename to docs/src/storage.md diff --git a/taskchampion/docs/src/sync.md b/docs/src/sync.md similarity index 100% rename from taskchampion/docs/src/sync.md rename to docs/src/sync.md diff --git a/taskchampion/docs/src/taskdb.md b/docs/src/taskdb.md similarity index 100% rename from taskchampion/docs/src/taskdb.md rename to docs/src/taskdb.md diff --git a/taskchampion/docs/src/tasks.md b/docs/src/tasks.md similarity index 100% rename from taskchampion/docs/src/tasks.md rename to docs/src/tasks.md diff --git a/taskchampion/docs/src/usage.md b/docs/src/usage.md similarity index 100% rename from taskchampion/docs/src/usage.md rename to docs/src/usage.md From 1de24f66dc21eff1363dc37d67358a0af0cb4737 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 16:12:46 -0500 Subject: [PATCH 062/548] restore README to top level --- taskchampion/README.md => README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) rename taskchampion/README.md => README.md (74%) diff --git a/taskchampion/README.md b/README.md similarity index 74% rename from taskchampion/README.md rename to README.md index 1a33e6b87..3a65a287f 100644 --- a/taskchampion/README.md +++ b/README.md @@ -5,7 +5,7 @@ TaskChampion is an open-source personal task-tracking application. Use it to keep track of what you need to do, with a quick command-line interface and flexible sorting and filtering. It is modeled on [TaskWarrior](https://taskwarrior.org), but not a drop-in replacement for that application. -Goals: +## Goals * Feature parity with TaskWarrior (but not compatibility) * Aproachable, maintainable codebase @@ -14,7 +14,15 @@ Goals: * Reliable concurrency - clients do not diverge * Storage performance O(n) with n number of tasks -See: +## Structure + +There are three crates here: + + * [taskchampion](./taskchampion) - the core of the tool + * [taskchampion-cli](./cli) - the command-line binary + * [taskchampion-sync-server](./sync-server) - the server against which `task sync` operates + +## See Also * [Documentation](docs/src/SUMMARY.md) (will be published as an mdbook eventually) * [Progress on the first version](https://github.com/djmitche/taskwarrior-rust/projects/1) From e0b69a62b1245e08380dc099ea24a7d7c9edffe0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 16:18:24 -0500 Subject: [PATCH 063/548] fix --help metadata --- cli/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 1ae6b148e..2d71285bc 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,10 +4,10 @@ use taskchampion::{taskstorage, Replica, Status}; use uuid::Uuid; fn main() { - let matches = App::new("Rask") + let matches = App::new("TaskChampion") .version("0.1") .author("Dustin J. Mitchell ") - .about("Replacement for TaskWarrior") + .about("Personal task-tracking") .subcommand( SubCommand::with_name("add").about("adds a task").arg( Arg::with_name("description") From fe4183c3cac20065e11a0745f93a2ab4588bac92 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 19:33:04 -0500 Subject: [PATCH 064/548] Refactor command-line handling into modules per subcommands --- Cargo.lock | 114 +++++++++++++++++++++++++++++++++++++++++ cli/Cargo.toml | 5 ++ cli/src/bin/task.rs | 43 ++++++++++++++++ cli/src/cmd/add.rs | 60 ++++++++++++++++++++++ cli/src/cmd/gc.rs | 37 +++++++++++++ cli/src/cmd/list.rs | 39 ++++++++++++++ cli/src/cmd/macros.rs | 45 ++++++++++++++++ cli/src/cmd/mod.rs | 69 +++++++++++++++++++++++++ cli/src/cmd/pending.rs | 49 ++++++++++++++++++ cli/src/lib.rs | 57 +++++++++++++++++++++ cli/src/main.rs | 59 --------------------- cli/tests/cli.rs | 42 +++++++++++++++ 12 files changed, 560 insertions(+), 59 deletions(-) create mode 100644 cli/src/bin/task.rs create mode 100644 cli/src/cmd/add.rs create mode 100644 cli/src/cmd/gc.rs create mode 100644 cli/src/cmd/list.rs create mode 100644 cli/src/cmd/macros.rs create mode 100644 cli/src/cmd/mod.rs create mode 100644 cli/src/cmd/pending.rs create mode 100644 cli/src/lib.rs delete mode 100644 cli/src/main.rs create mode 100644 cli/tests/cli.rs diff --git a/Cargo.lock b/Cargo.lock index e20743939..8471faca1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -24,6 +33,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "assert_cmd" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c88b9ca26f9c16ec830350d309397e74ee9abdfd8eb1f71cb6ecc71a3fc818da" +dependencies = [ + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atty" version = "0.2.14" @@ -144,6 +166,18 @@ dependencies = [ "bitflags", ] +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "failure" version = "0.1.8" @@ -166,6 +200,15 @@ dependencies = [ "synstructure", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -258,6 +301,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + [[package]] name = "miniz_oxide" version = "0.4.3" @@ -268,6 +317,12 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num-integer" version = "0.1.44" @@ -305,6 +360,35 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "predicates" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bfead12e90dccead362d62bb2c90a5f6fc4584963645bc7f71a735e0b0735a" +dependencies = [ + "difference", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" + +[[package]] +name = "predicates-tree" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" +dependencies = [ + "predicates-core", + "treeline", +] + [[package]] name = "proc-macro2" version = "1.0.24" @@ -524,6 +608,18 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "regex" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + [[package]] name = "regex-syntax" version = "0.6.21" @@ -667,7 +763,10 @@ dependencies = [ name = "taskchampion-cli" version = "0.1.0" dependencies = [ + "assert_cmd", "clap", + "failure", + "predicates", "taskchampion", "uuid", ] @@ -725,6 +824,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "time" version = "0.1.44" @@ -745,6 +853,12 @@ dependencies = [ "serde", ] +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + [[package]] name = "unicode-width" version = "0.1.8" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 948b05eb7..75ba8be47 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,3 +8,8 @@ edition = "2018" clap = "~2.33.0" uuid = { version = "0.8.1", features = ["serde", "v4"] } taskchampion = { path = "../taskchampion" } +failure = "0.1.8" + +[dev-dependencies] +assert_cmd = "1.0.1" +predicates = "1.0.5" diff --git a/cli/src/bin/task.rs b/cli/src/bin/task.rs new file mode 100644 index 000000000..e90ead9d2 --- /dev/null +++ b/cli/src/bin/task.rs @@ -0,0 +1,43 @@ +use clap::{Error as ClapError, ErrorKind}; +use std::process::exit; +use taskchampion_cli::parse_command_line; + +enum Output { + Stdout, + Stderr, +} +use Output::*; + +fn bail(err: E, output: Output, code: i32) -> ! { + match output { + Stdout => println!("{}", err), + Stderr => eprintln!("{}", err), + } + exit(code) +} + +fn main() { + let command = match parse_command_line(std::env::args_os()) { + Ok(command) => command, + Err(err) => { + match err.downcast::() { + Ok(err) => { + if err.kind == ErrorKind::HelpDisplayed + || err.kind == ErrorKind::VersionDisplayed + { + // --help and --version go to stdout and succeed + bail(err, Stdout, 0) + } else { + // other clap errors exit with failure + bail(err, Stderr, 1) + } + } + Err(err) => bail(err, Stderr, 1), + } + } + }; + + if let Err(err) = command.run() { + bail(err, Stderr, 1) + } +} diff --git a/cli/src/cmd/add.rs b/cli/src/cmd/add.rs new file mode 100644 index 000000000..c34e82453 --- /dev/null +++ b/cli/src/cmd/add.rs @@ -0,0 +1,60 @@ +use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; +use failure::{format_err, Fallible}; +use taskchampion::Status; +use uuid::Uuid; + +use crate::cmd::{ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + description: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("add").about("adds a task").arg( + Arg::with_name("description") + .help("task description") + .required(true), + ), + ) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("add", Some(matches)) => { + // TODO: .unwrap() would be safe here as description is required above + let description: String = match matches.value_of("description") { + Some(v) => v.into(), + None => return ArgMatchResult::Err(format_err!("no description provided")), + }; + ArgMatchResult::Ok(Box::new(Invocation { description })) + } + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let uuid = Uuid::new_v4(); + command + .get_replica() + .new_task(uuid, Status::Pending, self.description.clone()) + .unwrap(); + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "add", "foo bar"], |inv: &Invocation| { + assert_eq!(inv.description, "foo bar".to_string()); + }); + } +} diff --git a/cli/src/cmd/gc.rs b/cli/src/cmd/gc.rs new file mode 100644 index 000000000..d8948a715 --- /dev/null +++ b/cli/src/cmd/gc.rs @@ -0,0 +1,37 @@ +use crate::cmd::{ArgMatchResult, CommandInvocation}; +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; + +#[derive(Debug)] +struct Invocation {} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand(ClapSubCommand::with_name("gc").about("run garbage collection")) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("gc", _) => ArgMatchResult::Ok(Box::new(Invocation {})), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + command.get_replica().gc()?; + println!("garbage collected."); + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "gc"], |_inv| {}); + } +} diff --git a/cli/src/cmd/list.rs b/cli/src/cmd/list.rs new file mode 100644 index 000000000..fb02e4645 --- /dev/null +++ b/cli/src/cmd/list.rs @@ -0,0 +1,39 @@ +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; + +use crate::cmd::{ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation {} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand(ClapSubCommand::with_name("list").about("lists tasks")) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("list", _) => ArgMatchResult::Ok(Box::new(Invocation {})), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + for (uuid, task) in command.get_replica().all_tasks().unwrap() { + println!("{} - {:?}", uuid, task); + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "list"], |_inv| {}); + } +} diff --git a/cli/src/cmd/macros.rs b/cli/src/cmd/macros.rs new file mode 100644 index 000000000..e0fbcd47c --- /dev/null +++ b/cli/src/cmd/macros.rs @@ -0,0 +1,45 @@ +/// Define a Command type implementing SubCommand with the enclosed methods (`decorate_app` and +/// `arg_match`), along with a module-level `cmd` function as the parent module expects. +macro_rules! define_subcommand { + ($($f:item) +) => { + struct Command; + + pub(super) fn cmd() -> Box { + Box::new(Command) + } + + impl crate::cmd::SubCommand for Command { + $($f)+ + } + } +} + +/// Define an Invocation type implementing SubCommandInvocation with the enclosed methods. +macro_rules! subcommand_invocation { + ($($f:item) +) => { + impl crate::cmd::SubCommandInvocation for Invocation { + $($f)+ + + #[cfg(test)] + fn as_any(&self) -> &dyn std::any::Any { + self + } + } + } + +} + +/// Parse the first argument as a command line and convert the result to an Invocation (which must +/// be in scope). If the conversion works, calls the second argument with it. +#[cfg(test)] +macro_rules! with_subcommand_invocation { + ($args:expr, $check:expr) => { + let parsed = crate::parse_command_line($args).unwrap(); + let si = parsed + .subcommand + .as_any() + .downcast_ref::() + .expect("SubComand is not of the expected type"); + ($check)(si); + }; +} diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs new file mode 100644 index 000000000..0ec5ae5fd --- /dev/null +++ b/cli/src/cmd/mod.rs @@ -0,0 +1,69 @@ +use clap::{App, ArgMatches}; +use failure::{Error, Fallible}; +use std::path::Path; +use taskchampion::{taskstorage, Replica}; + +#[macro_use] +mod macros; + +mod add; +mod gc; +mod list; +mod pending; + +/// Get a list of all subcommands in this crate +pub(crate) fn subcommands() -> Vec> { + vec![add::cmd(), gc::cmd(), list::cmd(), pending::cmd()] +} + +/// The result of a [`crate::cmd::SubCommand::arg_match`] call +pub(crate) enum ArgMatchResult { + /// No match + None, + + /// A good match + Ok(Box), + + /// A match, but an issue with the command line + Err(Error), +} + +/// A subcommand represents a defined subcommand, and is typically a singleton. +pub(crate) trait SubCommand { + /// Decorate the given [`clap::App`] appropriately for this subcommand + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a>; + + /// If this ArgMatches is for this command, return an appropriate invocation. + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult; +} + +/// A subcommand invocation is specialized to a subcommand +pub(crate) trait SubCommandInvocation: std::fmt::Debug { + fn run(&self, command: &CommandInvocation) -> Fallible<()>; + + // tests use downcasting, which requires a function to cast to Any + #[cfg(test)] + fn as_any(&self) -> &dyn std::any::Any; +} + +/// A command invocation contains all of the necessary regarding a single invocation of the CLI. +#[derive(Debug)] +pub struct CommandInvocation { + pub(crate) subcommand: Box, +} + +impl CommandInvocation { + pub(crate) fn new(subcommand: Box) -> Self { + Self { subcommand } + } + + pub fn run(self) -> Fallible<()> { + self.subcommand.run(&self) + } + + fn get_replica(&self) -> Replica { + Replica::new(Box::new( + taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(), + )) + } +} diff --git a/cli/src/cmd/pending.rs b/cli/src/cmd/pending.rs new file mode 100644 index 000000000..e847a4aef --- /dev/null +++ b/cli/src/cmd/pending.rs @@ -0,0 +1,49 @@ +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; + +use crate::cmd::{ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation {} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand(ClapSubCommand::with_name("pending").about("lists pending tasks")) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("pending", _) => ArgMatchResult::Ok(Box::new(Invocation {})), + // default to this command when no subcommand is given + ("", _) => ArgMatchResult::Ok(Box::new(Invocation {})), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let working_set = command.get_replica().working_set().unwrap(); + for i in 1..working_set.len() { + if let Some(ref task) = working_set[i] { + println!("{}: {} - {:?}", i, task.get_uuid(), task); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "pending"], |_inv| {}); + } + + #[test] + fn parse_command_default() { + with_subcommand_invocation!(vec!["task"], |_inv| {}); + } +} diff --git a/cli/src/lib.rs b/cli/src/lib.rs new file mode 100644 index 000000000..af5aed9a7 --- /dev/null +++ b/cli/src/lib.rs @@ -0,0 +1,57 @@ +use clap::{App, AppSettings}; +use failure::Fallible; +use std::ffi::OsString; + +mod cmd; +use cmd::ArgMatchResult; +pub use cmd::CommandInvocation; + +/// Parse the given command line and return an as-yet un-executed CommandInvocation. +pub fn parse_command_line(iter: I) -> Fallible +where + I: IntoIterator, + T: Into + Clone, +{ + let subcommands = cmd::subcommands(); + + let mut app = App::new("TaskChampion") + .version("0.1") + .about("Personal task-tracking") + .setting(AppSettings::ColoredHelp); + + for subcommand in subcommands.iter() { + app = subcommand.decorate_app(app); + } + + let matches = app.get_matches_from_safe(iter)?; + + for subcommand in subcommands.iter() { + match subcommand.arg_match(&matches) { + ArgMatchResult::Ok(invocation) => return Ok(CommandInvocation::new(invocation)), + ArgMatchResult::Err(err) => return Err(err), + ArgMatchResult::None => {} + } + } + + // one of the subcommands also matches the lack of subcommands, so this never + // occurrs. + unreachable!() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse_command_line_success() -> Fallible<()> { + // This just verifies that one of the subcommands works; the subcommands themselves + // are tested in their own unit tests. + parse_command_line(vec!["task", "pending"].iter())?; + Ok(()) + } + + #[test] + fn test_parse_command_line_failure() { + assert!(parse_command_line(vec!["task", "--no-such-arg"].iter()).is_err()); + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs deleted file mode 100644 index 2d71285bc..000000000 --- a/cli/src/main.rs +++ /dev/null @@ -1,59 +0,0 @@ -use clap::{App, Arg, SubCommand}; -use std::path::Path; -use taskchampion::{taskstorage, Replica, Status}; -use uuid::Uuid; - -fn main() { - let matches = App::new("TaskChampion") - .version("0.1") - .author("Dustin J. Mitchell ") - .about("Personal task-tracking") - .subcommand( - SubCommand::with_name("add").about("adds a task").arg( - Arg::with_name("description") - .help("task description") - .required(true), - ), - ) - .subcommand(SubCommand::with_name("list").about("lists tasks")) - .subcommand(SubCommand::with_name("pending").about("lists pending tasks")) - .subcommand(SubCommand::with_name("gc").about("run garbage collection")) - .get_matches(); - - let mut replica = Replica::new(Box::new( - taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(), - )); - - match matches.subcommand() { - ("add", Some(matches)) => { - let uuid = Uuid::new_v4(); - replica - .new_task( - uuid, - Status::Pending, - matches.value_of("description").unwrap().into(), - ) - .unwrap(); - } - ("list", _) => { - for (uuid, task) in replica.all_tasks().unwrap() { - println!("{} - {:?}", uuid, task); - } - } - ("pending", _) => { - let working_set = replica.working_set().unwrap(); - for i in 1..working_set.len() { - if let Some(ref task) = working_set[i] { - println!("{}: {} - {:?}", i, task.get_uuid(), task); - } - } - } - ("gc", _) => { - replica.gc().unwrap(); - } - ("", None) => { - unreachable!(); - } - _ => unreachable!(), - }; -} diff --git a/cli/tests/cli.rs b/cli/tests/cli.rs new file mode 100644 index 000000000..711adc1a5 --- /dev/null +++ b/cli/tests/cli.rs @@ -0,0 +1,42 @@ +use assert_cmd::prelude::*; +use predicates::prelude::*; +use std::process::Command; + +// This tests that the task binary is running and parsing arguments. The details of subcommands +// are handled with unit tests. + +#[test] +fn help() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("task")?; + + cmd.arg("--help"); + cmd.assert() + .success() + .stdout(predicate::str::contains("Personal task-tracking")); + + Ok(()) +} + +#[test] +fn version() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("task")?; + + cmd.arg("--version"); + cmd.assert() + .success() + .stdout(predicate::str::contains("TaskChampion")); + + Ok(()) +} + +#[test] +fn invalid_option() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("task")?; + + cmd.arg("--no-such-option"); + cmd.assert() + .failure() + .stderr(predicate::str::contains("USAGE")); + + Ok(()) +} From f31a96176d093f0a02b74bf8adfd2f034a680e0d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 23 Nov 2020 21:58:45 -0500 Subject: [PATCH 065/548] use prettytable for tabular outputs --- Cargo.lock | 155 +++++++++++++++++++++++++++++++++++++++++ cli/Cargo.toml | 1 + cli/src/cmd/list.rs | 8 ++- cli/src/cmd/pending.rs | 8 ++- cli/src/lib.rs | 2 + cli/src/table.rs | 8 +++ 6 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 cli/src/table.rs diff --git a/Cargo.lock b/Cargo.lock index 8471faca1..4db4681d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "assert_cmd" version = "1.0.1" @@ -83,6 +95,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "bit-set" version = "0.5.2" @@ -104,6 +122,29 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "bstr" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "byteorder" version = "1.3.4" @@ -166,18 +207,74 @@ dependencies = [ "bitflags", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg 1.0.1", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "csv" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4666154fd004af3fd6f1da2e81a96fd5a81927fe8ddb6ecc79e2aa6e138b54" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "dirs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "failure" version = "0.1.8" @@ -389,6 +486,20 @@ dependencies = [ "treeline", ] +[[package]] +name = "prettytable-rs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" +dependencies = [ + "atty", + "csv", + "encode_unicode", + "lazy_static", + "term", + "unicode-width", +] + [[package]] name = "proc-macro2" version = "1.0.24" @@ -608,6 +719,17 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + [[package]] name = "regex" version = "1.4.2" @@ -620,6 +742,15 @@ dependencies = [ "thread_local", ] +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", +] + [[package]] name = "regex-syntax" version = "0.6.21" @@ -656,6 +787,18 @@ dependencies = [ "serde", ] +[[package]] +name = "rust-argon2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rustc-demangle" version = "0.1.18" @@ -767,6 +910,7 @@ dependencies = [ "clap", "failure", "predicates", + "prettytable-rs", "taskchampion", "uuid", ] @@ -795,6 +939,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "term" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" +dependencies = [ + "byteorder", + "dirs", + "winapi", +] + [[package]] name = "textwrap" version = "0.11.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 75ba8be47..c5bf41c85 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,6 +9,7 @@ clap = "~2.33.0" uuid = { version = "0.8.1", features = ["serde", "v4"] } taskchampion = { path = "../taskchampion" } failure = "0.1.8" +prettytable-rs = "0.8.0" [dev-dependencies] assert_cmd = "1.0.1" diff --git a/cli/src/cmd/list.rs b/cli/src/cmd/list.rs index fb02e4645..2d51484e9 100644 --- a/cli/src/cmd/list.rs +++ b/cli/src/cmd/list.rs @@ -1,5 +1,7 @@ +use crate::table; use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; use failure::Fallible; +use prettytable::{cell, row, Table}; use crate::cmd::{ArgMatchResult, CommandInvocation}; @@ -21,9 +23,13 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut t = Table::new(); + t.set_format(table::format()); + t.set_titles(row![b->"uuid", b->"description"]); for (uuid, task) in command.get_replica().all_tasks().unwrap() { - println!("{} - {:?}", uuid, task); + t.add_row(row![uuid, task.get_description()]); } + t.printstd(); Ok(()) } } diff --git a/cli/src/cmd/pending.rs b/cli/src/cmd/pending.rs index e847a4aef..8fbca52d9 100644 --- a/cli/src/cmd/pending.rs +++ b/cli/src/cmd/pending.rs @@ -1,5 +1,7 @@ +use crate::table; use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; use failure::Fallible; +use prettytable::{cell, row, Table}; use crate::cmd::{ArgMatchResult, CommandInvocation}; @@ -24,11 +26,15 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { let working_set = command.get_replica().working_set().unwrap(); + let mut t = Table::new(); + t.set_format(table::format()); + t.set_titles(row![b->"id", b->"description"]); for i in 1..working_set.len() { if let Some(ref task) = working_set[i] { - println!("{}: {} - {:?}", i, task.get_uuid(), task); + t.add_row(row![i, task.get_description()]); } } + t.printstd(); Ok(()) } } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index af5aed9a7..a71670ac1 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -3,6 +3,8 @@ use failure::Fallible; use std::ffi::OsString; mod cmd; +mod table; + use cmd::ArgMatchResult; pub use cmd::CommandInvocation; diff --git a/cli/src/table.rs b/cli/src/table.rs new file mode 100644 index 000000000..9fdb9b833 --- /dev/null +++ b/cli/src/table.rs @@ -0,0 +1,8 @@ +use prettytable::format; + +pub(crate) fn format() -> format::TableFormat { + format::FormatBuilder::new() + .column_separator(' ') + .borders(' ') + .build() +} From 82337632951e9ceba8bb6e9bf91332f423acab1f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Nov 2020 11:31:56 -0500 Subject: [PATCH 066/548] add an 'info' subcommand --- cli/src/cmd/info.rs | 55 ++++++++++++++++++++++++++++++++++++++++ cli/src/cmd/mod.rs | 10 +++++++- cli/src/cmd/shared.rs | 38 +++++++++++++++++++++++++++ taskchampion/src/task.rs | 10 ++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 cli/src/cmd/info.rs create mode 100644 cli/src/cmd/shared.rs diff --git a/cli/src/cmd/info.rs b/cli/src/cmd/info.rs new file mode 100644 index 000000000..bcb84d279 --- /dev/null +++ b/cli/src/cmd/info.rs @@ -0,0 +1,55 @@ +use crate::table; +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; +use prettytable::{cell, row, Table}; + +use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + task: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("info") + .about("info on the given task") + .arg(shared::task_arg())) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("info", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { + task: matches.value_of("task").unwrap().into(), + })), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let task = shared::get_task(&mut command.get_replica(), &self.task)?; + + let mut t = Table::new(); + t.set_format(table::format()); + t.add_row(row![b->"Uuid", task.get_uuid()]); + t.add_row(row![b->"Description", task.get_description()]); + t.add_row(row![b->"Status", task.get_status()]); + t.printstd(); + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "info", "1"], |inv: &Invocation| { + assert_eq!(inv.task, "1".to_string()); + }); + } +} diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index 0ec5ae5fd..e7d27255b 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -5,15 +5,23 @@ use taskchampion::{taskstorage, Replica}; #[macro_use] mod macros; +mod shared; mod add; mod gc; +mod info; mod list; mod pending; /// Get a list of all subcommands in this crate pub(crate) fn subcommands() -> Vec> { - vec![add::cmd(), gc::cmd(), list::cmd(), pending::cmd()] + vec![ + add::cmd(), + gc::cmd(), + list::cmd(), + pending::cmd(), + info::cmd(), + ] } /// The result of a [`crate::cmd::SubCommand::arg_match`] call diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs new file mode 100644 index 000000000..eb472ab23 --- /dev/null +++ b/cli/src/cmd/shared.rs @@ -0,0 +1,38 @@ +use clap::Arg; +use failure::{format_err, Fallible}; +use taskchampion::{Replica, Task}; +use uuid::Uuid; + +pub(super) fn task_arg<'a>() -> Arg<'a, 'a> { + Arg::with_name("task") + .help("task id or uuid") + .required(true) +} + +pub(super) fn get_task>(replica: &mut Replica, task_arg: S) -> Fallible { + let task_arg = task_arg.as_ref(); + + // first try treating task as a working-set reference + match task_arg.parse::() { + Ok(i) => { + let mut working_set = replica.working_set().unwrap(); + if i > 0 && i < working_set.len() as u64 { + if let Some(task) = working_set[i as usize].take() { + return Ok(task); + } + } + } + Err(_) => {} + } + + match Uuid::parse_str(task_arg) { + Ok(uuid) => { + if let Some(task) = replica.get_task(&uuid)? { + return Ok(task); + } + } + Err(_) => {} + } + + Err(format_err!("Cannot interpret {:?} as a task", task_arg)) +} diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index c0ccc1480..69d2a6f56 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -68,6 +68,16 @@ impl Status { Status::Deleted => "D", } } + + /// Get the full-name value for this status to use in the TaskMap. + pub fn to_string(&self) -> &str { + // TODO: should be impl Display + match self { + Status::Pending => "Pending", + Status::Completed => "Completed", + Status::Deleted => "Deleted", + } + } } #[derive(Debug, PartialEq)] From c8f14d68cb3e7a989134b0f02e0ac6da3a4a2ce5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Nov 2020 11:44:21 -0500 Subject: [PATCH 067/548] export the Uuid type from taskchampion --- Cargo.lock | 1 - cli/Cargo.toml | 1 - cli/src/cmd/add.rs | 3 +-- cli/src/cmd/shared.rs | 3 +-- taskchampion/src/lib.rs | 3 +++ 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4db4681d0..15ecf0a9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -912,7 +912,6 @@ dependencies = [ "predicates", "prettytable-rs", "taskchampion", - "uuid", ] [[package]] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c5bf41c85..cf26fad96 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -6,7 +6,6 @@ edition = "2018" [dependencies] clap = "~2.33.0" -uuid = { version = "0.8.1", features = ["serde", "v4"] } taskchampion = { path = "../taskchampion" } failure = "0.1.8" prettytable-rs = "0.8.0" diff --git a/cli/src/cmd/add.rs b/cli/src/cmd/add.rs index c34e82453..a04ad8658 100644 --- a/cli/src/cmd/add.rs +++ b/cli/src/cmd/add.rs @@ -1,7 +1,6 @@ use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; use failure::{format_err, Fallible}; -use taskchampion::Status; -use uuid::Uuid; +use taskchampion::{Status, Uuid}; use crate::cmd::{ArgMatchResult, CommandInvocation}; diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index eb472ab23..570095fb1 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -1,7 +1,6 @@ use clap::Arg; use failure::{format_err, Fallible}; -use taskchampion::{Replica, Task}; -use uuid::Uuid; +use taskchampion::{Replica, Task, Uuid}; pub(super) fn task_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("task") diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index d5d1d13be..471437a4f 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -34,5 +34,8 @@ pub use task::Priority; pub use task::Status; pub use task::{Task, TaskMut}; +/// Re-exported type from the `uuid` crate, for ease of compatibility for consumers of this crate. +pub use uuid::Uuid; + #[cfg(test)] pub(crate) mod testing; From fc668e5ca86944ca096abd96eeadb10faf923546 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Nov 2020 11:52:50 -0500 Subject: [PATCH 068/548] use usize to index working set --- taskchampion/src/replica.rs | 4 ++-- taskchampion/src/taskdb.rs | 4 ++-- taskchampion/src/taskstorage/inmemory.rs | 7 +++---- taskchampion/src/taskstorage/kv.rs | 7 ++++--- taskchampion/src/taskstorage/mod.rs | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index a3df48f7e..19937eb2c 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -48,7 +48,7 @@ impl Replica { } /// Add the given uuid to the working set, returning its index. - pub(crate) fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + pub(crate) fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { self.taskdb.add_to_working_set(uuid) } @@ -92,7 +92,7 @@ impl Replica { } /// Get an existing task by its working set index - pub fn get_working_set_task(&mut self, i: u64) -> Fallible> { + pub fn get_working_set_task(&mut self, i: usize) -> Fallible> { let working_set = self.taskdb.working_set()?; if (i as usize) < working_set.len() { if let Some(uuid) = working_set[i as usize] { diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 2221326c7..a24ffff0b 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -153,13 +153,13 @@ impl TaskDB { /// Add the given uuid to the working set and return its index; if it is already in the working /// set, its index is returned. This does *not* renumber any existing tasks. - pub fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + pub fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { let mut txn = self.storage.txn()?; // search for an existing entry for this task.. for (i, elt) in txn.get_working_set()?.iter().enumerate() { if *elt == Some(*uuid) { // (note that this drops the transaction with no changes made) - return Ok(i as u64); + return Ok(i); } } // and if not found, add one diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs index cdc376c5e..c438b548e 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/taskstorage/inmemory.rs @@ -108,14 +108,13 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(self.data_ref().working_set.clone()) } - fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { let working_set = &mut self.mut_data_ref().working_set; working_set.push(Some(uuid.clone())); - Ok(working_set.len() as u64) + Ok(working_set.len()) } - fn remove_from_working_set(&mut self, index: u64) -> Fallible<()> { - let index = index as usize; + fn remove_from_working_set(&mut self, index: usize) -> Fallible<()> { let working_set = &mut self.mut_data_ref().working_set; if index >= working_set.len() || working_set[index].is_none() { return Err(format_err!("No task found with index {}", index)); diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index 8cbe033b7..f7cd0cc4b 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -302,7 +302,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(res) } - fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); let kvtxn = self.kvtxn(); @@ -323,10 +323,11 @@ impl<'t> TaskStorageTxn for Txn<'t> { NEXT_WORKING_SET_INDEX.into(), Msgpack::to_value_buf(next_index + 1)?, )?; - Ok(next_index) + Ok(next_index as usize) } - fn remove_from_working_set(&mut self, index: u64) -> Fallible<()> { + fn remove_from_working_set(&mut self, index: usize) -> Fallible<()> { + let index = index as u64; let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); let kvtxn = self.kvtxn(); diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index 699b59ff2..8c37e934b 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -80,10 +80,10 @@ pub trait TaskStorageTxn { /// Add a task to the working set and return its (one-based) index. This index will be one greater /// than the highest used index. - fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible; + fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible; /// Remove a task from the working set. Other tasks' indexes are not affected. - fn remove_from_working_set(&mut self, index: u64) -> Fallible<()>; + fn remove_from_working_set(&mut self, index: usize) -> Fallible<()>; /// Clear all tasks from the working set in preparation for a garbage-collection operation. fn clear_working_set(&mut self) -> Fallible<()>; From 2dd86edd4a1fffb76d57dc75daae3eec0e05aca2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Nov 2020 11:54:48 -0500 Subject: [PATCH 069/548] use Replica::get_working_set_task --- cli/src/cmd/shared.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index eb472ab23..cc969efc6 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -13,13 +13,10 @@ pub(super) fn get_task>(replica: &mut Replica, task_arg: S) -> Fal let task_arg = task_arg.as_ref(); // first try treating task as a working-set reference - match task_arg.parse::() { + match task_arg.parse::() { Ok(i) => { - let mut working_set = replica.working_set().unwrap(); - if i > 0 && i < working_set.len() as u64 { - if let Some(task) = working_set[i as usize].take() { - return Ok(task); - } + if let Some(task) = replica.get_working_set_task(i)? { + return Ok(task); } } Err(_) => {} From 1c5e9b009b965fee7967a4327931d04f996a33ce Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Nov 2020 12:05:30 -0500 Subject: [PATCH 070/548] Add Replica::get_working_set_index and use it This is probably ridiculously inefficient, as it will load the working set for each and every task listed. Optimize later! --- cli/src/cmd/info.rs | 9 +++++++-- cli/src/cmd/list.rs | 11 ++++++++--- taskchampion/src/replica.rs | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/cli/src/cmd/info.rs b/cli/src/cmd/info.rs index bcb84d279..cdc39cc11 100644 --- a/cli/src/cmd/info.rs +++ b/cli/src/cmd/info.rs @@ -30,11 +30,16 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let task = shared::get_task(&mut command.get_replica(), &self.task)?; + let mut replica = command.get_replica(); + let task = shared::get_task(&mut replica, &self.task)?; + let uuid = task.get_uuid(); let mut t = Table::new(); t.set_format(table::format()); - t.add_row(row![b->"Uuid", task.get_uuid()]); + t.add_row(row![b->"Uuid", uuid]); + if let Some(i) = replica.get_working_set_index(uuid)? { + t.add_row(row![b->"Id", i]); + } t.add_row(row![b->"Description", task.get_description()]); t.add_row(row![b->"Status", task.get_status()]); t.printstd(); diff --git a/cli/src/cmd/list.rs b/cli/src/cmd/list.rs index 2d51484e9..d29227c92 100644 --- a/cli/src/cmd/list.rs +++ b/cli/src/cmd/list.rs @@ -23,11 +23,16 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica(); let mut t = Table::new(); t.set_format(table::format()); - t.set_titles(row![b->"uuid", b->"description"]); - for (uuid, task) in command.get_replica().all_tasks().unwrap() { - t.add_row(row![uuid, task.get_description()]); + t.set_titles(row![b->"id", b->"description"]); + for (uuid, task) in replica.all_tasks().unwrap() { + let mut id = uuid.to_string(); + if let Some(i) = replica.get_working_set_index(&uuid)? { + id = i.to_string(); + } + t.add_row(row![id, task.get_description()]); } t.printstd(); Ok(()) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 19937eb2c..1485a9e0b 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -105,6 +105,19 @@ impl Replica { return Ok(None); } + /// Get the working set index for the given task uuid + pub fn get_working_set_index(&mut self, uuid: &Uuid) -> Fallible> { + let working_set = self.taskdb.working_set()?; + for (i, u) in working_set.iter().enumerate() { + if let Some(ref u) = u { + if u == uuid { + return Ok(Some(i)); + } + } + } + Ok(None) + } + /// Create a new task. The task must not already exist. pub fn new_task(&mut self, uuid: Uuid, status: Status, description: String) -> Fallible { // check that it doesn't exist; this is a convenience check, as the task @@ -223,6 +236,10 @@ mod tests { let t = rep.get_task(&uuid).unwrap().unwrap(); assert_eq!(t.get_status(), Status::Deleted); assert_eq!(t.get_description(), "gone"); + + rep.gc().unwrap(); + + assert!(rep.get_working_set_index(t.get_uuid()).unwrap().is_none()); } #[test] @@ -241,6 +258,8 @@ mod tests { assert_eq!(ws.len(), 2); assert!(ws[0].is_none()); assert_eq!(ws[1].as_ref().unwrap().get_uuid(), &uuid); + + assert_eq!(rep.get_working_set_index(t.get_uuid()).unwrap().unwrap(), 1); } #[test] From ca70d2c914f772fdadccbed0c24f36e64b51e1df Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Nov 2020 12:44:32 -0500 Subject: [PATCH 071/548] add cargo clippy to CI --- .taskcluster.yml | 29 ++++++++++++++++-- cli/src/cmd/pending.rs | 4 +-- cli/src/cmd/shared.rs | 18 ++++-------- sync-server/src/lib.rs | 9 +++--- taskchampion/src/lib.rs | 1 + taskchampion/src/replica.rs | 31 ++++++++++--------- taskchampion/src/task.rs | 10 +++---- taskchampion/src/taskdb.rs | 36 ++++++++++------------- taskchampion/src/taskstorage/inmemory.rs | 16 +++++----- taskchampion/src/taskstorage/kv.rs | 24 +++++++-------- taskchampion/src/taskstorage/mod.rs | 6 ++-- taskchampion/src/taskstorage/operation.rs | 7 ++--- 12 files changed, 98 insertions(+), 93 deletions(-) diff --git a/.taskcluster.yml b/.taskcluster.yml index 5f7883c74..9cdef3214 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -37,13 +37,38 @@ tasks: cd repo && git config advice.detachedHead false && git checkout ${ref} && - cargo test && - cargo fmt -- --check + cargo test metadata: name: taskchampion-tests description: Run tests for taskchampion owner: dustin@v.igoro.us source: ${repo_url} + - $if: run + then: + provisionerId: 'proj-misc' + workerType: 'ci' + deadline: {$fromNow: '1 hour'} + expires: {$fromNow: '1 day'} + payload: + maxRunTime: 3600 + image: rust:latest + command: + - /bin/bash + - '-c' + - >- + rustup component add rustfmt && + git clone ${repo_url} repo && + cd repo && + git config advice.detachedHead false && + git checkout ${ref} && + rustup component add clippy-preview && + cargo clippy && + cargo fmt -- --check + metadata: + name: taskchampion-clippy + description: Run clippy and rustfmt for taskchampion + owner: dustin@v.igoro.us + source: ${repo_url} - $if: run then: provisionerId: 'proj-misc' diff --git a/cli/src/cmd/pending.rs b/cli/src/cmd/pending.rs index 8fbca52d9..b55c044d0 100644 --- a/cli/src/cmd/pending.rs +++ b/cli/src/cmd/pending.rs @@ -29,8 +29,8 @@ subcommand_invocation! { let mut t = Table::new(); t.set_format(table::format()); t.set_titles(row![b->"id", b->"description"]); - for i in 1..working_set.len() { - if let Some(ref task) = working_set[i] { + for (i, item) in working_set.iter().enumerate() { + if let Some(ref task) = item { t.add_row(row![i, task.get_description()]); } } diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index a7ef4ff58..4e24bfcc9 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -12,22 +12,16 @@ pub(super) fn get_task>(replica: &mut Replica, task_arg: S) -> Fal let task_arg = task_arg.as_ref(); // first try treating task as a working-set reference - match task_arg.parse::() { - Ok(i) => { - if let Some(task) = replica.get_working_set_task(i)? { - return Ok(task); - } + if let Ok(i) = task_arg.parse::() { + if let Some(task) = replica.get_working_set_task(i)? { + return Ok(task); } - Err(_) => {} } - match Uuid::parse_str(task_arg) { - Ok(uuid) => { - if let Some(task) = replica.get_task(&uuid)? { - return Ok(task); - } + if let Ok(uuid) = Uuid::parse_str(task_arg) { + if let Some(task) = replica.get_task(&uuid)? { + return Ok(task); } - Err(_) => {} } Err(format_err!("Cannot interpret {:?} as a task", task_arg)) diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs index 4a50d77a2..e85125ed0 100644 --- a/sync-server/src/lib.rs +++ b/sync-server/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::new_without_default)] + use std::collections::HashMap; type Blob = Vec; @@ -32,10 +34,7 @@ impl User { if last_version == since_version as usize { return vec![]; } - self.versions[since_version as usize..last_version] - .iter() - .map(|r| r.clone()) - .collect::>() + self.versions[since_version as usize..last_version].to_vec() } fn add_version(&mut self, version: u64, blob: Blob) -> VersionAdd { @@ -72,7 +71,7 @@ impl Server { self.users .get(username) .map(|user| user.get_versions(since_version)) - .unwrap_or_else(|| vec![]) + .unwrap_or_default() } /// Add a new version. If the given version number is incorrect, this responds with the diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 471437a4f..cfc127a93 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -22,6 +22,7 @@ See the [TaskChampion Book](https://github.com/djmitche/taskchampion/blob/main/d for more information about the design and usage of the tool. */ + mod errors; mod replica; pub mod server; diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 1485a9e0b..0ca2e67f6 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -16,9 +16,9 @@ pub struct Replica { impl Replica { pub fn new(storage: Box) -> Replica { - return Replica { + Replica { taskdb: TaskDB::new(storage), - }; + } } #[cfg(test)] @@ -56,7 +56,7 @@ impl Replica { pub fn all_tasks(&mut self) -> Fallible> { let mut res = HashMap::new(); for (uuid, tm) in self.taskdb.all_tasks()?.drain(..) { - res.insert(uuid.clone(), Task::new(uuid.clone(), tm)); + res.insert(uuid, Task::new(uuid, tm)); } Ok(res) } @@ -71,10 +71,10 @@ impl Replica { pub fn working_set(&mut self) -> Fallible>> { let working_set = self.taskdb.working_set()?; let mut res = Vec::with_capacity(working_set.len()); - for i in 0..working_set.len() { - res.push(match working_set[i] { + for item in working_set.iter() { + res.push(match item { Some(u) => match self.taskdb.get_task(&u)? { - Some(tm) => Some(Task::new(u, tm)), + Some(tm) => Some(Task::new(*u, tm)), None => None, }, None => None, @@ -88,7 +88,7 @@ impl Replica { Ok(self .taskdb .get_task(uuid)? - .map(move |tm| Task::new(uuid.clone(), tm))) + .map(move |tm| Task::new(*uuid, tm))) } /// Get an existing task by its working set index @@ -102,7 +102,7 @@ impl Replica { .map(move |tm| Task::new(uuid, tm))); } } - return Ok(None); + Ok(None) } /// Get the working set index for the given task uuid @@ -126,8 +126,7 @@ impl Replica { if self.taskdb.get_task(&uuid)?.is_some() { return Err(Error::DBError(format!("Task {} already exists", uuid)).into()); } - self.taskdb - .apply(Operation::Create { uuid: uuid.clone() })?; + self.taskdb.apply(Operation::Create { uuid })?; let mut task = Task::new(uuid, TaskMap::new()).into_mut(self); task.set_description(description)?; task.set_status(status)?; @@ -172,7 +171,7 @@ mod tests { let uuid = Uuid::new_v4(); let t = rep - .new_task(uuid.clone(), Status::Pending, "a task".into()) + .new_task(uuid, Status::Pending, "a task".into()) .unwrap(); assert_eq!(t.get_description(), String::from("a task")); assert_eq!(t.get_status(), Status::Pending); @@ -185,7 +184,7 @@ mod tests { let uuid = Uuid::new_v4(); let t = rep - .new_task(uuid.clone(), Status::Pending, "a task".into()) + .new_task(uuid, Status::Pending, "a task".into()) .unwrap(); let mut t = t.into_mut(&mut rep); @@ -211,10 +210,10 @@ mod tests { let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); - rep.new_task(uuid.clone(), Status::Pending, "a task".into()) + rep.new_task(uuid, Status::Pending, "a task".into()) .unwrap(); - rep.delete_task(uuid.clone()).unwrap(); + rep.delete_task(uuid).unwrap(); assert_eq!(rep.get_task(&uuid).unwrap(), None); } @@ -223,7 +222,7 @@ mod tests { let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); - rep.new_task(uuid.clone(), Status::Pending, "another task".into()) + rep.new_task(uuid, Status::Pending, "another task".into()) .unwrap(); let t = rep.get_task(&uuid).unwrap().unwrap(); @@ -247,7 +246,7 @@ mod tests { let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); - rep.new_task(uuid.clone(), Status::Pending, "to-be-pending".into()) + rep.new_task(uuid, Status::Pending, "to-be-pending".into()) .unwrap(); let t = rep.get_working_set_task(1).unwrap().unwrap(); diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 69d2a6f56..332eb57b1 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -123,7 +123,7 @@ impl Task { pub fn into_mut(self, replica: &mut Replica) -> TaskMut { TaskMut { task: self, - replica: replica, + replica, updated_modified: false, } } @@ -170,7 +170,7 @@ impl<'r> TaskMut<'r> { /// new status puts it in that set. pub fn set_status(&mut self, status: Status) -> Fallible<()> { if status == Status::Pending { - let uuid = self.uuid.clone(); + let uuid = self.uuid; self.replica.add_to_working_set(&uuid)?; } self.set_string("status", Some(String::from(status.to_taskmap()))) @@ -190,7 +190,7 @@ impl<'r> TaskMut<'r> { if !self.updated_modified { let now = format!("{}", Utc::now().timestamp()); self.replica - .update_task(self.task.uuid.clone(), "modified", Some(now.clone()))?; + .update_task(self.task.uuid, "modified", Some(now.clone()))?; self.task.taskmap.insert(String::from("modified"), now); self.updated_modified = true; } @@ -200,7 +200,7 @@ impl<'r> TaskMut<'r> { fn set_string(&mut self, property: &str, value: Option) -> Fallible<()> { self.lastmod()?; self.replica - .update_task(self.task.uuid.clone(), property, value.as_ref())?; + .update_task(self.task.uuid, property, value.as_ref())?; if let Some(v) = value { self.task.taskmap.insert(property.to_string(), v); @@ -213,7 +213,7 @@ impl<'r> TaskMut<'r> { fn set_timestamp(&mut self, property: &str, value: Option>) -> Fallible<()> { self.lastmod()?; self.replica.update_task( - self.task.uuid.clone(), + self.task.uuid, property, value.map(|v| format!("{}", v.timestamp())), ) diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index a24ffff0b..f705f3d5a 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -44,32 +44,30 @@ impl TaskDB { fn apply_op(txn: &mut dyn TaskStorageTxn, op: &Operation) -> Fallible<()> { match op { - &Operation::Create { uuid } => { + Operation::Create { uuid } => { // insert if the task does not already exist - if !txn.create_task(uuid)? { + if !txn.create_task(*uuid)? { return Err(Error::DBError(format!("Task {} already exists", uuid)).into()); } } - &Operation::Delete { ref uuid } => { + Operation::Delete { ref uuid } => { if !txn.delete_task(uuid)? { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } } - &Operation::Update { + Operation::Update { ref uuid, ref property, ref value, timestamp: _, } => { // update if this task exists, otherwise ignore - if let Some(task) = txn.get_task(uuid)? { - let mut task = task.clone(); - // TODO: update working_set if this is changing state to or from pending + if let Some(mut task) = txn.get_task(uuid)? { match value { Some(ref val) => task.insert(property.to_string(), val.clone()), None => task.remove(property), }; - txn.set_task(uuid.clone(), task)?; + txn.set_task(*uuid, task)?; } else { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } @@ -80,19 +78,19 @@ impl TaskDB { } /// Get all tasks. - pub fn all_tasks<'a>(&'a mut self) -> Fallible> { + pub fn all_tasks(&mut self) -> Fallible> { let mut txn = self.storage.txn()?; txn.all_tasks() } /// Get the UUIDs of all tasks - pub fn all_task_uuids<'a>(&'a mut self) -> Fallible> { + pub fn all_task_uuids(&mut self) -> Fallible> { let mut txn = self.storage.txn()?; txn.all_task_uuids() } /// Get the working set - pub fn working_set<'a>(&'a mut self) -> Fallible>> { + pub fn working_set(&mut self) -> Fallible>> { let mut txn = self.storage.txn()?; txn.get_working_set() } @@ -124,7 +122,7 @@ impl TaskDB { if let Some(uuid) = elt { if let Some(task) = txn.get_task(&uuid)? { if in_working_set(&task) { - new_ws.push(uuid.clone()); + new_ws.push(uuid); seen.insert(uuid); } } @@ -134,10 +132,8 @@ impl TaskDB { // Now go hunting for tasks that should be in this list but are not, adding them at the // end of the list. for (uuid, task) in txn.all_tasks()? { - if !seen.contains(&uuid) { - if in_working_set(&task) { - new_ws.push(uuid.clone()); - } + if !seen.contains(&uuid) && in_working_set(&task) { + new_ws.push(uuid); } } @@ -186,8 +182,8 @@ impl TaskDB { TaskDB::apply_version(txn.as_mut(), version)?; } - let operations: Vec = txn.operations()?.iter().map(|o| o.clone()).collect(); - if operations.len() == 0 { + let operations: Vec = txn.operations()?.to_vec(); + if operations.is_empty() { // nothing to sync back to the server.. break; } @@ -195,7 +191,7 @@ impl TaskDB { // now make a version of our local changes and push those let new_version = Version { version: txn.base_version()? + 1, - operations: operations, + operations, }; let new_version_str = serde_json::to_string(&new_version).unwrap(); println!("sending version {:?} to server", new_version.version); @@ -457,7 +453,7 @@ mod tests { // add everything to the TaskDB for uuid in &uuids { - db.apply(Operation::Create { uuid: uuid.clone() })?; + db.apply(Operation::Create { uuid: *uuid })?; } for i in &[0usize, 1, 4] { db.apply(Operation::Update { diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs index c438b548e..dbc1b43f7 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/taskstorage/inmemory.rs @@ -1,3 +1,5 @@ +#![allow(clippy::new_without_default)] + use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; use failure::{format_err, Fallible}; use std::collections::hash_map::Entry; @@ -48,7 +50,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { fn create_task(&mut self, uuid: Uuid) -> Fallible { if let ent @ Entry::Vacant(_) = self.mut_data_ref().tasks.entry(uuid) { - ent.or_insert(TaskMap::new()); + ent.or_insert_with(TaskMap::new); Ok(true) } else { Ok(false) @@ -61,11 +63,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { } fn delete_task(&mut self, uuid: &Uuid) -> Fallible { - if let Some(_) = self.mut_data_ref().tasks.remove(uuid) { - Ok(true) - } else { - Ok(false) - } + Ok(self.mut_data_ref().tasks.remove(uuid).is_some()) } fn all_tasks<'a>(&mut self) -> Fallible> { @@ -73,12 +71,12 @@ impl<'t> TaskStorageTxn for Txn<'t> { .data_ref() .tasks .iter() - .map(|(u, t)| (u.clone(), t.clone())) + .map(|(u, t)| (*u, t.clone())) .collect()) } fn all_task_uuids<'a>(&mut self) -> Fallible> { - Ok(self.data_ref().tasks.keys().map(|u| u.clone()).collect()) + Ok(self.data_ref().tasks.keys().copied().collect()) } fn base_version(&mut self) -> Fallible { @@ -110,7 +108,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { let working_set = &mut self.mut_data_ref().working_set; - working_set.push(Some(uuid.clone())); + working_set.push(Some(*uuid)); Ok(working_set.len()) } diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index f7cd0cc4b..6a4e8179a 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -13,21 +13,20 @@ struct Key(uuid::Bytes); impl From<&[u8]> for Key { fn from(bytes: &[u8]) -> Key { - let key = Key(bytes.try_into().unwrap()); - key + Key(bytes.try_into().unwrap()) } } impl From<&Uuid> for Key { fn from(uuid: &Uuid) -> Key { - let key = Key(uuid.as_bytes().clone()); + let key = Key(*uuid.as_bytes()); key } } impl From for Key { fn from(uuid: Uuid) -> Key { - let key = Key(uuid.as_bytes().clone()); + let key = Key(*uuid.as_bytes()); key } } @@ -108,7 +107,7 @@ struct Txn<'t> { impl<'t> Txn<'t> { // get the underlying kv Txn - fn kvtxn<'a>(&mut self) -> &mut kv::Txn<'t> { + fn kvtxn(&mut self) -> &mut kv::Txn<'t> { if let Some(ref mut txn) = self.txn { txn } else { @@ -316,7 +315,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { kvtxn.set( working_set_bucket, next_index.into(), - Msgpack::to_value_buf(uuid.clone())?, + Msgpack::to_value_buf(*uuid)?, )?; kvtxn.set( numbers_bucket, @@ -387,7 +386,7 @@ mod test { let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; - assert!(txn.create_task(uuid.clone())?); + assert!(txn.create_task(uuid)?); txn.commit()?; } { @@ -405,12 +404,12 @@ mod test { let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; - assert!(txn.create_task(uuid.clone())?); + assert!(txn.create_task(uuid)?); txn.commit()?; } { let mut txn = storage.txn()?; - assert!(!txn.create_task(uuid.clone())?); + assert!(!txn.create_task(uuid)?); txn.commit()?; } Ok(()) @@ -436,10 +435,7 @@ mod test { let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; - txn.set_task( - uuid.clone(), - taskmap_with(vec![("k".to_string(), "v".to_string())]), - )?; + txn.set_task(uuid, taskmap_with(vec![("k".to_string(), "v".to_string())]))?; txn.commit()?; } { @@ -472,7 +468,7 @@ mod test { let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; - assert!(txn.create_task(uuid.clone())?); + assert!(txn.create_task(uuid)?); txn.commit()?; } { diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index 8c37e934b..90ec0460e 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -52,10 +52,10 @@ pub trait TaskStorageTxn { fn delete_task(&mut self, uuid: &Uuid) -> Fallible; /// Get the uuids and bodies of all tasks in the storage, in undefined order. - fn all_tasks<'a>(&mut self) -> Fallible>; + fn all_tasks(&mut self) -> Fallible>; /// Get the uuids of all tasks in the storage, in undefined order. - fn all_task_uuids<'a>(&mut self) -> Fallible>; + fn all_task_uuids(&mut self) -> Fallible>; /// Get the current base_version for this storage -- the last version synced from the server. fn base_version(&mut self) -> Fallible; @@ -65,7 +65,7 @@ pub trait TaskStorageTxn { /// Get the current set of outstanding operations (operations that have not been sync'd to the /// server yet) - fn operations<'a>(&mut self) -> Fallible>; + fn operations(&mut self) -> Fallible>; /// Add an operation to the end of the list of operations in the storage. Note that this /// merely *stores* the operation; it is up to the TaskDB to apply it. diff --git a/taskchampion/src/taskstorage/operation.rs b/taskchampion/src/taskstorage/operation.rs index 52bc55d56..665a151ef 100644 --- a/taskchampion/src/taskstorage/operation.rs +++ b/taskchampion/src/taskstorage/operation.rs @@ -109,12 +109,9 @@ impl Operation { } else if timestamp1 < timestamp2 { // prefer the later modification (None, Some(operation2)) - } else if timestamp1 > timestamp2 { - // prefer the later modification - //(Some(operation1), None) - (Some(operation1), None) } else { - // arbitrarily resolve in favor of the first operation + // prefer the later modification or, if the modifications are the same, + // just choose one of them (Some(operation1), None) } } From fc4fcc9e5d856bec2c6283344640605c981d79c9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Nov 2020 13:00:42 -0500 Subject: [PATCH 072/548] Remove `remove_from_working_set` method. Items are only removed from the working set when it is rebuilt, so this method is unnecessary. --- taskchampion/src/taskstorage/inmemory.rs | 62 +------------------ taskchampion/src/taskstorage/kv.rs | 77 +----------------------- taskchampion/src/taskstorage/mod.rs | 4 +- 3 files changed, 3 insertions(+), 140 deletions(-) diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs index dbc1b43f7..8fb3da3a1 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/taskstorage/inmemory.rs @@ -1,7 +1,7 @@ #![allow(clippy::new_without_default)] use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; -use failure::{format_err, Fallible}; +use failure::Fallible; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; @@ -112,15 +112,6 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(working_set.len()) } - fn remove_from_working_set(&mut self, index: usize) -> Fallible<()> { - let working_set = &mut self.mut_data_ref().working_set; - if index >= working_set.len() || working_set[index].is_none() { - return Err(format_err!("No task found with index {}", index)); - } - working_set[index] = None; - Ok(()) - } - fn clear_working_set(&mut self) -> Fallible<()> { self.mut_data_ref().working_set = vec![None]; Ok(()) @@ -206,57 +197,6 @@ mod test { Ok(()) } - #[test] - fn add_and_remove_from_working_set_holes() -> Fallible<()> { - let mut storage = InMemoryStorage::new(); - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(&uuid1)?; - txn.add_to_working_set(&uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - txn.remove_from_working_set(1)?; - txn.add_to_working_set(&uuid1)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, None, Some(uuid2), Some(uuid1)]); - } - - Ok(()) - } - - #[test] - fn remove_working_set_doesnt_exist() -> Fallible<()> { - let mut storage = InMemoryStorage::new(); - let uuid1 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(&uuid1)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let res = txn.remove_from_working_set(0); - assert!(res.is_err()); - let res = txn.remove_from_working_set(2); - assert!(res.is_err()); - } - - Ok(()) - } - #[test] fn clear_working_set() -> Fallible<()> { let mut storage = InMemoryStorage::new(); diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index 6a4e8179a..18e18fb19 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -1,5 +1,5 @@ use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; -use failure::{format_err, Fallible}; +use failure::Fallible; use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; use std::convert::TryInto; @@ -325,28 +325,6 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(next_index as usize) } - fn remove_from_working_set(&mut self, index: usize) -> Fallible<()> { - let index = index as u64; - let working_set_bucket = self.working_set_bucket(); - let numbers_bucket = self.numbers_bucket(); - let kvtxn = self.kvtxn(); - - let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { - Ok(buf) => buf.inner()?.to_serde(), - Err(Error::NotFound) => 1, - Err(e) => return Err(e.into()), - }; - if index == 0 || index >= next_index { - return Err(format_err!("No task found with index {}", index)); - } - - match kvtxn.del(working_set_bucket, index.into()) { - Err(Error::NotFound) => Err(format_err!("No task found with index {}", index)), - Err(e) => Err(e.into()), - Ok(_) => Ok(()), - } - } - fn clear_working_set(&mut self) -> Fallible<()> { let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); @@ -672,59 +650,6 @@ mod test { Ok(()) } - #[test] - fn add_and_remove_from_working_set_holes() -> Fallible<()> { - let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(&uuid1)?; - txn.add_to_working_set(&uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - txn.remove_from_working_set(1)?; - txn.add_to_working_set(&uuid1)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, None, Some(uuid2), Some(uuid1)]); - } - - Ok(()) - } - - #[test] - fn remove_working_set_doesnt_exist() -> Fallible<()> { - let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(&uuid1)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let res = txn.remove_from_working_set(0); - assert!(res.is_err()); - let res = txn.remove_from_working_set(2); - assert!(res.is_err()); - } - - Ok(()) - } - #[test] fn clear_working_set() -> Fallible<()> { let tmp_dir = TempDir::new("test")?; diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index 90ec0460e..8f25781c5 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -82,10 +82,8 @@ pub trait TaskStorageTxn { /// than the highest used index. fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible; - /// Remove a task from the working set. Other tasks' indexes are not affected. - fn remove_from_working_set(&mut self, index: usize) -> Fallible<()>; - /// Clear all tasks from the working set in preparation for a garbage-collection operation. + /// Note that this is the only way items are removed from the set. fn clear_working_set(&mut self) -> Fallible<()>; /// Commit any changes made in the transaction. It is an error to call this more than From 549d3b9f6d7d6c06518735667c2e78e8ef41dec2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Nov 2020 18:04:49 -0500 Subject: [PATCH 073/548] refactor taskchampion::server into a module with submodules --- taskchampion/src/lib.rs | 3 --- taskchampion/src/server/mod.rs | 6 ++++++ .../src/{testing/testserver.rs => server/test.rs} | 0 taskchampion/src/{server.rs => server/types.rs} | 8 ++++++-- taskchampion/src/taskdb.rs | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 taskchampion/src/server/mod.rs rename taskchampion/src/{testing/testserver.rs => server/test.rs} (100%) rename taskchampion/src/{server.rs => server/types.rs} (69%) diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index cfc127a93..98ed5f8c3 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -37,6 +37,3 @@ pub use task::{Task, TaskMut}; /// Re-exported type from the `uuid` crate, for ease of compatibility for consumers of this crate. pub use uuid::Uuid; - -#[cfg(test)] -pub(crate) mod testing; diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs new file mode 100644 index 000000000..e73c7f74a --- /dev/null +++ b/taskchampion/src/server/mod.rs @@ -0,0 +1,6 @@ +#[cfg(test)] +pub(crate) mod test; + +mod types; + +pub use types::{Blob, Server, VersionAdd}; diff --git a/taskchampion/src/testing/testserver.rs b/taskchampion/src/server/test.rs similarity index 100% rename from taskchampion/src/testing/testserver.rs rename to taskchampion/src/server/test.rs diff --git a/taskchampion/src/server.rs b/taskchampion/src/server/types.rs similarity index 69% rename from taskchampion/src/server.rs rename to taskchampion/src/server/types.rs index 233838fa6..cae8029a8 100644 --- a/taskchampion/src/server.rs +++ b/taskchampion/src/server/types.rs @@ -1,9 +1,12 @@ +/// A Blob is a hunk of encoded data that is sent to the server. The server does not interpret +/// this data at all. pub type Blob = Vec; +/// VersionAdd is the response type from [`crate:server::Server::add_version`]. pub enum VersionAdd { - // OK, version added + /// OK, version added Ok, - // Rejected, must be based on the the given version + /// Rejected, must be based on the the given version ExpectedVersion(u64), } @@ -18,3 +21,4 @@ pub trait Server { fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob); } + diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index f705f3d5a..542ca5cc3 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -296,8 +296,8 @@ impl TaskDB { #[cfg(test)] mod tests { use super::*; + use crate::server::test::TestServer; use crate::taskstorage::InMemoryStorage; - use crate::testing::testserver::TestServer; use chrono::Utc; use proptest::prelude::*; use std::collections::HashMap; From 75edd2773fb4cd3e8909004466913def938cfd7e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Nov 2020 18:12:48 -0500 Subject: [PATCH 074/548] make server operations fallible --- taskchampion/src/server/test.rs | 35 +++++++++++++++++--------------- taskchampion/src/server/types.rs | 10 +++++---- taskchampion/src/taskdb.rs | 4 ++-- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/taskchampion/src/server/test.rs b/taskchampion/src/server/test.rs index b81063b1b..e9a85f17d 100644 --- a/taskchampion/src/server/test.rs +++ b/taskchampion/src/server/test.rs @@ -1,4 +1,5 @@ use crate::server::{Blob, Server, VersionAdd}; +use failure::Fallible; use std::collections::HashMap; pub(crate) struct TestServer { @@ -27,21 +28,22 @@ impl TestServer { impl Server for TestServer { /// Get a vector of all versions after `since_version` - fn get_versions(&self, username: &str, since_version: u64) -> Vec { - self.users - .get(username) - .map(|user| user.get_versions(since_version)) - .unwrap_or_else(|| vec![]) + fn get_versions(&self, username: &str, since_version: u64) -> Fallible> { + if let Some(user) = self.users.get(username) { + user.get_versions(since_version) + } else { + Ok(vec![]) + } } /// Add a new version. If the given version number is incorrect, this responds with the /// appropriate version and expects the caller to try again. - fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> VersionAdd { + fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> Fallible { self.get_user_mut(username).add_version(version, blob) } - fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) { - self.get_user_mut(username).add_snapshot(version, blob); + fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) -> Fallible<()> { + self.get_user_mut(username).add_snapshot(version, blob) } } @@ -53,29 +55,30 @@ impl User { } } - fn get_versions(&self, since_version: u64) -> Vec { + fn get_versions(&self, since_version: u64) -> Fallible> { let last_version = self.versions.len(); if last_version == since_version as usize { - return vec![]; + return Ok(vec![]); } - self.versions[since_version as usize..last_version] + Ok(self.versions[since_version as usize..last_version] .iter() .map(|r| r.clone()) - .collect::>() + .collect::>()) } - fn add_version(&mut self, version: u64, blob: Blob) -> VersionAdd { + fn add_version(&mut self, version: u64, blob: Blob) -> Fallible { // of by one here: client wants to send version 1 first let expected_version = self.versions.len() as u64 + 1; if version != expected_version { - return VersionAdd::ExpectedVersion(expected_version); + return Ok(VersionAdd::ExpectedVersion(expected_version)); } self.versions.push(blob); - VersionAdd::Ok + Ok(VersionAdd::Ok) } - fn add_snapshot(&mut self, version: u64, blob: Blob) { + fn add_snapshot(&mut self, version: u64, blob: Blob) -> Fallible<()> { self.snapshots.insert(version, blob); + Ok(()) } } diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index cae8029a8..567997cac 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -1,3 +1,5 @@ +use failure::Fallible; + /// A Blob is a hunk of encoded data that is sent to the server. The server does not interpret /// this data at all. pub type Blob = Vec; @@ -13,12 +15,12 @@ pub enum VersionAdd { /// A value implementing this trait can act as a server against which a replica can sync. pub trait Server { /// Get a vector of all versions after `since_version` - fn get_versions(&self, username: &str, since_version: u64) -> Vec; + fn get_versions(&self, username: &str, since_version: u64) -> Fallible>; /// Add a new version. If the given version number is incorrect, this responds with the /// appropriate version and expects the caller to try again. - fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> VersionAdd; + fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> Fallible; - fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob); + /// TODO: undefined yet + fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) -> Fallible<()>; } - diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 542ca5cc3..65e321888 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -172,7 +172,7 @@ impl TaskDB { // replicas trying to sync to the same server) loop { // first pull changes and "rebase" on top of them - let new_versions = server.get_versions(username, txn.base_version()?); + let new_versions = server.get_versions(username, txn.base_version()?)?; for version_blob in new_versions { let version_str = str::from_utf8(&version_blob).unwrap(); let version: Version = serde_json::from_str(version_str).unwrap(); @@ -196,7 +196,7 @@ impl TaskDB { let new_version_str = serde_json::to_string(&new_version).unwrap(); println!("sending version {:?} to server", new_version.version); if let VersionAdd::Ok = - server.add_version(username, new_version.version, new_version_str.into()) + server.add_version(username, new_version.version, new_version_str.into())? { txn.set_base_version(new_version.version)?; txn.set_operations(vec![])?; From e92fc0628b613005402799d532a3c3b1748794f8 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 25 Nov 2020 16:39:05 -0500 Subject: [PATCH 075/548] add signing support --- Cargo.lock | 122 +++++++++++++++++++++++++++++ taskchampion/Cargo.toml | 1 + taskchampion/src/server/mod.rs | 1 + taskchampion/src/server/signing.rs | 87 ++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 taskchampion/src/server/signing.rs diff --git a/Cargo.lock b/Cargo.lock index 15ecf0a9f..c5ee93f04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + [[package]] name = "byteorder" version = "1.3.4" @@ -350,6 +356,15 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +[[package]] +name = "js-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "kv" version = "0.10.0" @@ -398,6 +413,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if 0.1.10", +] + [[package]] name = "memchr" version = "2.3.4" @@ -445,6 +469,12 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" +[[package]] +name = "once_cell" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" + [[package]] name = "pkg-config" version = "0.3.19" @@ -766,6 +796,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "ring" +version = "0.16.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5911690c9b773bab7e657471afc207f3827b249a657241327e3544d79bcabdd" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rmp" version = "0.8.9" @@ -854,6 +899,12 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.8.0" @@ -896,6 +947,7 @@ dependencies = [ "kv", "lmdb-rkv", "proptest", + "ring", "serde", "serde_json", "tempdir", @@ -1025,6 +1077,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "uuid" version = "0.8.1" @@ -1062,6 +1120,70 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasm-bindgen" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" +dependencies = [ + "cfg-if 0.1.10", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" + +[[package]] +name = "web-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 1f6681454..509a7db8b 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -12,6 +12,7 @@ chrono = { version = "0.4.10", features = ["serde"] } failure = {version = "0.1.5", features = ["derive"] } kv = {version = "0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "0.12.3"} +ring = { version = "0.16.17", features = ["std"] } [dev-dependencies] proptest = "0.9.4" diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index e73c7f74a..9a6214425 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -1,6 +1,7 @@ #[cfg(test)] pub(crate) mod test; +mod signing; mod types; pub use types::{Blob, Server, VersionAdd}; diff --git a/taskchampion/src/server/signing.rs b/taskchampion/src/server/signing.rs new file mode 100644 index 000000000..e1e49501a --- /dev/null +++ b/taskchampion/src/server/signing.rs @@ -0,0 +1,87 @@ +#![allow(dead_code)] // TODO: temporary until this module is used +//! This is a general wrapper around an asymmetric-key signature system. + +use failure::Fallible; +use ring::{ + rand, + signature::{Ed25519KeyPair, KeyPair, Signature, UnparsedPublicKey, ED25519}, +}; + +type PublicKey = Vec; +type PrivateKey = Vec; + +/// Generate a pair of (public, private) key material (in fact the private key is a keypair) +pub fn new_keypair() -> Fallible<(PublicKey, PrivateKey)> { + let rng = rand::SystemRandom::new(); + let key_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng)?; + let key_pair = Ed25519KeyPair::from_pkcs8(key_pkcs8.as_ref())?; + let pub_key = key_pair.public_key(); + Ok(( + pub_key.as_ref().to_vec() as PublicKey, + key_pkcs8.as_ref().to_vec() as PrivateKey, + )) +} + +pub struct Signer { + key_pair: Ed25519KeyPair, +} + +impl Signer { + /// Create a new signer, given a pkcs#8 v2 document containing the keypair. + fn new(priv_key: PrivateKey) -> Fallible { + Ok(Self { + key_pair: Ed25519KeyPair::from_pkcs8(&priv_key)?, + }) + } + + pub fn sign>(&self, message: B) -> Fallible { + Ok(self.key_pair.sign(message.as_ref())) + } +} + +pub struct Verifier { + pub_key: PublicKey, +} + +impl Verifier { + fn new(pub_key: PublicKey) -> Fallible { + Ok(Self { pub_key }) + } + + pub fn verify, B2: AsRef<[u8]>>( + &self, + message: B1, + signature: B2, + ) -> Fallible<()> { + let pub_key = UnparsedPublicKey::new(&ED25519, &self.pub_key); + Ok(pub_key.verify(message.as_ref(), signature.as_ref())?) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_verify_ok() -> Fallible<()> { + let (public, private) = new_keypair()?; + let signer = Signer::new(private)?; + let verifier = Verifier::new(public)?; + + let message = b"Hello, world"; + let signature = signer.sign(message)?; + verifier.verify(message, signature) + } + + #[test] + fn test_verify_bad_message() -> Fallible<()> { + let (public, private) = new_keypair()?; + let signer = Signer::new(private)?; + let verifier = Verifier::new(public)?; + + let message = b"Hello, world"; + let signature = signer.sign(message)?; + assert!(verifier.verify(b"Hello, cruel world", signature).is_err()); + Ok(()) + } +} From a81c84d7c75a3ab83c29a5c0044835d3bd84b00b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 25 Nov 2020 17:52:47 -0500 Subject: [PATCH 076/548] refactor sync to new model --- taskchampion/src/replica.rs | 4 +- taskchampion/src/server/mod.rs | 2 +- taskchampion/src/server/test.rs | 120 +++++++++++------------ taskchampion/src/server/types.rs | 52 +++++++--- taskchampion/src/taskdb.rs | 98 ++++++++++-------- taskchampion/src/taskstorage/inmemory.rs | 14 +-- taskchampion/src/taskstorage/kv.rs | 36 ++++--- taskchampion/src/taskstorage/mod.rs | 10 +- 8 files changed, 193 insertions(+), 143 deletions(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 0ca2e67f6..f141e2348 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -145,8 +145,8 @@ impl Replica { } /// Synchronize this replica against the given server. - pub fn sync(&mut self, username: &str, server: &mut dyn Server) -> Fallible<()> { - self.taskdb.sync(username, server) + pub fn sync(&mut self, server: &mut dyn Server) -> Fallible<()> { + self.taskdb.sync(server) } /// Perform "garbage collection" on this replica. In particular, this renumbers the working diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 9a6214425..06009b127 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -4,4 +4,4 @@ pub(crate) mod test; mod signing; mod types; -pub use types::{Blob, Server, VersionAdd}; +pub use types::*; diff --git a/taskchampion/src/server/test.rs b/taskchampion/src/server/test.rs index e9a85f17d..3175c2aa5 100644 --- a/taskchampion/src/server/test.rs +++ b/taskchampion/src/server/test.rs @@ -1,84 +1,78 @@ -use crate::server::{Blob, Server, VersionAdd}; +use crate::server::{ + AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, +}; use failure::Fallible; use std::collections::HashMap; +use uuid::Uuid; -pub(crate) struct TestServer { - users: HashMap, +struct Version { + version_id: VersionId, + parent_version_id: VersionId, + history_segment: HistorySegment, } -struct User { - // versions, indexed at v-1 - versions: Vec, - snapshots: HashMap, +pub(crate) struct TestServer { + latest_version_id: VersionId, + // NOTE: indexed by parent_version_id! + versions: HashMap, } impl TestServer { + /// A test server has no notion of clients, signatures, encryption, etc. pub fn new() -> TestServer { TestServer { - users: HashMap::new(), + latest_version_id: NO_VERSION_ID, + versions: HashMap::new(), } } - - fn get_user_mut(&mut self, username: &str) -> &mut User { - self.users - .entry(username.to_string()) - .or_insert_with(User::new) - } } impl Server for TestServer { - /// Get a vector of all versions after `since_version` - fn get_versions(&self, username: &str, since_version: u64) -> Fallible> { - if let Some(user) = self.users.get(username) { - user.get_versions(since_version) - } else { - Ok(vec![]) - } - } - /// Add a new version. If the given version number is incorrect, this responds with the /// appropriate version and expects the caller to try again. - fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> Fallible { - self.get_user_mut(username).add_version(version, blob) + fn add_version( + &mut self, + parent_version_id: VersionId, + history_segment: HistorySegment, + ) -> Fallible { + // no client lookup + // no signature validation + + // check the parent_version_id for linearity + if self.latest_version_id != NO_VERSION_ID { + if parent_version_id != self.latest_version_id { + return Ok(AddVersionResult::ExpectedParentVersion( + self.latest_version_id, + )); + } + } + + // invent a new ID for this version + let version_id = Uuid::new_v4(); + + self.versions.insert( + parent_version_id, + Version { + version_id, + parent_version_id, + history_segment, + }, + ); + self.latest_version_id = version_id; + + Ok(AddVersionResult::Ok(version_id)) } - fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) -> Fallible<()> { - self.get_user_mut(username).add_snapshot(version, blob) - } -} - -impl User { - fn new() -> User { - User { - versions: vec![], - snapshots: HashMap::new(), - } - } - - fn get_versions(&self, since_version: u64) -> Fallible> { - let last_version = self.versions.len(); - if last_version == since_version as usize { - return Ok(vec![]); - } - Ok(self.versions[since_version as usize..last_version] - .iter() - .map(|r| r.clone()) - .collect::>()) - } - - fn add_version(&mut self, version: u64, blob: Blob) -> Fallible { - // of by one here: client wants to send version 1 first - let expected_version = self.versions.len() as u64 + 1; - if version != expected_version { - return Ok(VersionAdd::ExpectedVersion(expected_version)); - } - self.versions.push(blob); - - Ok(VersionAdd::Ok) - } - - fn add_snapshot(&mut self, version: u64, blob: Blob) -> Fallible<()> { - self.snapshots.insert(version, blob); - Ok(()) + /// Get a vector of all versions after `since_version` + fn get_child_version(&self, parent_version_id: VersionId) -> Fallible { + if let Some(version) = self.versions.get(&parent_version_id) { + Ok(GetVersionResult::Version { + version_id: version.version_id, + parent_version_id: version.parent_version_id, + history_segment: version.history_segment.clone(), + }) + } else { + Ok(GetVersionResult::NoSuchVersion) + } } } diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index 567997cac..9d95f588b 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -1,26 +1,46 @@ use failure::Fallible; +use uuid::Uuid; -/// A Blob is a hunk of encoded data that is sent to the server. The server does not interpret -/// this data at all. -pub type Blob = Vec; +/// Versions are referred to with sha2 hashes. +pub type VersionId = Uuid; + +/// The distinguished value for "no version" +pub const NO_VERSION_ID: VersionId = Uuid::nil(); + +/// A segment in the history of this task database, in the form of a sequence of operations. This +/// data is pre-encoded, and from the protocol level appears as a sequence of bytes. +pub type HistorySegment = Vec; /// VersionAdd is the response type from [`crate:server::Server::add_version`]. -pub enum VersionAdd { - /// OK, version added - Ok, - /// Rejected, must be based on the the given version - ExpectedVersion(u64), +pub enum AddVersionResult { + /// OK, version added with the given ID + Ok(VersionId), + /// Rejected; expected a version with the given parent version + ExpectedParentVersion(VersionId), +} + +/// A version as downloaded from the server +pub enum GetVersionResult { + /// No such version exists + NoSuchVersion, + + /// The requested version + Version { + version_id: VersionId, + parent_version_id: VersionId, + history_segment: HistorySegment, + }, } /// A value implementing this trait can act as a server against which a replica can sync. pub trait Server { - /// Get a vector of all versions after `since_version` - fn get_versions(&self, username: &str, since_version: u64) -> Fallible>; + /// Add a new version. + fn add_version( + &mut self, + parent_version_id: VersionId, + history_segment: HistorySegment, + ) -> Fallible; - /// Add a new version. If the given version number is incorrect, this responds with the - /// appropriate version and expects the caller to try again. - fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> Fallible; - - /// TODO: undefined yet - fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) -> Fallible<()>; + /// Get the version with the given parent VersionId + fn get_child_version(&self, parent_version_id: VersionId) -> Fallible; } diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 65e321888..86523717d 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -1,5 +1,5 @@ use crate::errors::Error; -use crate::server::{Server, VersionAdd}; +use crate::server::{AddVersionResult, GetVersionResult, Server}; use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; use failure::Fallible; use serde::{Deserialize, Serialize}; @@ -13,7 +13,6 @@ pub struct TaskDB { #[derive(Serialize, Deserialize, Debug)] struct Version { - version: u64, operations: Vec, } @@ -165,21 +164,34 @@ impl TaskDB { } /// Sync to the given server, pulling remote changes and pushing local changes. - pub fn sync(&mut self, username: &str, server: &mut dyn Server) -> Fallible<()> { + pub fn sync(&mut self, server: &mut dyn Server) -> Fallible<()> { let mut txn = self.storage.txn()?; // retry synchronizing until the server accepts our version (this allows for races between // replicas trying to sync to the same server) loop { - // first pull changes and "rebase" on top of them - let new_versions = server.get_versions(username, txn.base_version()?)?; - for version_blob in new_versions { - let version_str = str::from_utf8(&version_blob).unwrap(); - let version: Version = serde_json::from_str(version_str).unwrap(); - assert_eq!(version.version, txn.base_version()? + 1); - println!("applying version {:?} from server", version.version); + let mut base_version_id = txn.base_version()?; - TaskDB::apply_version(txn.as_mut(), version)?; + // first pull changes and "rebase" on top of them + loop { + if let GetVersionResult::Version { + version_id, + history_segment, + .. + } = server.get_child_version(base_version_id)? + { + let version_str = str::from_utf8(&history_segment).unwrap(); + let version: Version = serde_json::from_str(version_str).unwrap(); + println!("applying version {:?} from server", version_id); + + // apply this verison and update base_version in storage + TaskDB::apply_version(txn.as_mut(), version)?; + txn.set_base_version(version_id)?; + base_version_id = version_id; + } else { + // at the moment, no more child versions, so we can try adding our own + break; + } } let operations: Vec = txn.operations()?.to_vec(); @@ -189,18 +201,23 @@ impl TaskDB { } // now make a version of our local changes and push those - let new_version = Version { - version: txn.base_version()? + 1, - operations, - }; - let new_version_str = serde_json::to_string(&new_version).unwrap(); - println!("sending version {:?} to server", new_version.version); - if let VersionAdd::Ok = - server.add_version(username, new_version.version, new_version_str.into())? - { - txn.set_base_version(new_version.version)?; - txn.set_operations(vec![])?; - break; + let new_version = Version { operations }; + let history_segment = serde_json::to_string(&new_version).unwrap().into(); + println!("sending new version to server"); + match server.add_version(base_version_id, history_segment)? { + AddVersionResult::Ok(new_version_id) => { + println!("version {:?} received by server", new_version_id); + txn.set_base_version(new_version_id)?; + txn.set_operations(vec![])?; + break; + } + AddVersionResult::ExpectedParentVersion(parent_version_id) => { + println!( + "new version rejected; must be based on {:?}", + parent_version_id + ); + // ..continue the outer loop + } } } @@ -256,7 +273,6 @@ impl TaskDB { } local_operations = new_local_ops; } - txn.set_base_version(version.version)?; txn.set_operations(local_operations)?; Ok(()) } @@ -518,10 +534,10 @@ mod tests { let mut server = TestServer::new(); let mut db1 = newdb(); - db1.sync("me", &mut server).unwrap(); + db1.sync(&mut server).unwrap(); let mut db2 = newdb(); - db2.sync("me", &mut server).unwrap(); + db2.sync(&mut server).unwrap(); // make some changes in parallel to db1 and db2.. let uuid1 = Uuid::new_v4(); @@ -545,9 +561,9 @@ mod tests { .unwrap(); // and synchronize those around - db1.sync("me", &mut server).unwrap(); - db2.sync("me", &mut server).unwrap(); - db1.sync("me", &mut server).unwrap(); + db1.sync(&mut server).unwrap(); + db2.sync(&mut server).unwrap(); + db1.sync(&mut server).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // now make updates to the same task on both sides @@ -567,9 +583,9 @@ mod tests { .unwrap(); // and synchronize those around - db1.sync("me", &mut server).unwrap(); - db2.sync("me", &mut server).unwrap(); - db1.sync("me", &mut server).unwrap(); + db1.sync(&mut server).unwrap(); + db2.sync(&mut server).unwrap(); + db1.sync(&mut server).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); } @@ -578,10 +594,10 @@ mod tests { let mut server = TestServer::new(); let mut db1 = newdb(); - db1.sync("me", &mut server).unwrap(); + db1.sync(&mut server).unwrap(); let mut db2 = newdb(); - db2.sync("me", &mut server).unwrap(); + db2.sync(&mut server).unwrap(); // create and update a task.. let uuid = Uuid::new_v4(); @@ -595,9 +611,9 @@ mod tests { .unwrap(); // and synchronize those around - db1.sync("me", &mut server).unwrap(); - db2.sync("me", &mut server).unwrap(); - db1.sync("me", &mut server).unwrap(); + db1.sync(&mut server).unwrap(); + db2.sync(&mut server).unwrap(); + db1.sync(&mut server).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // delete and re-create the task on db1 @@ -620,9 +636,9 @@ mod tests { }) .unwrap(); - db1.sync("me", &mut server).unwrap(); - db2.sync("me", &mut server).unwrap(); - db1.sync("me", &mut server).unwrap(); + db1.sync(&mut server).unwrap(); + db2.sync(&mut server).unwrap(); + db1.sync(&mut server).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); } @@ -678,7 +694,7 @@ mod tests { println!(" {:?} (ignored)", e); } }, - Action::Sync => db.sync("me", &mut server).unwrap(), + Action::Sync => db.sync(&mut server).unwrap(), } } diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs index 8fb3da3a1..66a871ed8 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/taskstorage/inmemory.rs @@ -1,6 +1,8 @@ #![allow(clippy::new_without_default)] -use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; +use crate::taskstorage::{ + Operation, TaskMap, TaskStorage, TaskStorageTxn, VersionId, DEFAULT_BASE_VERSION, +}; use failure::Fallible; use std::collections::hash_map::Entry; use std::collections::HashMap; @@ -9,7 +11,7 @@ use uuid::Uuid; #[derive(PartialEq, Debug, Clone)] struct Data { tasks: HashMap, - base_version: u64, + base_version: VersionId, operations: Vec, working_set: Vec>, } @@ -79,11 +81,11 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(self.data_ref().tasks.keys().copied().collect()) } - fn base_version(&mut self) -> Fallible { - Ok(self.data_ref().base_version) + fn base_version(&mut self) -> Fallible { + Ok(self.data_ref().base_version.clone()) } - fn set_base_version(&mut self, version: u64) -> Fallible<()> { + fn set_base_version(&mut self, version: VersionId) -> Fallible<()> { self.mut_data_ref().base_version = version; Ok(()) } @@ -138,7 +140,7 @@ impl InMemoryStorage { InMemoryStorage { data: Data { tasks: HashMap::new(), - base_version: 0, + base_version: DEFAULT_BASE_VERSION.into(), operations: vec![], working_set: vec![None], }, diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index 18e18fb19..52947a5c0 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -1,4 +1,6 @@ -use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; +use crate::taskstorage::{ + Operation, TaskMap, TaskStorage, TaskStorageTxn, VersionId, DEFAULT_BASE_VERSION, +}; use failure::Fallible; use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; @@ -48,6 +50,7 @@ pub struct KVStorage<'t> { store: Store, tasks_bucket: Bucket<'t, Key, ValueBuf>>, numbers_bucket: Bucket<'t, Integer, ValueBuf>>, + uuids_bucket: Bucket<'t, Integer, ValueBuf>>, operations_bucket: Bucket<'t, Integer, ValueBuf>>, working_set_bucket: Bucket<'t, Integer, ValueBuf>>, } @@ -61,6 +64,7 @@ impl<'t> KVStorage<'t> { let mut config = Config::default(directory); config.bucket("tasks", None); config.bucket("numbers", None); + config.bucket("uuids", None); config.bucket("operations", None); config.bucket("working_set", None); let store = Store::new(config)?; @@ -71,6 +75,9 @@ impl<'t> KVStorage<'t> { // this bucket contains various u64s, indexed by constants above let numbers_bucket = store.int_bucket::>>(Some("numbers"))?; + // this bucket contains various Uuids, indexed by constants above + let uuids_bucket = store.int_bucket::>>(Some("uuids"))?; + // this bucket contains operations, numbered consecutively; the NEXT_OPERATION number gives // the index of the next operation to insert let operations_bucket = @@ -85,6 +92,7 @@ impl<'t> KVStorage<'t> { store, tasks_bucket, numbers_bucket, + uuids_bucket, operations_bucket, working_set_bucket, }) @@ -122,6 +130,9 @@ impl<'t> Txn<'t> { fn numbers_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { &self.storage.numbers_bucket } + fn uuids_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { + &self.storage.uuids_bucket + } fn operations_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { &self.storage.operations_bucket } @@ -193,26 +204,26 @@ impl<'t> TaskStorageTxn for Txn<'t> { .collect()) } - fn base_version(&mut self) -> Fallible { - let bucket = self.numbers_bucket(); + fn base_version(&mut self) -> Fallible { + let bucket = self.uuids_bucket(); let base_version = match self.kvtxn().get(bucket, BASE_VERSION.into()) { Ok(buf) => buf, - Err(Error::NotFound) => return Ok(0), + Err(Error::NotFound) => return Ok(DEFAULT_BASE_VERSION.into()), Err(e) => return Err(e.into()), } .inner()? .to_serde(); - Ok(base_version) + Ok(base_version as VersionId) } - fn set_base_version(&mut self, version: u64) -> Fallible<()> { - let numbers_bucket = self.numbers_bucket(); + fn set_base_version(&mut self, version: VersionId) -> Fallible<()> { + let uuids_bucket = self.uuids_bucket(); let kvtxn = self.kvtxn(); kvtxn.set( - numbers_bucket, + uuids_bucket, BASE_VERSION.into(), - Msgpack::to_value_buf(version)?, + Msgpack::to_value_buf(version as Uuid)?, )?; Ok(()) } @@ -528,7 +539,7 @@ mod test { let mut storage = KVStorage::new(&tmp_dir.path())?; { let mut txn = storage.txn()?; - assert_eq!(txn.base_version()?, 0); + assert_eq!(txn.base_version()?, DEFAULT_BASE_VERSION); } Ok(()) } @@ -537,14 +548,15 @@ mod test { fn test_base_version_setting() -> Fallible<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; + let u = Uuid::new_v4(); { let mut txn = storage.txn()?; - txn.set_base_version(3)?; + txn.set_base_version(u)?; txn.commit()?; } { let mut txn = storage.txn()?; - assert_eq!(txn.base_version()?, 3); + assert_eq!(txn.base_version()?, u); } Ok(()) } diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index 8f25781c5..4ddd7df75 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -23,6 +23,12 @@ fn taskmap_with(mut properties: Vec<(String, String)>) -> TaskMap { rv } +/// The type of VersionIds +pub use crate::server::VersionId; + +/// The default for base_version. +pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NO_VERSION_ID; + /// A TaskStorage transaction, in which storage operations are performed. /// /// # Concurrency @@ -58,10 +64,10 @@ pub trait TaskStorageTxn { fn all_task_uuids(&mut self) -> Fallible>; /// Get the current base_version for this storage -- the last version synced from the server. - fn base_version(&mut self) -> Fallible; + fn base_version(&mut self) -> Fallible; /// Set the current base_version for this storage. - fn set_base_version(&mut self, version: u64) -> Fallible<()>; + fn set_base_version(&mut self, version: VersionId) -> Fallible<()>; /// Get the current set of outstanding operations (operations that have not been sync'd to the /// server yet) From 8f7e2e279005aa953006a72c3ce2ab88d934a590 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 25 Nov 2020 18:06:56 -0500 Subject: [PATCH 077/548] add a 'task sync' command using a copy of the test server --- cli/src/cmd/mod.rs | 26 ++--------- cli/src/cmd/shared.rs | 31 ++++++++++++- cli/src/cmd/sync.rs | 39 ++++++++++++++++ taskchampion/src/server/local.rs | 79 ++++++++++++++++++++++++++++++++ taskchampion/src/server/mod.rs | 2 + 5 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 cli/src/cmd/sync.rs create mode 100644 taskchampion/src/server/local.rs diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index e7d27255b..3e55d0475 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -1,7 +1,5 @@ use clap::{App, ArgMatches}; use failure::{Error, Fallible}; -use std::path::Path; -use taskchampion::{taskstorage, Replica}; #[macro_use] mod macros; @@ -12,6 +10,7 @@ mod gc; mod info; mod list; mod pending; +mod sync; /// Get a list of all subcommands in this crate pub(crate) fn subcommands() -> Vec> { @@ -21,6 +20,7 @@ pub(crate) fn subcommands() -> Vec> { list::cmd(), pending::cmd(), info::cmd(), + sync::cmd(), ] } @@ -54,24 +54,4 @@ pub(crate) trait SubCommandInvocation: std::fmt::Debug { fn as_any(&self) -> &dyn std::any::Any; } -/// A command invocation contains all of the necessary regarding a single invocation of the CLI. -#[derive(Debug)] -pub struct CommandInvocation { - pub(crate) subcommand: Box, -} - -impl CommandInvocation { - pub(crate) fn new(subcommand: Box) -> Self { - Self { subcommand } - } - - pub fn run(self) -> Fallible<()> { - self.subcommand.run(&self) - } - - fn get_replica(&self) -> Replica { - Replica::new(Box::new( - taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(), - )) - } -} +pub use shared::CommandInvocation; diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index 4e24bfcc9..411c85f25 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -1,6 +1,7 @@ use clap::Arg; use failure::{format_err, Fallible}; -use taskchampion::{Replica, Task, Uuid}; +use std::path::Path; +use taskchampion::{server, taskstorage, Replica, Task, Uuid}; pub(super) fn task_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("task") @@ -26,3 +27,31 @@ pub(super) fn get_task>(replica: &mut Replica, task_arg: S) -> Fal Err(format_err!("Cannot interpret {:?} as a task", task_arg)) } + +/// A command invocation contains all of the necessary regarding a single invocation of the CLI. +#[derive(Debug)] +pub struct CommandInvocation { + pub(crate) subcommand: Box, +} + +impl CommandInvocation { + pub(crate) fn new(subcommand: Box) -> Self { + Self { subcommand } + } + + pub fn run(self) -> Fallible<()> { + self.subcommand.run(&self) + } + + // -- utilities for command invocations + + pub(super) fn get_replica(&self) -> Replica { + Replica::new(Box::new( + taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(), + )) + } + + pub(super) fn get_server(&self) -> impl server::Server { + server::LocalServer::new() + } +} diff --git a/cli/src/cmd/sync.rs b/cli/src/cmd/sync.rs new file mode 100644 index 000000000..f968ecf39 --- /dev/null +++ b/cli/src/cmd/sync.rs @@ -0,0 +1,39 @@ +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; + +use crate::cmd::{ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation {} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand(ClapSubCommand::with_name("sync").about("sync with the server")) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("sync", _) => ArgMatchResult::Ok(Box::new(Invocation {})), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica(); + let mut server = command.get_server(); + replica.sync(&mut server)?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "sync"], |_inv| {}); + } +} diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs new file mode 100644 index 000000000..30999496a --- /dev/null +++ b/taskchampion/src/server/local.rs @@ -0,0 +1,79 @@ +use crate::server::{ + AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, +}; +use failure::Fallible; +use std::collections::HashMap; +use uuid::Uuid; + +struct Version { + version_id: VersionId, + parent_version_id: VersionId, + history_segment: HistorySegment, +} + +pub struct LocalServer { + latest_version_id: VersionId, + // NOTE: indexed by parent_version_id! + versions: HashMap, +} + +impl LocalServer { + /// A test server has no notion of clients, signatures, encryption, etc. + pub fn new() -> LocalServer { + LocalServer { + latest_version_id: NO_VERSION_ID, + versions: HashMap::new(), + } + } +} + +impl Server for LocalServer { + /// Add a new version. If the given version number is incorrect, this responds with the + /// appropriate version and expects the caller to try again. + fn add_version( + &mut self, + parent_version_id: VersionId, + history_segment: HistorySegment, + ) -> Fallible { + // no client lookup + // no signature validation + + // check the parent_version_id for linearity + if self.latest_version_id != NO_VERSION_ID { + if parent_version_id != self.latest_version_id { + return Ok(AddVersionResult::ExpectedParentVersion( + self.latest_version_id, + )); + } + } + + // invent a new ID for this version + let version_id = Uuid::new_v4(); + + self.versions.insert( + parent_version_id, + Version { + version_id, + parent_version_id, + history_segment, + }, + ); + self.latest_version_id = version_id; + + Ok(AddVersionResult::Ok(version_id)) + } + + /// Get a vector of all versions after `since_version` + fn get_child_version(&self, parent_version_id: VersionId) -> Fallible { + if let Some(version) = self.versions.get(&parent_version_id) { + Ok(GetVersionResult::Version { + version_id: version.version_id, + parent_version_id: version.parent_version_id, + history_segment: version.history_segment.clone(), + }) + } else { + Ok(GetVersionResult::NoSuchVersion) + } + } +} + diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 06009b127..0331bb63c 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -1,7 +1,9 @@ #[cfg(test)] pub(crate) mod test; +mod local; mod signing; mod types; +pub use local::LocalServer; pub use types::*; From 3537db9bbedf84e17406ed283b1ba8a303d298ed Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 25 Nov 2020 19:13:32 -0500 Subject: [PATCH 078/548] implement a local sync server --- cli/src/cmd/shared.rs | 6 +- cli/src/cmd/sync.rs | 2 +- taskchampion/src/lib.rs | 1 + taskchampion/src/server/local.rs | 211 +++++++++++++++++++++++++---- taskchampion/src/server/test.rs | 2 +- taskchampion/src/server/types.rs | 4 +- taskchampion/src/taskdb.rs | 17 ++- taskchampion/src/taskstorage/kv.rs | 39 +----- taskchampion/src/utils.rs | 39 ++++++ 9 files changed, 249 insertions(+), 72 deletions(-) create mode 100644 taskchampion/src/utils.rs diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index 411c85f25..25940bb28 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -51,7 +51,9 @@ impl CommandInvocation { )) } - pub(super) fn get_server(&self) -> impl server::Server { - server::LocalServer::new() + pub(super) fn get_server(&self) -> Fallible { + Ok(server::LocalServer::new(Path::new( + "/tmp/task-sync-server", + ))?) } } diff --git a/cli/src/cmd/sync.rs b/cli/src/cmd/sync.rs index f968ecf39..a17920436 100644 --- a/cli/src/cmd/sync.rs +++ b/cli/src/cmd/sync.rs @@ -22,7 +22,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { let mut replica = command.get_replica(); - let mut server = command.get_server(); + let mut server = command.get_server()?; replica.sync(&mut server)?; Ok(()) } diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 98ed5f8c3..932dbdeef 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -29,6 +29,7 @@ pub mod server; mod task; mod taskdb; pub mod taskstorage; +mod utils; pub use replica::Replica; pub use task::Priority; diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 30999496a..9659625b3 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -1,33 +1,108 @@ use crate::server::{ AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, }; +use crate::utils::Key; use failure::Fallible; -use std::collections::HashMap; +use kv::msgpack::Msgpack; +use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; +use serde::{Deserialize, Serialize}; +use std::path::Path; use uuid::Uuid; +#[derive(Serialize, Deserialize, Debug)] struct Version { version_id: VersionId, parent_version_id: VersionId, history_segment: HistorySegment, } -pub struct LocalServer { - latest_version_id: VersionId, +pub struct LocalServer<'t> { + store: Store, // NOTE: indexed by parent_version_id! - versions: HashMap, + versions_bucket: Bucket<'t, Key, ValueBuf>>, + latest_version_bucket: Bucket<'t, Integer, ValueBuf>>, } -impl LocalServer { +impl<'t> LocalServer<'t> { /// A test server has no notion of clients, signatures, encryption, etc. - pub fn new() -> LocalServer { - LocalServer { - latest_version_id: NO_VERSION_ID, - versions: HashMap::new(), + pub fn new(directory: &Path) -> Fallible { + let mut config = Config::default(directory); + config.bucket("versions", None); + config.bucket("numbers", None); + config.bucket("latest_version", None); + config.bucket("operations", None); + config.bucket("working_set", None); + let store = Store::new(config)?; + + // versions are stored indexed by VersionId (uuid) + let versions_bucket = store.bucket::>>(Some("versions"))?; + + // this bucket contains the latest version at key 0 + let latest_version_bucket = + store.int_bucket::>>(Some("latest_version"))?; + + Ok(LocalServer { + store, + versions_bucket, + latest_version_bucket, + }) + } + + fn get_latest_version_id(&mut self) -> Fallible { + let txn = self.store.read_txn()?; + let base_version = match txn.get(&self.latest_version_bucket, 0.into()) { + Ok(buf) => buf, + Err(Error::NotFound) => return Ok(NO_VERSION_ID), + Err(e) => return Err(e.into()), } + .inner()? + .to_serde(); + Ok(base_version as VersionId) + } + + fn set_latest_version_id(&mut self, version_id: VersionId) -> Fallible<()> { + let mut txn = self.store.write_txn()?; + txn.set( + &self.latest_version_bucket, + 0.into(), + Msgpack::to_value_buf(version_id as Uuid)?, + )?; + txn.commit()?; + Ok(()) + } + + fn get_version_by_parent_version_id( + &mut self, + parent_version_id: VersionId, + ) -> Fallible> { + let txn = self.store.read_txn()?; + + let version = match txn.get(&self.versions_bucket, parent_version_id.into()) { + Ok(buf) => buf, + Err(Error::NotFound) => return Ok(None), + Err(e) => return Err(e.into()), + } + .inner()? + .to_serde(); + Ok(Some(version)) + } + + fn add_version_by_parent_version_id(&mut self, version: Version) -> Fallible<()> { + let mut txn = self.store.write_txn()?; + txn.set( + &self.versions_bucket, + version.parent_version_id.into(), + Msgpack::to_value_buf(version)?, + )?; + txn.commit()?; + Ok(()) } } -impl Server for LocalServer { +impl<'t> Server for LocalServer<'t> { + // TODO: better transaction isolation for add_version (gets and sets should be in the same + // transaction) + /// Add a new version. If the given version number is incorrect, this responds with the /// appropriate version and expects the caller to try again. fn add_version( @@ -39,33 +114,27 @@ impl Server for LocalServer { // no signature validation // check the parent_version_id for linearity - if self.latest_version_id != NO_VERSION_ID { - if parent_version_id != self.latest_version_id { - return Ok(AddVersionResult::ExpectedParentVersion( - self.latest_version_id, - )); - } + let latest_version_id = self.get_latest_version_id()?; + if latest_version_id != NO_VERSION_ID && parent_version_id != latest_version_id { + return Ok(AddVersionResult::ExpectedParentVersion(latest_version_id)); } // invent a new ID for this version let version_id = Uuid::new_v4(); - self.versions.insert( + self.add_version_by_parent_version_id(Version { + version_id, parent_version_id, - Version { - version_id, - parent_version_id, - history_segment, - }, - ); - self.latest_version_id = version_id; + history_segment, + })?; + self.set_latest_version_id(version_id)?; Ok(AddVersionResult::Ok(version_id)) } /// Get a vector of all versions after `since_version` - fn get_child_version(&self, parent_version_id: VersionId) -> Fallible { - if let Some(version) = self.versions.get(&parent_version_id) { + fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible { + if let Some(version) = self.get_version_by_parent_version_id(parent_version_id)? { Ok(GetVersionResult::Version { version_id: version.version_id, parent_version_id: version.parent_version_id, @@ -77,3 +146,93 @@ impl Server for LocalServer { } } +#[cfg(test)] +mod test { + use super::*; + use failure::Fallible; + use tempdir::TempDir; + + #[test] + fn test_empty() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut server = LocalServer::new(&tmp_dir.path())?; + let child_version = server.get_child_version(NO_VERSION_ID)?; + assert_eq!(child_version, GetVersionResult::NoSuchVersion); + Ok(()) + } + + #[test] + fn test_add_zero_base() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut server = LocalServer::new(&tmp_dir.path())?; + let history = b"1234".to_vec(); + match server.add_version(NO_VERSION_ID, history.clone())? { + AddVersionResult::ExpectedParentVersion(_) => { + panic!("should have accepted the version") + } + AddVersionResult::Ok(version_id) => { + let new_version = server.get_child_version(NO_VERSION_ID)?; + assert_eq!( + new_version, + GetVersionResult::Version { + version_id, + parent_version_id: NO_VERSION_ID, + history_segment: history, + } + ); + } + } + + Ok(()) + } + + #[test] + fn test_add_nonzero_base() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut server = LocalServer::new(&tmp_dir.path())?; + let history = b"1234".to_vec(); + let parent_version_id = Uuid::new_v4() as VersionId; + + // This is OK because the server has no latest_version_id yet + match server.add_version(parent_version_id, history.clone())? { + AddVersionResult::ExpectedParentVersion(_) => { + panic!("should have accepted the version") + } + AddVersionResult::Ok(version_id) => { + let new_version = server.get_child_version(parent_version_id)?; + assert_eq!( + new_version, + GetVersionResult::Version { + version_id, + parent_version_id, + history_segment: history, + } + ); + } + } + + Ok(()) + } + + #[test] + fn test_add_nonzero_base_forbidden() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let mut server = LocalServer::new(&tmp_dir.path())?; + let history = b"1234".to_vec(); + let parent_version_id = Uuid::new_v4() as VersionId; + + // add a version + if let AddVersionResult::ExpectedParentVersion(_) = + server.add_version(parent_version_id, history.clone())? + { + panic!("should have accepted the version") + } + + // then add another, not based on that one + if let AddVersionResult::Ok(_) = server.add_version(parent_version_id, history.clone())? { + panic!("should not have accepted the version") + } + + Ok(()) + } +} diff --git a/taskchampion/src/server/test.rs b/taskchampion/src/server/test.rs index 3175c2aa5..3d57147ca 100644 --- a/taskchampion/src/server/test.rs +++ b/taskchampion/src/server/test.rs @@ -64,7 +64,7 @@ impl Server for TestServer { } /// Get a vector of all versions after `since_version` - fn get_child_version(&self, parent_version_id: VersionId) -> Fallible { + fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible { if let Some(version) = self.versions.get(&parent_version_id) { Ok(GetVersionResult::Version { version_id: version.version_id, diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index 9d95f588b..473a17e46 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -12,6 +12,7 @@ pub const NO_VERSION_ID: VersionId = Uuid::nil(); pub type HistorySegment = Vec; /// VersionAdd is the response type from [`crate:server::Server::add_version`]. +#[derive(Debug, PartialEq)] pub enum AddVersionResult { /// OK, version added with the given ID Ok(VersionId), @@ -20,6 +21,7 @@ pub enum AddVersionResult { } /// A version as downloaded from the server +#[derive(Debug, PartialEq)] pub enum GetVersionResult { /// No such version exists NoSuchVersion, @@ -42,5 +44,5 @@ pub trait Server { ) -> Fallible; /// Get the version with the given parent VersionId - fn get_child_version(&self, parent_version_id: VersionId) -> Fallible; + fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible; } diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 86523717d..eb69e35ae 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -1,7 +1,7 @@ use crate::errors::Error; use crate::server::{AddVersionResult, GetVersionResult, Server}; use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; -use failure::Fallible; +use failure::{format_err, Fallible}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::str; @@ -168,7 +168,9 @@ impl TaskDB { let mut txn = self.storage.txn()?; // retry synchronizing until the server accepts our version (this allows for races between - // replicas trying to sync to the same server) + // replicas trying to sync to the same server). If the server insists on the same base + // version twice, then we have diverged. + let mut requested_parent_version_id = None; loop { let mut base_version_id = txn.base_version()?; @@ -189,6 +191,7 @@ impl TaskDB { txn.set_base_version(version_id)?; base_version_id = version_id; } else { + println!("no child versions of {:?}", base_version_id); // at the moment, no more child versions, so we can try adding our own break; } @@ -196,6 +199,7 @@ impl TaskDB { let operations: Vec = txn.operations()?.to_vec(); if operations.is_empty() { + println!("no changes to push to server"); // nothing to sync back to the server.. break; } @@ -216,7 +220,14 @@ impl TaskDB { "new version rejected; must be based on {:?}", parent_version_id ); - // ..continue the outer loop + if let Some(requested) = requested_parent_version_id { + if parent_version_id == requested { + return Err(format_err!( + "Server's task history has diverged from this replica" + )); + } + } + requested_parent_version_id = Some(parent_version_id); } } } diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index 52947a5c0..f6ee9a7f0 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -1,50 +1,13 @@ use crate::taskstorage::{ Operation, TaskMap, TaskStorage, TaskStorageTxn, VersionId, DEFAULT_BASE_VERSION, }; +use crate::utils::Key; use failure::Fallible; use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; -use std::convert::TryInto; use std::path::Path; use uuid::Uuid; -/// A representation of a UUID as a key. This is just a newtype wrapping the 128-bit packed form -/// of a UUID. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -struct Key(uuid::Bytes); - -impl From<&[u8]> for Key { - fn from(bytes: &[u8]) -> Key { - Key(bytes.try_into().unwrap()) - } -} - -impl From<&Uuid> for Key { - fn from(uuid: &Uuid) -> Key { - let key = Key(*uuid.as_bytes()); - key - } -} - -impl From for Key { - fn from(uuid: Uuid) -> Key { - let key = Key(*uuid.as_bytes()); - key - } -} - -impl From for Uuid { - fn from(key: Key) -> Uuid { - Uuid::from_bytes(key.0) - } -} - -impl AsRef<[u8]> for Key { - fn as_ref(&self) -> &[u8] { - &self.0[..] - } -} - /// KVStorage is an on-disk storage backend which uses LMDB via the `kv` crate. pub struct KVStorage<'t> { store: Store, diff --git a/taskchampion/src/utils.rs b/taskchampion/src/utils.rs new file mode 100644 index 000000000..aafe6f010 --- /dev/null +++ b/taskchampion/src/utils.rs @@ -0,0 +1,39 @@ +use std::convert::TryInto; +use uuid::Uuid; + +/// A representation of a UUID as a key. This is just a newtype wrapping the 128-bit packed form +/// of a UUID. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct Key(uuid::Bytes); + +impl From<&[u8]> for Key { + fn from(bytes: &[u8]) -> Key { + Key(bytes.try_into().unwrap()) + } +} + +impl From<&Uuid> for Key { + fn from(uuid: &Uuid) -> Key { + let key = Key(*uuid.as_bytes()); + key + } +} + +impl From for Key { + fn from(uuid: Uuid) -> Key { + let key = Key(*uuid.as_bytes()); + key + } +} + +impl From for Uuid { + fn from(key: Key) -> Uuid { + Uuid::from_bytes(key.0) + } +} + +impl AsRef<[u8]> for Key { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} From 1511a0e38e0177767677f0c4936a9b9b7fdcc446 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 25 Nov 2020 19:46:23 -0500 Subject: [PATCH 079/548] update docs based on modified sync designs --- docs/src/sync.md | 58 +++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/docs/src/sync.md b/docs/src/sync.md index 3f664542e..cd2621cdc 100644 --- a/docs/src/sync.md +++ b/docs/src/sync.md @@ -36,24 +36,32 @@ This process is analogous (vaguely) to rebasing a sequence of Git commits. ### Versions -Occasionally, database states are named with an integer, called a version. -The system as a whole (all replicas) constructs a monotonic sequence of versions and the operations that separate each version from the next. -No gaps are allowed in the version numbering. -Version 0 is implicitly the empty database. +Occasionally, database states are given a name (that takes the form of a UUID). +The system as a whole (all replicas) constructs a branch-free sequence of versions and the operations that separate each version from the next. +The version with the nil UUID is implicitly the empty database. -The server stores the operations to change a state from a version N to a version N+1, and provides that information as needed to replicas. +The server stores the operations to change a state from a "parent" version to a "child" version, and provides that information as needed to replicas. Replicas use this information to update their local task databases, and to generate new versions to send to the server. -Replicas generate a new version to transmit changes made locally to the server. +Replicas generate a new version to transmit local changes to the server. The changes are represented as a sequence of operations with the state resulting from the final operation corresponding to the version. -In order to keep the gap-free monotonic numbering, the server will only accept a proposed version from a replica if its number is one greater that the latest version on the server. +In order to keep the versions in a single sequence, the server will only accept a proposed version from a replica if its parent version matches the latest version on the server. -In the non-conflict case (such as with a single replica), then, a replica's synchronization process involves gathering up the operations it has accumulated since its last synchronization; bundling those operations into version N+1; and sending that version to the server. +In the non-conflict case (such as with a single replica), then, a replica's synchronization process involves gathering up the operations it has accumulated since its last synchronization; bundling those operations into a version; and sending that version to the server. + +### Replica Invariant + +The replica's [storage](./storage.md) contains the current state in `tasks`, the as-yet un-synchronized operations in `operations`, and the last version at which synchronization occurred in `base_version`. + +The replica's un-synchronized operations are already reflected in its local `tasks`, so the following invariant holds: + +> Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical +> to `tasks`. ### Transformation When the latest version on the server contains operations that are not present in the replica, then the states have diverged. -For example (with lower-case letters designating operations): +For example: ```text o -- version N @@ -67,6 +75,8 @@ For example (with lower-case letters designating operations): o -- version N+1 ``` +(diagram notation: `o` designates a state, lower-case letters designate operations, and versions are presented as if they were numbered sequentially) + In this situation, the replica must "rebase" the local operations onto the latest version from the server and try again. This process is performed using operational transformation (OT). The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the replica can apply to its local task database to reach the same state @@ -96,25 +106,23 @@ Careful selection of the operations and the transformation function ensure this. See the comments in the source code for the details of how this transformation process is implemented. -## Replica Implementation +## Synchronization Process -The replica's [storage](./storage.md) contains the current state in `tasks`, the as-yet un-synchronized operations in `operations`, and the last version at which synchronization occurred in `base_version`. - -To perform a synchronization, the replica first requests any versions greater than `base_version` from the server, and rebases any local operations on top of those new versions, updating `base_version`. +To perform a synchronization, the replica first requests the child version of `base_version` from the server (`get_child_version`). +It applies that version to its local `tasks`, rebases its local `operations` as described above, and updates `base_version`. +The replica repeats this process until the server indicates no additional child versions exist. If there are no un-synchronized local operations, the process is complete. -Otherwise, the replica creates a new version containing those local operations and uploads that to the server. -In most cases, this will succeed, but if another replica has created a new version in the interim, then the new version will conflict with that other replica's new version. + +Otherwise, the replica creates a new version containing its local operations, giving its `base_version` as the parent version, and transmits that to the server (`add_version`). +In most cases, this will succeed, but if another replica has created a new version in the interim, then the new version will conflict with that other replica's new version and the server will respond with the new expected parent version. In this case, the process repeats. +If the server indicates a conflict twice with the same expected base version, that is an indication that the replica has diverged (something serious has gone wrong). -The replica's un-synchronized operations are already reflected in `tasks`, so the following invariant holds: +## Servers -> Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical -> to `tasks`. +A replica depends on periodic synchronization for performant operation. +Without synchronization, its list of pending operations would grow indefinitely, and tasks could never be expired. +So all replicas, even "singleton" replicas which do not replicate task data with any other replica, must synchronize periodically. -## Server Implementation - -The server implementation is simple. -It supports fetching versions keyed by number, and adding a new version. -In adding a new version, the version number must be one greater than the greatest existing version. - -Critically, the server operates on nothing more than numbered, opaque blobs of data. +TaskChampion provides a `LocalServer` for this purpose. +It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally, all within the `task` binary. From 087333a227dab1ba28cdab6bb32ffea9031cbcbc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 25 Nov 2020 23:16:05 -0500 Subject: [PATCH 080/548] refactor sync server into modules --- Cargo.lock | 1388 +++++++++++++++++++++- sync-server/Cargo.toml | 5 + sync-server/src/api/add_version.rs | 25 + sync-server/src/api/get_child_version.rs | 19 + sync-server/src/api/mod.rs | 2 + sync-server/src/lib.rs | 86 -- sync-server/src/main.rs | 24 + sync-server/src/server.rs | 34 + sync-server/src/types.rs | 23 + 9 files changed, 1504 insertions(+), 102 deletions(-) create mode 100644 sync-server/src/api/add_version.rs create mode 100644 sync-server/src/api/get_child_version.rs create mode 100644 sync-server/src/api/mod.rs delete mode 100644 sync-server/src/lib.rs create mode 100644 sync-server/src/main.rs create mode 100644 sync-server/src/server.rs create mode 100644 sync-server/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index c5ee93f04..6f24b0e1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,266 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "actix-codec" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project 0.4.27", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-connect" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "derive_more", + "either", + "futures-util", + "http", + "log", + "trust-dns-proto", + "trust-dns-resolver", +] + +[[package]] +name = "actix-http" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" +dependencies = [ + "actix-codec", + "actix-connect", + "actix-rt", + "actix-service", + "actix-threadpool", + "actix-utils", + "base64 0.13.0", + "bitflags", + "brotli2", + "bytes", + "cookie", + "copyless", + "derive_more", + "either", + "encoding_rs", + "flate2", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "h2", + "http", + "httparse", + "indexmap", + "itoa", + "language-tags", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project 1.0.2", + "rand 0.7.3", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "sha-1", + "slab", + "time 0.2.23", +] + +[[package]] +name = "actix-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a60f9ba7c4e6df97f3aacb14bb5c0cd7d98a49dcbaed0d7f292912ad9a6a3ed2" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd1f7dbda1645bf7da33554db60891755f6c01c1b2169e2f4c492098d30c235" +dependencies = [ + "bytestring", + "http", + "log", + "regex", + "serde", +] + +[[package]] +name = "actix-rt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" +dependencies = [ + "actix-macros", + "actix-threadpool", + "copyless", + "futures-channel", + "futures-util", + "smallvec", + "tokio", +] + +[[package]] +name = "actix-server" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-channel", + "futures-util", + "log", + "mio", + "mio-uds", + "num_cpus", + "slab", + "socket2", +] + +[[package]] +name = "actix-service" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" +dependencies = [ + "futures-util", + "pin-project 0.4.27", +] + +[[package]] +name = "actix-testing" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" +dependencies = [ + "actix-macros", + "actix-rt", + "actix-server", + "actix-service", + "log", + "socket2", +] + +[[package]] +name = "actix-threadpool" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30" +dependencies = [ + "derive_more", + "futures-channel", + "lazy_static", + "log", + "num_cpus", + "parking_lot", + "threadpool", +] + +[[package]] +name = "actix-tls" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb" +dependencies = [ + "actix-codec", + "actix-service", + "actix-utils", + "futures-util", +] + +[[package]] +name = "actix-utils" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "bitflags", + "bytes", + "either", + "futures-channel", + "futures-sink", + "futures-util", + "log", + "pin-project 0.4.27", + "slab", +] + +[[package]] +name = "actix-web" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a89a7b133e734f6d1e555502d450408ae04105826aef7e3605019747d3ac732" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-testing", + "actix-threadpool", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "awc", + "bytes", + "derive_more", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "log", + "mime", + "pin-project 1.0.2", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "socket2", + "time 0.2.23", + "tinyvec", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "addr2line" version = "0.14.0" @@ -30,7 +291,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -58,6 +319,17 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-trait" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -66,7 +338,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -81,6 +353,30 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "awc" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9056f5e27b0d56bedd82f78eceaba0bcddcbbcbbefae3cd0a53994b28c96ff5" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "base64 0.13.0", + "bytes", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "log", + "mime", + "percent-encoding", + "rand 0.7.3", + "serde", + "serde_json", + "serde_urlencoded", +] + [[package]] name = "backtrace" version = "0.3.55" @@ -95,12 +391,24 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + [[package]] name = "base64" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bit-set" version = "0.5.2" @@ -133,6 +441,35 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "brotli2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +dependencies = [ + "brotli-sys", + "libc", +] + [[package]] name = "bstr" version = "0.2.14" @@ -157,6 +494,21 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytestring" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363" +dependencies = [ + "bytes", +] + [[package]] name = "cc" version = "1.0.65" @@ -185,8 +537,8 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time", - "winapi", + "time 0.1.44", + "winapi 0.3.9", ] [[package]] @@ -213,12 +565,59 @@ dependencies = [ "bitflags", ] +[[package]] +name = "cloudabi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" +dependencies = [ + "bitflags", +] + +[[package]] +name = "const_fn" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" + [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "cookie" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f" +dependencies = [ + "percent-encoding", + "time 0.2.23", + "version_check", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -252,12 +651,32 @@ dependencies = [ "memchr", ] +[[package]] +name = "derive_more" +version = "0.99.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "dirs" version = "1.0.5" @@ -266,21 +685,54 @@ checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", "redox_users", - "winapi", + "winapi 0.3.9", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enum-as-inner" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "failure" version = "0.1.8" @@ -303,6 +755,18 @@ dependencies = [ "synstructure", ] +[[package]] +name = "flate2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "float-cmp" version = "0.8.0" @@ -318,12 +782,140 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" + +[[package]] +name = "futures-io" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" + +[[package]] +name = "futures-macro" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" + +[[package]] +name = "futures-task" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project 1.0.2", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.1.15" @@ -341,6 +933,41 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", + "tracing-futures", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.17" @@ -350,6 +977,85 @@ dependencies = [ "libc", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", +] + +[[package]] +name = "http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +dependencies = [ + "autocfg 1.0.1", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2", + "widestring", + "winapi 0.3.9", + "winreg", +] + [[package]] name = "itoa" version = "0.4.6" @@ -365,6 +1071,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "kv" version = "0.10.0" @@ -378,6 +1094,12 @@ dependencies = [ "toml", ] +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + [[package]] name = "lazy_static" version = "1.4.0" @@ -390,6 +1112,12 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + [[package]] name = "lmdb-rkv" version = "0.12.3" @@ -413,6 +1141,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.11" @@ -422,12 +1159,39 @@ dependencies = [ "cfg-if 0.1.10", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "miniz_oxide" version = "0.4.3" @@ -438,6 +1202,59 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -463,6 +1280,16 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.22.0" @@ -475,6 +1302,102 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi 0.1.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +dependencies = [ + "pin-project-internal 0.4.27", +] + +[[package]] +name = "pin-project" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" +dependencies = [ + "pin-project-internal 1.0.2", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" + +[[package]] +name = "pin-project-lite" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.19" @@ -530,6 +1453,18 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -584,7 +1519,7 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -603,7 +1538,7 @@ dependencies = [ "rand_os", "rand_pcg", "rand_xorshift", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -698,7 +1633,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ "libc", "rand_core 0.4.2", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -707,12 +1642,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" dependencies = [ - "cloudabi", + "cloudabi 0.0.3", "fuchsia-cprng", "libc", "rand_core 0.4.2", "rdrand", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -793,7 +1728,17 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", ] [[package]] @@ -808,7 +1753,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -838,7 +1783,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" dependencies = [ - "base64", + "base64 0.12.3", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -850,6 +1795,15 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "rusty-fork" version = "0.2.2" @@ -868,6 +1822,27 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.117" @@ -899,12 +1874,134 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpuid-bool", + "digest", + "opaque-debug", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "signal-hook-registry" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" + +[[package]] +name = "socket2" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "standback" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8" +dependencies = [ + "version_check", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "strsim" version = "0.8.0" @@ -925,6 +2022,13 @@ dependencies = [ [[package]] name = "sync-server" version = "0.1.0" +dependencies = [ + "actix-web", + "failure", + "serde", + "serde_json", + "taskchampion", +] [[package]] name = "synstructure" @@ -987,7 +2091,7 @@ dependencies = [ "rand 0.7.3", "redox_syscall", "remove_dir_all", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -998,7 +2102,7 @@ checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", "dirs", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1039,6 +2143,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.1.44" @@ -1047,7 +2160,94 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi 0.3.9", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff" +dependencies = [ + "bytes", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-uds", + "pin-project-lite 0.1.11", + "signal-hook-registry", + "slab", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.1.11", + "tokio", ] [[package]] @@ -1059,12 +2259,113 @@ dependencies = [ "serde", ] +[[package]] +name = "tracing" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite 0.2.0", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" +dependencies = [ + "pin-project 0.4.27", + "tracing", +] + [[package]] name = "treeline" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" +[[package]] +name = "trust-dns-proto" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53861fcb288a166aae4c508ae558ed18b53838db728d4d310aad08270a7d4c2b" +dependencies = [ + "async-trait", + "backtrace", + "enum-as-inner", + "futures", + "idna", + "lazy_static", + "log", + "rand 0.7.3", + "smallvec", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6759e8efc40465547b0dfce9500d733c65f969a4cbbfbe3ccf68daaa46ef179e" +dependencies = [ + "backtrace", + "cfg-if 0.1.10", + "futures", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + [[package]] name = "unicode-width" version = "0.1.8" @@ -1083,6 +2384,18 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "uuid" version = "0.8.1" @@ -1099,6 +2412,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -1184,6 +2503,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -1194,6 +2525,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1205,3 +2542,22 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index e2df075ed..d243e2647 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -7,3 +7,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +actix-web = "3.3.0" +failure = "0.1.8" +serde = "1.0.117" +serde_json = "1.0.59" +taskchampion = { path = "../taskchampion" } diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs new file mode 100644 index 000000000..75398ecd9 --- /dev/null +++ b/sync-server/src/api/add_version.rs @@ -0,0 +1,25 @@ +use crate::server::SyncServer; +use crate::types::{ClientId, HistorySegment, VersionId}; +use actix_web::{error, http::StatusCode, post, web, HttpResponse, Responder, Result}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +/// Request body to add_version +#[derive(Serialize, Deserialize)] +pub(crate) struct AddVersionRequest { + // TODO: temporary! + #[serde(default)] + history_segment: HistorySegment, +} + +#[post("/client/{client_id}/add-version/{parent_version_id}")] +pub(crate) async fn service( + data: web::Data>, + web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, + body: web::Json, +) -> Result { + let result = data + .add_version(client_id, parent_version_id, &body.history_segment) + .map_err(|e| error::InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?; + Ok(HttpResponse::Ok().json(result)) +} diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs new file mode 100644 index 000000000..8512e6f4a --- /dev/null +++ b/sync-server/src/api/get_child_version.rs @@ -0,0 +1,19 @@ +use crate::server::SyncServer; +use crate::types::{ClientId, VersionId}; +use actix_web::{error, get, http::StatusCode, web, HttpResponse, Result}; +use std::sync::Arc; + +#[get("/client/{client_id}/get-child-version/{parent_version_id}")] +pub(crate) async fn service( + data: web::Data>, + web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, +) -> Result { + let result = data + .get_child_version(client_id, parent_version_id) + .map_err(|e| error::InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?; + if let Some(result) = result { + Ok(HttpResponse::Ok().json(result)) + } else { + Err(error::ErrorNotFound("no such version")) + } +} diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs new file mode 100644 index 000000000..b955fcc25 --- /dev/null +++ b/sync-server/src/api/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod add_version; +pub(crate) mod get_child_version; diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs deleted file mode 100644 index e85125ed0..000000000 --- a/sync-server/src/lib.rs +++ /dev/null @@ -1,86 +0,0 @@ -#![allow(clippy::new_without_default)] - -use std::collections::HashMap; - -type Blob = Vec; - -struct User { - // versions, indexed at v-1 - versions: Vec, - snapshots: HashMap, -} - -pub struct Server { - users: HashMap, -} - -pub enum VersionAdd { - // OK, version added - Ok, - // Rejected, must be based on the the given version - ExpectedVersion(u64), -} - -impl User { - fn new() -> User { - User { - versions: vec![], - snapshots: HashMap::new(), - } - } - - fn get_versions(&self, since_version: u64) -> Vec { - let last_version = self.versions.len(); - if last_version == since_version as usize { - return vec![]; - } - self.versions[since_version as usize..last_version].to_vec() - } - - fn add_version(&mut self, version: u64, blob: Blob) -> VersionAdd { - // of by one here: client wants to send version 1 first - let expected_version = self.versions.len() as u64 + 1; - if version != expected_version { - return VersionAdd::ExpectedVersion(expected_version); - } - self.versions.push(blob); - - VersionAdd::Ok - } - - fn add_snapshot(&mut self, version: u64, blob: Blob) { - self.snapshots.insert(version, blob); - } -} - -impl Server { - pub fn new() -> Server { - Server { - users: HashMap::new(), - } - } - - fn get_user_mut(&mut self, username: &str) -> &mut User { - self.users - .entry(username.to_string()) - .or_insert_with(User::new) - } - - /// Get a vector of all versions after `since_version` - pub fn get_versions(&self, username: &str, since_version: u64) -> Vec { - self.users - .get(username) - .map(|user| user.get_versions(since_version)) - .unwrap_or_default() - } - - /// Add a new version. If the given version number is incorrect, this responds with the - /// appropriate version and expects the caller to try again. - pub fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> VersionAdd { - self.get_user_mut(username).add_version(version, blob) - } - - pub fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) { - self.get_user_mut(username).add_snapshot(version, blob); - } -} diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs new file mode 100644 index 000000000..20b7b118f --- /dev/null +++ b/sync-server/src/main.rs @@ -0,0 +1,24 @@ +use actix_web::{App, HttpServer}; +use server::SyncServer; +use std::sync::Arc; + +mod api; +mod server; +mod types; + +// TODO: use hawk to sign requests + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let sync_server = Arc::new(SyncServer::new()); + + HttpServer::new(move || { + App::new() + .data(sync_server.clone()) + .service(api::get_child_version::service) + .service(api::add_version::service) + }) + .bind("127.0.0.1:8080")? + .run() + .await +} diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs new file mode 100644 index 000000000..8c4bdfdf2 --- /dev/null +++ b/sync-server/src/server.rs @@ -0,0 +1,34 @@ +use crate::types::{AddVersionResult, ClientId, GetVersionResult, HistorySegment, VersionId}; +use failure::Fallible; +use taskchampion::Uuid; + +/// The sync server's implementation; HTTP API method call through to methods on a single +/// instance of this type. +pub(crate) struct SyncServer {} + +impl SyncServer { + pub(crate) fn new() -> Self { + Self {} + } + + pub(crate) fn get_child_version( + &self, + _client_id: ClientId, + parent_version_id: VersionId, + ) -> Fallible> { + Ok(Some(GetVersionResult { + version_id: Uuid::new_v4(), + parent_version_id, + history_segment: b"abcd".to_vec(), + })) + } + + pub(crate) fn add_version( + &self, + _client_id: ClientId, + _parent_version_id: VersionId, + _history_segment: &HistorySegment, + ) -> Fallible { + Ok(AddVersionResult::Ok(Uuid::new_v4())) + } +} diff --git a/sync-server/src/types.rs b/sync-server/src/types.rs new file mode 100644 index 000000000..f4b28901d --- /dev/null +++ b/sync-server/src/types.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; +use taskchampion::Uuid; + +pub(crate) type HistorySegment = Vec; +pub(crate) type ClientId = Uuid; +pub(crate) type VersionId = Uuid; + +/// Response to get_child_version +#[derive(Serialize, Deserialize)] +pub(crate) struct GetVersionResult { + pub(crate) version_id: Uuid, + pub(crate) parent_version_id: Uuid, + pub(crate) history_segment: HistorySegment, +} + +/// Response to add_version +#[derive(Serialize, Deserialize)] +pub(crate) enum AddVersionResult { + /// OK, version added with the given ID + Ok(VersionId), + /// Rejected; expected a version with the given parent version + ExpectedParentVersion(VersionId), +} From a5c06008b3894ef28ddf86c25e2a7a35f475f537 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 10:52:28 -0500 Subject: [PATCH 081/548] use a trait object for the server, for dynamic dispatch --- sync-server/src/api/add_version.rs | 5 ++-- sync-server/src/api/get_child_version.rs | 5 ++-- sync-server/src/api/mod.rs | 6 ++++ sync-server/src/main.rs | 5 ++-- sync-server/src/{server.rs => server/mod.rs} | 30 ++++++++++++++++---- 5 files changed, 36 insertions(+), 15 deletions(-) rename sync-server/src/{server.rs => server/mod.rs} (54%) diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 75398ecd9..cdc96db2a 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -1,8 +1,7 @@ -use crate::server::SyncServer; +use crate::api::ServerState; use crate::types::{ClientId, HistorySegment, VersionId}; use actix_web::{error, http::StatusCode, post, web, HttpResponse, Responder, Result}; use serde::{Deserialize, Serialize}; -use std::sync::Arc; /// Request body to add_version #[derive(Serialize, Deserialize)] @@ -14,7 +13,7 @@ pub(crate) struct AddVersionRequest { #[post("/client/{client_id}/add-version/{parent_version_id}")] pub(crate) async fn service( - data: web::Data>, + data: web::Data, web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, body: web::Json, ) -> Result { diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 8512e6f4a..4383a6fbc 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -1,11 +1,10 @@ -use crate::server::SyncServer; +use crate::api::ServerState; use crate::types::{ClientId, VersionId}; use actix_web::{error, get, http::StatusCode, web, HttpResponse, Result}; -use std::sync::Arc; #[get("/client/{client_id}/get-child-version/{parent_version_id}")] pub(crate) async fn service( - data: web::Data>, + data: web::Data, web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, ) -> Result { let result = data diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index b955fcc25..cdab25fca 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -1,2 +1,8 @@ +use crate::server::SyncServer; +use std::sync::Arc; + pub(crate) mod add_version; pub(crate) mod get_child_version; + +/// The type containing a reference to the SyncServer object in the Actix state. +pub(crate) type ServerState = Arc>; diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 20b7b118f..191908fb2 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,5 +1,4 @@ use actix_web::{App, HttpServer}; -use server::SyncServer; use std::sync::Arc; mod api; @@ -10,11 +9,11 @@ mod types; #[actix_web::main] async fn main() -> std::io::Result<()> { - let sync_server = Arc::new(SyncServer::new()); + let server_state = Arc::new(Box::new(server::NullSyncServer::new())); HttpServer::new(move || { App::new() - .data(sync_server.clone()) + .data(server_state.clone()) .service(api::get_child_version::service) .service(api::add_version::service) }) diff --git a/sync-server/src/server.rs b/sync-server/src/server/mod.rs similarity index 54% rename from sync-server/src/server.rs rename to sync-server/src/server/mod.rs index 8c4bdfdf2..df4377366 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server/mod.rs @@ -2,16 +2,34 @@ use crate::types::{AddVersionResult, ClientId, GetVersionResult, HistorySegment, use failure::Fallible; use taskchampion::Uuid; -/// The sync server's implementation; HTTP API method call through to methods on a single -/// instance of this type. -pub(crate) struct SyncServer {} +pub(crate) trait SyncServer { + fn get_child_version( + &self, + client_id: ClientId, + parent_version_id: VersionId, + ) -> Fallible>; -impl SyncServer { + fn add_version( + &self, + client_id: ClientId, + parent_version_id: VersionId, + history_segment: &HistorySegment, + ) -> Fallible; +} + +// TODO: temporary +/// A "null" sync server's implementation; HTTP API methods call through to methods on a single +/// instance of this type. +pub(crate) struct NullSyncServer {} + +impl NullSyncServer { pub(crate) fn new() -> Self { Self {} } +} - pub(crate) fn get_child_version( +impl SyncServer for NullSyncServer { + fn get_child_version( &self, _client_id: ClientId, parent_version_id: VersionId, @@ -23,7 +41,7 @@ impl SyncServer { })) } - pub(crate) fn add_version( + fn add_version( &self, _client_id: ClientId, _parent_version_id: VersionId, From e84871931f068a33b1d3e16eb43fd1e0163c7ba7 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 11:32:20 -0500 Subject: [PATCH 082/548] Refactor HTTP implementation of API methods --- Cargo.lock | 15 +++++- sync-server/Cargo.toml | 3 +- sync-server/src/api/add_version.rs | 63 ++++++++++++++++++------ sync-server/src/api/get_child_version.rs | 21 +++++++- sync-server/src/api/mod.rs | 10 ++++ sync-server/src/main.rs | 6 ++- sync-server/src/server/mod.rs | 9 ++-- sync-server/src/types.rs | 3 -- 8 files changed, 100 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f24b0e1f..4c744d952 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -822,6 +822,7 @@ checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -844,6 +845,17 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" +[[package]] +name = "futures-executor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.8" @@ -2025,8 +2037,7 @@ version = "0.1.0" dependencies = [ "actix-web", "failure", - "serde", - "serde_json", + "futures", "taskchampion", ] diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index d243e2647..3c638aa7c 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -9,6 +9,5 @@ edition = "2018" [dependencies] actix-web = "3.3.0" failure = "0.1.8" -serde = "1.0.117" -serde_json = "1.0.59" +futures = "0.3.8" taskchampion = { path = "../taskchampion" } diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index cdc96db2a..40cb5fdd0 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -1,24 +1,57 @@ -use crate::api::ServerState; -use crate::types::{ClientId, HistorySegment, VersionId}; -use actix_web::{error, http::StatusCode, post, web, HttpResponse, Responder, Result}; -use serde::{Deserialize, Serialize}; +use crate::api::{ + ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, +}; +use crate::types::{AddVersionResult, ClientId, VersionId}; +use actix_web::{ + error, http::StatusCode, post, web, HttpMessage, HttpRequest, HttpResponse, Result, +}; +use futures::StreamExt; -/// Request body to add_version -#[derive(Serialize, Deserialize)] -pub(crate) struct AddVersionRequest { - // TODO: temporary! - #[serde(default)] - history_segment: HistorySegment, -} +/// Max history segment size: 100MB +const MAX_SIZE: usize = 100 * 1024 * 1024; +/// Add a new version, after checking prerequisites. The history segment should be transmitted in +/// the request entity body and must have content-type +/// `application/vnd.taskchampion.history-segment`. The content can be encoded in any of the +/// formats supported by actix-web. +/// +/// On success, the response is a 200 OK with the new version ID in the `X-Version-Id` header. If +/// the version cannot be added due to a conflict, the response is a 409 CONFLICT with the expected +/// parent version ID in the `X-Parent-Version-Id` header. +/// +/// Returns other 4xx or 5xx responses on other errors. #[post("/client/{client_id}/add-version/{parent_version_id}")] pub(crate) async fn service( + req: HttpRequest, data: web::Data, web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, - body: web::Json, -) -> Result { + mut payload: web::Payload, +) -> Result { + // check content-type + if req.content_type() != HISTORY_SEGMENT_CONTENT_TYPE { + return Err(error::ErrorBadRequest("Bad content-type")); + } + + // read the body in its entirety + let mut body = web::BytesMut::new(); + while let Some(chunk) = payload.next().await { + let chunk = chunk?; + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + return Err(error::ErrorBadRequest("overflow")); + } + body.extend_from_slice(&chunk); + } + let result = data - .add_version(client_id, parent_version_id, &body.history_segment) + .add_version(client_id, parent_version_id, body.to_vec()) .map_err(|e| error::InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?; - Ok(HttpResponse::Ok().json(result)) + Ok(match result { + AddVersionResult::Ok(version_id) => HttpResponse::Ok() + .header(VERSION_ID_HEADER, version_id.to_string()) + .body(""), + AddVersionResult::ExpectedParentVersion(parent_version_id) => HttpResponse::Conflict() + .header(PARENT_VERSION_ID_HEADER, parent_version_id.to_string()) + .body(""), + }) } diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 4383a6fbc..0667ec034 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -1,7 +1,17 @@ -use crate::api::ServerState; +use crate::api::{ + ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, +}; use crate::types::{ClientId, VersionId}; use actix_web::{error, get, http::StatusCode, web, HttpResponse, Result}; +/// Get a child version. +/// +/// On succcess, the response is the same sequence of bytes originally sent to the server, +/// with content-type `application/vnd.taskchampion.history-segment`. The `X-Version-Id` and +/// `X-Parent-Version-Id` headers contain the corresponding values. +/// +/// If no such child exists, returns a 404 with no content. +/// Returns other 4xx or 5xx responses on other errors. #[get("/client/{client_id}/get-child-version/{parent_version_id}")] pub(crate) async fn service( data: web::Data, @@ -11,7 +21,14 @@ pub(crate) async fn service( .get_child_version(client_id, parent_version_id) .map_err(|e| error::InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?; if let Some(result) = result { - Ok(HttpResponse::Ok().json(result)) + Ok(HttpResponse::Ok() + .content_type(HISTORY_SEGMENT_CONTENT_TYPE) + .header(VERSION_ID_HEADER, result.version_id.to_string()) + .header( + PARENT_VERSION_ID_HEADER, + result.parent_version_id.to_string(), + ) + .body(result.history_segment)) } else { Err(error::ErrorNotFound("no such version")) } diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index cdab25fca..9dcca18bc 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -4,5 +4,15 @@ use std::sync::Arc; pub(crate) mod add_version; pub(crate) mod get_child_version; +/// The content-type for history segments (opaque blobs of bytes) +pub(crate) const HISTORY_SEGMENT_CONTENT_TYPE: &str = + "application/vnd.taskchampion.history-segment"; + +/// The header names for version ID +pub(crate) const VERSION_ID_HEADER: &str = "X-Version-Id"; + +/// The header names for parent version ID +pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; + /// The type containing a reference to the SyncServer object in the Actix state. pub(crate) type ServerState = Arc>; diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 191908fb2..7caa360f8 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,5 +1,6 @@ use actix_web::{App, HttpServer}; -use std::sync::Arc; +use api::ServerState; +use server::{NullSyncServer, SyncServer}; mod api; mod server; @@ -9,7 +10,8 @@ mod types; #[actix_web::main] async fn main() -> std::io::Result<()> { - let server_state = Arc::new(Box::new(server::NullSyncServer::new())); + let server_box: Box = Box::new(NullSyncServer::new()); + let server_state = ServerState::new(server_box); HttpServer::new(move || { App::new() diff --git a/sync-server/src/server/mod.rs b/sync-server/src/server/mod.rs index df4377366..71961468b 100644 --- a/sync-server/src/server/mod.rs +++ b/sync-server/src/server/mod.rs @@ -2,7 +2,7 @@ use crate::types::{AddVersionResult, ClientId, GetVersionResult, HistorySegment, use failure::Fallible; use taskchampion::Uuid; -pub(crate) trait SyncServer { +pub(crate) trait SyncServer: Sync + Send { fn get_child_version( &self, client_id: ClientId, @@ -13,7 +13,7 @@ pub(crate) trait SyncServer { &self, client_id: ClientId, parent_version_id: VersionId, - history_segment: &HistorySegment, + history_segment: HistorySegment, ) -> Fallible; } @@ -45,8 +45,9 @@ impl SyncServer for NullSyncServer { &self, _client_id: ClientId, _parent_version_id: VersionId, - _history_segment: &HistorySegment, + _history_segment: HistorySegment, ) -> Fallible { - Ok(AddVersionResult::Ok(Uuid::new_v4())) + //Ok(AddVersionResult::Ok(Uuid::new_v4())) + Ok(AddVersionResult::ExpectedParentVersion(Uuid::new_v4())) } } diff --git a/sync-server/src/types.rs b/sync-server/src/types.rs index f4b28901d..69dbe2fbc 100644 --- a/sync-server/src/types.rs +++ b/sync-server/src/types.rs @@ -1,4 +1,3 @@ -use serde::{Deserialize, Serialize}; use taskchampion::Uuid; pub(crate) type HistorySegment = Vec; @@ -6,7 +5,6 @@ pub(crate) type ClientId = Uuid; pub(crate) type VersionId = Uuid; /// Response to get_child_version -#[derive(Serialize, Deserialize)] pub(crate) struct GetVersionResult { pub(crate) version_id: Uuid, pub(crate) parent_version_id: Uuid, @@ -14,7 +12,6 @@ pub(crate) struct GetVersionResult { } /// Response to add_version -#[derive(Serialize, Deserialize)] pub(crate) enum AddVersionResult { /// OK, version added with the given ID Ok(VersionId), From 2457d8bc435079f964eea1c26148f96d4c0e24fe Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 11:34:04 -0500 Subject: [PATCH 083/548] move types in crate::types to crate::server --- sync-server/src/api/add_version.rs | 2 +- sync-server/src/api/get_child_version.rs | 2 +- sync-server/src/main.rs | 1 - sync-server/src/server/mod.rs | 19 ++++++++++++++++++- sync-server/src/types.rs | 20 -------------------- 5 files changed, 20 insertions(+), 24 deletions(-) delete mode 100644 sync-server/src/types.rs diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 40cb5fdd0..83f010ef5 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -1,7 +1,7 @@ use crate::api::{ ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::types::{AddVersionResult, ClientId, VersionId}; +use crate::server::{AddVersionResult, ClientId, VersionId}; use actix_web::{ error, http::StatusCode, post, web, HttpMessage, HttpRequest, HttpResponse, Result, }; diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 0667ec034..4056408ce 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -1,7 +1,7 @@ use crate::api::{ ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::types::{ClientId, VersionId}; +use crate::server::{ClientId, VersionId}; use actix_web::{error, get, http::StatusCode, web, HttpResponse, Result}; /// Get a child version. diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 7caa360f8..880ce7b27 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -4,7 +4,6 @@ use server::{NullSyncServer, SyncServer}; mod api; mod server; -mod types; // TODO: use hawk to sign requests diff --git a/sync-server/src/server/mod.rs b/sync-server/src/server/mod.rs index 71961468b..5bda024f0 100644 --- a/sync-server/src/server/mod.rs +++ b/sync-server/src/server/mod.rs @@ -1,7 +1,24 @@ -use crate::types::{AddVersionResult, ClientId, GetVersionResult, HistorySegment, VersionId}; use failure::Fallible; use taskchampion::Uuid; +pub(crate) type HistorySegment = Vec; +pub(crate) type ClientId = Uuid; +pub(crate) type VersionId = Uuid; + +/// Response to get_child_version +pub(crate) struct GetVersionResult { + pub(crate) version_id: Uuid, + pub(crate) parent_version_id: Uuid, + pub(crate) history_segment: HistorySegment, +} + +/// Response to add_version +pub(crate) enum AddVersionResult { + /// OK, version added with the given ID + Ok(VersionId), + /// Rejected; expected a version with the given parent version + ExpectedParentVersion(VersionId), +} pub(crate) trait SyncServer: Sync + Send { fn get_child_version( &self, diff --git a/sync-server/src/types.rs b/sync-server/src/types.rs deleted file mode 100644 index 69dbe2fbc..000000000 --- a/sync-server/src/types.rs +++ /dev/null @@ -1,20 +0,0 @@ -use taskchampion::Uuid; - -pub(crate) type HistorySegment = Vec; -pub(crate) type ClientId = Uuid; -pub(crate) type VersionId = Uuid; - -/// Response to get_child_version -pub(crate) struct GetVersionResult { - pub(crate) version_id: Uuid, - pub(crate) parent_version_id: Uuid, - pub(crate) history_segment: HistorySegment, -} - -/// Response to add_version -pub(crate) enum AddVersionResult { - /// OK, version added with the given ID - Ok(VersionId), - /// Rejected; expected a version with the given parent version - ExpectedParentVersion(VersionId), -} From 2dae271851de9aed73e80a084d3388458c66d5a0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 12:13:00 -0500 Subject: [PATCH 084/548] build an in-memory sync server implementation --- sync-server/src/main.rs | 4 +- sync-server/src/server/inmemory.rs | 108 +++++++++++++++++++++++++++++ sync-server/src/server/mod.rs | 42 ++--------- 3 files changed, 117 insertions(+), 37 deletions(-) create mode 100644 sync-server/src/server/inmemory.rs diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 880ce7b27..a8e4cc9de 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,6 +1,6 @@ use actix_web::{App, HttpServer}; use api::ServerState; -use server::{NullSyncServer, SyncServer}; +use server::{InMemorySyncServer, SyncServer}; mod api; mod server; @@ -9,7 +9,7 @@ mod server; #[actix_web::main] async fn main() -> std::io::Result<()> { - let server_box: Box = Box::new(NullSyncServer::new()); + let server_box: Box = Box::new(InMemorySyncServer::new()); let server_state = ServerState::new(server_box); HttpServer::new(move || { diff --git a/sync-server/src/server/inmemory.rs b/sync-server/src/server/inmemory.rs new file mode 100644 index 000000000..37302282e --- /dev/null +++ b/sync-server/src/server/inmemory.rs @@ -0,0 +1,108 @@ +use super::{ + AddVersionResult, ClientId, GetVersionResult, HistorySegment, SyncServer, VersionId, + NO_VERSION_ID, +}; +use failure::Fallible; +use std::collections::HashMap; +use std::sync::{Mutex, RwLock}; +use taskchampion::Uuid; + +/// An in-memory server backend that can be useful for testing. +pub(crate) struct InMemorySyncServer { + clients: RwLock>>, +} + +struct Version { + version_id: VersionId, + history_segment: HistorySegment, +} + +struct Client { + latest_version_id: VersionId, + // NOTE: indexed by parent_version_id! + versions: HashMap, +} + +impl InMemorySyncServer { + pub(crate) fn new() -> Self { + Self { + clients: RwLock::new(HashMap::new()), + } + } +} + +impl SyncServer for InMemorySyncServer { + fn get_child_version( + &self, + client_id: ClientId, + parent_version_id: VersionId, + ) -> Fallible> { + let clients = self.clients.read().expect("poisoned lock"); + if let Some(client) = clients.get(&client_id) { + let client = client.lock().expect("poisoned lock"); + if let Some(version) = client.versions.get(&parent_version_id) { + return Ok(Some(GetVersionResult { + version_id: version.version_id, + parent_version_id, + history_segment: version.history_segment.clone(), + })); + } + } + Ok(None) + } + + fn add_version( + &self, + client_id: ClientId, + parent_version_id: VersionId, + history_segment: HistorySegment, + ) -> Fallible { + let mut clients = self.clients.write().expect("poisoned lock"); + if let Some(client) = clients.get_mut(&client_id) { + let mut client = client.lock().expect("poisoned lock"); + if client.latest_version_id != NO_VERSION_ID { + if parent_version_id != client.latest_version_id { + return Ok(AddVersionResult::ExpectedParentVersion( + client.latest_version_id, + )); + } + } + + // invent a new ID for this version + let version_id = Uuid::new_v4(); + + client.versions.insert( + parent_version_id, + Version { + version_id, + history_segment, + }, + ); + client.latest_version_id = version_id; + + Ok(AddVersionResult::Ok(version_id)) + } else { + // new client, so insert a client with just this new version + + let latest_version_id = Uuid::new_v4(); + let mut versions = HashMap::new(); + versions.insert( + parent_version_id, + Version { + version_id: latest_version_id, + history_segment, + }, + ); + + clients.insert( + client_id, + Mutex::new(Client { + latest_version_id, + versions, + }), + ); + + Ok(AddVersionResult::Ok(latest_version_id)) + } + } +} diff --git a/sync-server/src/server/mod.rs b/sync-server/src/server/mod.rs index 5bda024f0..6d8593c03 100644 --- a/sync-server/src/server/mod.rs +++ b/sync-server/src/server/mod.rs @@ -1,6 +1,13 @@ use failure::Fallible; use taskchampion::Uuid; +mod inmemory; + +pub(crate) use inmemory::InMemorySyncServer; + +/// The distinguished value for "no version" +pub const NO_VERSION_ID: VersionId = Uuid::nil(); + pub(crate) type HistorySegment = Vec; pub(crate) type ClientId = Uuid; pub(crate) type VersionId = Uuid; @@ -33,38 +40,3 @@ pub(crate) trait SyncServer: Sync + Send { history_segment: HistorySegment, ) -> Fallible; } - -// TODO: temporary -/// A "null" sync server's implementation; HTTP API methods call through to methods on a single -/// instance of this type. -pub(crate) struct NullSyncServer {} - -impl NullSyncServer { - pub(crate) fn new() -> Self { - Self {} - } -} - -impl SyncServer for NullSyncServer { - fn get_child_version( - &self, - _client_id: ClientId, - parent_version_id: VersionId, - ) -> Fallible> { - Ok(Some(GetVersionResult { - version_id: Uuid::new_v4(), - parent_version_id, - history_segment: b"abcd".to_vec(), - })) - } - - fn add_version( - &self, - _client_id: ClientId, - _parent_version_id: VersionId, - _history_segment: HistorySegment, - ) -> Fallible { - //Ok(AddVersionResult::Ok(Uuid::new_v4())) - Ok(AddVersionResult::ExpectedParentVersion(Uuid::new_v4())) - } -} From 3fb2327a5b08ae9835d5bc164c9fc85d5fdeabc4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 14:10:46 -0500 Subject: [PATCH 085/548] add docs for replica/server protocol --- docs/src/SUMMARY.md | 4 +- docs/src/sync-model.md | 128 +++++++++++++++++++++++++++++++++++++ docs/src/sync-protocol.md | 92 ++++++++++++++++++++++++++ docs/src/sync.md | 131 ++------------------------------------ 4 files changed, 228 insertions(+), 127 deletions(-) create mode 100644 docs/src/sync-model.md create mode 100644 docs/src/sync-protocol.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 4555c20c6..91a180ab5 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -7,5 +7,7 @@ - [Replica Storage](./storage.md) - [Task Database](./taskdb.md) - [Tasks](./tasks.md) -- [Synchronization](./sync.md) +- [Synchronization and the Sync Server](./sync.md) + - [Synchronization Model](./sync-model.md) + * [Server-Replica Protocol](./sync-protocol.md) - [Planned Functionality](./plans.md) diff --git a/docs/src/sync-model.md b/docs/src/sync-model.md new file mode 100644 index 000000000..691312efa --- /dev/null +++ b/docs/src/sync-model.md @@ -0,0 +1,128 @@ +# Synchronization Model + +The [task database](./taskdb.md) also implements synchronization. +Synchronization occurs between disconnected replicas, mediated by a server. +The replicas never communicate directly with one another. +The server does not have access to the task data; it sees only opaque blobs of data with a small amount of metadata. + +The synchronization process is a critical part of the task database's functionality, and it cannot function efficiently without occasional synchronization operations + +## Operational Transforms + +Synchronization is based on [operational transformation](https://en.wikipedia.org/wiki/Operational_transformation). +This section will assume some familiarity with the concept. + +## State and Operations + +At a given time, the set of tasks in a replica's storage is the essential "state" of that replica. +All modifications to that state occur via operations, as defined in [Replica Storage](./storage.md). +We can draw a network, or graph, with the nodes representing states and the edges representing operations. +For example: + +```text + o -- State: {abc-d123: 'get groceries', priority L} + | + | -- Operation: set abc-d123 priority to H + | + o -- State: {abc-d123: 'get groceries', priority H} +``` + +For those familiar with distributed version control systems, a state is analogous to a revision, while an operation is analogous to a commit. + +Fundamentally, synchronization involves all replicas agreeing on a single, linear sequence of operations and the state that those operations create. +Since the replicas are not connected, each may have additional operations that have been applied locally, but which have not yet been agreed on. +The synchronization process uses operational transformation to "linearize" those operations. +This process is analogous (vaguely) to rebasing a sequence of Git commits. + +### Versions + +Occasionally, database states are given a name (that takes the form of a UUID). +The system as a whole (all replicas) constructs a branch-free sequence of versions and the operations that separate each version from the next. +The version with the nil UUID is implicitly the empty database. + +The server stores the operations to change a state from a "parent" version to a "child" version, and provides that information as needed to replicas. +Replicas use this information to update their local task databases, and to generate new versions to send to the server. + +Replicas generate a new version to transmit local changes to the server. +The changes are represented as a sequence of operations with the state resulting from the final operation corresponding to the version. +In order to keep the versions in a single sequence, the server will only accept a proposed version from a replica if its parent version matches the latest version on the server. + +In the non-conflict case (such as with a single replica), then, a replica's synchronization process involves gathering up the operations it has accumulated since its last synchronization; bundling those operations into a version; and sending that version to the server. + +### Replica Invariant + +The replica's [storage](./storage.md) contains the current state in `tasks`, the as-yet un-synchronized operations in `operations`, and the last version at which synchronization occurred in `base_version`. + +The replica's un-synchronized operations are already reflected in its local `tasks`, so the following invariant holds: + +> Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical +> to `tasks`. + +### Transformation + +When the latest version on the server contains operations that are not present in the replica, then the states have diverged. +For example: + +```text + o -- version N + w|\a + o o + x| \b + o o + y| \c + o o -- replica's local state + z| + o -- version N+1 +``` + +(diagram notation: `o` designates a state, lower-case letters designate operations, and versions are presented as if they were numbered sequentially) + +In this situation, the replica must "rebase" the local operations onto the latest version from the server and try again. +This process is performed using operational transformation (OT). +The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the replica can apply to its local task database to reach the same state +Continuing the example above, the resulting operations are shown with `'`: + +```text + o -- version N + w|\a + o o + x| \b + o o + y| \c + o o -- replica's intermediate local state + z| |w' + o-N+1 o + a'\ |x' + o o + b'\ |y' + o o + c'\|z' + o -- version N+2 +``` + +The replica applies w' through z' locally, and sends a' through c' to the server as the operations to generate version N+2. +Either path through this graph, a-b-c-w'-x'-y'-z' or a'-b'-c'-w-x-y-z, must generate *precisely* the same final state at version N+2. +Careful selection of the operations and the transformation function ensure this. + +See the comments in the source code for the details of how this transformation process is implemented. + +## Synchronization Process + +To perform a synchronization, the replica first requests the child version of `base_version` from the server (GetChildVersion). +It applies that version to its local `tasks`, rebases its local `operations` as described above, and updates `base_version`. +The replica repeats this process until the server indicates no additional child versions exist. +If there are no un-synchronized local operations, the process is complete. + +Otherwise, the replica creates a new version containing its local operations, giving its `base_version` as the parent version, and transmits that to the server (AddVersion). +In most cases, this will succeed, but if another replica has created a new version in the interim, then the new version will conflict with that other replica's new version and the server will respond with the new expected parent version. +In this case, the process repeats. +If the server indicates a conflict twice with the same expected base version, that is an indication that the replica has diverged (something serious has gone wrong). + +## Servers + +A replica depends on periodic synchronization for performant operation. +Without synchronization, its list of pending operations would grow indefinitely, and tasks could never be expired. +So all replicas, even "singleton" replicas which do not replicate task data with any other replica, must synchronize periodically. + +TaskChampion provides a `LocalServer` for this purpose. +It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally, all within the `task` binary. diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md new file mode 100644 index 000000000..9a5caa247 --- /dev/null +++ b/docs/src/sync-protocol.md @@ -0,0 +1,92 @@ +# Server-Replica Protocol + +The server-replica protocol is defined abstractly in terms of request/response transactions from the replica to the server. +This is made concrete in an HTTP representation. + +The protocol builds on the model presented in the previous chapter, and in particular on the synchronization process. + +## Clients + +From the server's perspective, replicas are indistinguishable, so this protocol uses the term "client" to refer generically to all replicas replicating a single task history. + +## Server + +For each client, the server is responsible for storing the task history, in the form of a branch-free sequence of versions. + +For each client, it stores a set of versions as well as the latest version ID, defaulting to the nil UUID. +Each version has a version ID, a parent version ID, and a history segment (opaque data containing the operations for that version). +The server should maintain the following invariants: + +1. Given a client c, c.latestVersion is nil or exists in the set of versions. +1. Given versions v1 and v2 for a client, with v1.versionId != v2.versionId and v1.parentVersionId != nil, v1.parentVersionId != v2.parentVersionId. + In other words, versions do not branch. + +Note that versions form a linked list beginning with the version stored in he client. +This linked list need not continue back to a version with v.parentVersionId = nil. +It may end at any point when v.parentVersionId is not found in the set of Versions. +This observation allows the server to discard older versions. + +## Transactions + +### AddVersion + +The AddVersion transaction requests that the server add a new version to the client's task history. +The request contains the following; + + * parent version ID + * history segment + +The server determines whether the new version is acceptable, atomically with respect to other requests for the same client. +If it has no versions for the client, it accepts the version. +If it already has one or more versions for the client, then it accepts the version only if the given parent version ID matches its stored latest parent ID. + +If the version is accepted, the server generates a new version ID for it. +The version is added to the set of versions for the client, the client's latest version ID is set to the new version ID. +The new version ID is returned in the response to the client. + +If the version is not accepted, the server makes no changes, but responds to the client with a conflict indication containing the latest version ID. +The client may then "rebase" its operations and try again. +Note that if a client receives two conflict responses with the same parent version ID, it is an indication that the client's version history has diverged from that on the server. + +### GetChildVersion + +The GetChildVersion transaction is a read-only request for a version. +The request consists of a parent version ID. +The server searches its set of versions for a version with the given parent ID. +If found, it returns the version's + + * version ID, + * parent version ID (matching that in the request), and + * history segment. + +If not found, the server returns a negative response. + +## HTTP Representation + +The transactions above are realized for an HTTP server at `` using the HTTP requests and responses described here. +The `origin` *should* be an HTTPS endpoint on general principle, but nothing in the functonality or security of the protocol depends on connection encryption. + +The replica identifies itself to the server using a `clientId` in the form of a UUID. + +### AddVersion + +The request is a `POST` to `/client//add-version/`. +The request body contains the history segment, optionally encoded using any encoding supported by actix-web. +The content-type must be `application/vnd.taskchampion.history-segment`. + +The success response is a 200 OK with an empty body. +The new version ID appears in the `X-Version-Id` header. + +On conflict, the response is a 409 CONFLICT with an empty body. +The expected parent version ID appears in the `X-Parent-Version-Id` header. + +Other error responses (4xx or 5xx) may be returned and should be treated appropriately to their meanings in the HTTP specification. + +### GetChildVersion + +The request is a `GET` to `/client//get-child-version/`. +The response is 404 NOT FOUND if no such version exists. +Otherwise, the response is a 200 OK. +The version's history segment is returned in the response body, with content-type `application/vnd.taskchampion.history-segment`. +The version ID appears in the `X-Version-Id` header. +The response body may be encoded, in accordance with any `Accept-Encoding` header in the request. diff --git a/docs/src/sync.md b/docs/src/sync.md index cd2621cdc..fed75d17f 100644 --- a/docs/src/sync.md +++ b/docs/src/sync.md @@ -1,128 +1,7 @@ -# Synchronization +# Synchronization and the Sync Server -The [task database](./taskdb.md) also implements synchronization. -Synchronization occurs between disconnected replicas, mediated by a server. -The replicas never communicate directly with one another. -The server does not have access to the task data; it sees only opaque blobs of data with a small amount of metadata. +This section covers *synchronization* of *replicas* containing the same set of tasks. +A replica is can perform all operations locally without connecting to a sync server, then share those operations with other replicas when it connects. +Sync is a critical feature of TaskChampion, allowing users to consult and update the same task list on multiple devices, without requiring constant connection. -The synchronization process is a critical part of the task database's functionality, and it cannot function efficiently without occasional synchronization operations - -## Operational Transformations - -Synchronization is based on [operational transformation](https://en.wikipedia.org/wiki/Operational_transformation). -This section will assume some familiarity with the concept. - -## State and Operations - -At a given time, the set of tasks in a replica's storage is the essential "state" of that replica. -All modifications to that state occur via operations, as defined in [Replica Storage](./storage.md). -We can draw a network, or graph, with the nodes representing states and the edges representing operations. -For example: - -```text - o -- State: {abc-d123: 'get groceries', priority L} - | - | -- Operation: set abc-d123 priority to H - | - o -- State: {abc-d123: 'get groceries', priority H} -``` - -For those familiar with distributed version control systems, a state is analogous to a revision, while an operation is analogous to a commit. - -Fundamentally, synchronization involves all replicas agreeing on a single, linear sequence of operations and the state that those operations create. -Since the replicas are not connected, each may have additional operations that have been applied locally, but which have not yet been agreed on. -The synchronization process uses operational transformation to "linearize" those operations. -This process is analogous (vaguely) to rebasing a sequence of Git commits. - -### Versions - -Occasionally, database states are given a name (that takes the form of a UUID). -The system as a whole (all replicas) constructs a branch-free sequence of versions and the operations that separate each version from the next. -The version with the nil UUID is implicitly the empty database. - -The server stores the operations to change a state from a "parent" version to a "child" version, and provides that information as needed to replicas. -Replicas use this information to update their local task databases, and to generate new versions to send to the server. - -Replicas generate a new version to transmit local changes to the server. -The changes are represented as a sequence of operations with the state resulting from the final operation corresponding to the version. -In order to keep the versions in a single sequence, the server will only accept a proposed version from a replica if its parent version matches the latest version on the server. - -In the non-conflict case (such as with a single replica), then, a replica's synchronization process involves gathering up the operations it has accumulated since its last synchronization; bundling those operations into a version; and sending that version to the server. - -### Replica Invariant - -The replica's [storage](./storage.md) contains the current state in `tasks`, the as-yet un-synchronized operations in `operations`, and the last version at which synchronization occurred in `base_version`. - -The replica's un-synchronized operations are already reflected in its local `tasks`, so the following invariant holds: - -> Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical -> to `tasks`. - -### Transformation - -When the latest version on the server contains operations that are not present in the replica, then the states have diverged. -For example: - -```text - o -- version N - w|\a - o o - x| \b - o o - y| \c - o o -- replica's local state - z| - o -- version N+1 -``` - -(diagram notation: `o` designates a state, lower-case letters designate operations, and versions are presented as if they were numbered sequentially) - -In this situation, the replica must "rebase" the local operations onto the latest version from the server and try again. -This process is performed using operational transformation (OT). -The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the replica can apply to its local task database to reach the same state -Continuing the example above, the resulting operations are shown with `'`: - -```text - o -- version N - w|\a - o o - x| \b - o o - y| \c - o o -- replica's intermediate local state - z| |w' - o-N+1 o - a'\ |x' - o o - b'\ |y' - o o - c'\|z' - o -- version N+2 -``` - -The replica applies w' through z' locally, and sends a' through c' to the server as the operations to generate version N+2. -Either path through this graph, a-b-c-w'-x'-y'-z' or a'-b'-c'-w-x-y-z, must generate *precisely* the same final state at version N+2. -Careful selection of the operations and the transformation function ensure this. - -See the comments in the source code for the details of how this transformation process is implemented. - -## Synchronization Process - -To perform a synchronization, the replica first requests the child version of `base_version` from the server (`get_child_version`). -It applies that version to its local `tasks`, rebases its local `operations` as described above, and updates `base_version`. -The replica repeats this process until the server indicates no additional child versions exist. -If there are no un-synchronized local operations, the process is complete. - -Otherwise, the replica creates a new version containing its local operations, giving its `base_version` as the parent version, and transmits that to the server (`add_version`). -In most cases, this will succeed, but if another replica has created a new version in the interim, then the new version will conflict with that other replica's new version and the server will respond with the new expected parent version. -In this case, the process repeats. -If the server indicates a conflict twice with the same expected base version, that is an indication that the replica has diverged (something serious has gone wrong). - -## Servers - -A replica depends on periodic synchronization for performant operation. -Without synchronization, its list of pending operations would grow indefinitely, and tasks could never be expired. -So all replicas, even "singleton" replicas which do not replicate task data with any other replica, must synchronize periodically. - -TaskChampion provides a `LocalServer` for this purpose. -It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally, all within the `task` binary. +This is a complex topic, and the section is broken into several chapters, beginning at the lower levels of the implementation and working up. From 7472749fee861b3cc4db463a29069a6cdc47dbcf Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 17:27:17 -0500 Subject: [PATCH 086/548] add tests for API methods --- Cargo.lock | 1 + sync-server/Cargo.toml | 5 +- sync-server/src/api/add_version.rs | 123 +++++++++++++++++++++++ sync-server/src/api/get_child_version.rs | 78 ++++++++++++++ sync-server/src/api/mod.rs | 11 +- sync-server/src/main.rs | 35 +++++-- sync-server/src/server/mod.rs | 3 + sync-server/src/test.rs | 57 +++++++++++ 8 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 sync-server/src/test.rs diff --git a/Cargo.lock b/Cargo.lock index 4c744d952..103d2e8ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2035,6 +2035,7 @@ dependencies = [ name = "sync-server" version = "0.1.0" dependencies = [ + "actix-rt", "actix-web", "failure", "futures", diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 3c638aa7c..e9df49ac7 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -9,5 +9,8 @@ edition = "2018" [dependencies] actix-web = "3.3.0" failure = "0.1.8" -futures = "0.3.8" taskchampion = { path = "../taskchampion" } +futures = "0.3.8" + +[dev-dependencies] +actix-rt = "1.1.1" diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 83f010ef5..4dc8393cf 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -43,6 +43,10 @@ pub(crate) async fn service( body.extend_from_slice(&chunk); } + if body.is_empty() { + return Err(error::ErrorBadRequest("Empty body")); + } + let result = data .add_version(client_id, parent_version_id, body.to_vec()) .map_err(|e| error::InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?; @@ -55,3 +59,122 @@ pub(crate) async fn service( .body(""), }) } + +#[cfg(test)] +mod test { + use super::*; + use crate::api::ServerState; + use crate::app_scope; + use crate::server::SyncServer; + use crate::test::TestServer; + use actix_web::{test, App}; + use taskchampion::Uuid; + + #[actix_rt::test] + async fn test_success() { + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(TestServer { + expected_client_id: client_id, + av_expected_parent_version_id: parent_version_id, + av_expected_history_segment: b"abcd".to_vec(), + av_result: Some(AddVersionResult::Ok(version_id)), + ..Default::default() + }); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header( + "Content-Type", + "application/vnd.taskchampion.history-segment", + ) + .set_payload(b"abcd".to_vec()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get("X-Version-Id").unwrap(), + &version_id.to_string() + ); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); + } + + #[actix_rt::test] + async fn test_conflict() { + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(TestServer { + expected_client_id: client_id, + av_expected_parent_version_id: parent_version_id, + av_expected_history_segment: b"abcd".to_vec(), + av_result: Some(AddVersionResult::ExpectedParentVersion(version_id)), + ..Default::default() + }); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header( + "Content-Type", + "application/vnd.taskchampion.history-segment", + ) + .set_payload(b"abcd".to_vec()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::CONFLICT); + assert_eq!(resp.headers().get("X-Version-Id"), None); + assert_eq!( + resp.headers().get("X-Parent-Version-Id").unwrap(), + &version_id.to_string() + ); + } + + #[actix_rt::test] + async fn test_bad_content_type() { + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(TestServer { + ..Default::default() + }); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header("Content-Type", "not/correct") + .set_payload(b"abcd".to_vec()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[actix_rt::test] + async fn test_empty_body() { + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(TestServer { + ..Default::default() + }); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header( + "Content-Type", + "application/vnd.taskchampion.history-segment", + ) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } +} diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 4056408ce..b16f5219b 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -33,3 +33,81 @@ pub(crate) async fn service( Err(error::ErrorNotFound("no such version")) } } + +#[cfg(test)] +mod test { + use super::*; + use crate::api::ServerState; + use crate::app_scope; + use crate::server::{GetVersionResult, SyncServer}; + use crate::test::TestServer; + use actix_web::{test, App}; + use taskchampion::Uuid; + + #[actix_rt::test] + async fn test_success() { + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(TestServer { + expected_client_id: client_id, + gcv_expected_parent_version_id: parent_version_id, + gcv_result: Some(GetVersionResult { + version_id: version_id, + parent_version_id: parent_version_id, + history_segment: b"abcd".to_vec(), + }), + ..Default::default() + }); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!( + "/client/{}/get-child-version/{}", + client_id, parent_version_id + ); + let req = test::TestRequest::get().uri(&uri).to_request(); + let mut resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get("X-Version-Id").unwrap(), + &version_id.to_string() + ); + assert_eq!( + resp.headers().get("X-Parent-Version-Id").unwrap(), + &parent_version_id.to_string() + ); + assert_eq!( + resp.headers().get("Content-Type").unwrap(), + &"application/vnd.taskchampion.history-segment".to_string() + ); + + use futures::StreamExt; + let (bytes, _) = resp.take_body().into_future().await; + assert_eq!(bytes.unwrap().unwrap().as_ref(), b"abcd"); + } + + #[actix_rt::test] + async fn test_not_found() { + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(TestServer { + expected_client_id: client_id, + gcv_expected_parent_version_id: parent_version_id, + gcv_result: None, + ..Default::default() + }); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!( + "/client/{}/get-child-version/{}", + client_id, parent_version_id + ); + let req = test::TestRequest::get().uri(&uri).to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + assert_eq!(resp.headers().get("X-Version-Id"), None); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); + } +} diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index 9dcca18bc..c58d2420b 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -1,8 +1,9 @@ use crate::server::SyncServer; +use actix_web::{web, Scope}; use std::sync::Arc; -pub(crate) mod add_version; -pub(crate) mod get_child_version; +mod add_version; +mod get_child_version; /// The content-type for history segments (opaque blobs of bytes) pub(crate) const HISTORY_SEGMENT_CONTENT_TYPE: &str = @@ -16,3 +17,9 @@ pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; /// The type containing a reference to the SyncServer object in the Actix state. pub(crate) type ServerState = Arc>; + +pub(crate) fn api_scope() -> Scope { + web::scope("") + .service(get_child_version::service) + .service(add_version::service) +} diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index a8e4cc9de..3123c80db 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,24 +1,37 @@ -use actix_web::{App, HttpServer}; -use api::ServerState; +use actix_web::{get, web, App, HttpServer, Responder, Scope}; +use api::{api_scope, ServerState}; use server::{InMemorySyncServer, SyncServer}; mod api; mod server; +#[cfg(test)] +mod test; + // TODO: use hawk to sign requests +#[get("/")] +async fn index() -> impl Responder { + // TODO: add version here + "TaskChampion sync server" +} + +/// Return a scope defining the URL rules for this server, with access to +/// the given ServerState. +pub(crate) fn app_scope(server_state: ServerState) -> Scope { + web::scope("") + .data(server_state) + .service(index) + .service(api_scope()) +} + #[actix_web::main] async fn main() -> std::io::Result<()> { let server_box: Box = Box::new(InMemorySyncServer::new()); let server_state = ServerState::new(server_box); - HttpServer::new(move || { - App::new() - .data(server_state.clone()) - .service(api::get_child_version::service) - .service(api::add_version::service) - }) - .bind("127.0.0.1:8080")? - .run() - .await + HttpServer::new(move || App::new().service(app_scope(server_state.clone()))) + .bind("127.0.0.1:8080")? + .run() + .await } diff --git a/sync-server/src/server/mod.rs b/sync-server/src/server/mod.rs index 6d8593c03..7768c0c08 100644 --- a/sync-server/src/server/mod.rs +++ b/sync-server/src/server/mod.rs @@ -13,6 +13,7 @@ pub(crate) type ClientId = Uuid; pub(crate) type VersionId = Uuid; /// Response to get_child_version +#[derive(Clone)] pub(crate) struct GetVersionResult { pub(crate) version_id: Uuid, pub(crate) parent_version_id: Uuid, @@ -20,12 +21,14 @@ pub(crate) struct GetVersionResult { } /// Response to add_version +#[derive(Clone)] pub(crate) enum AddVersionResult { /// OK, version added with the given ID Ok(VersionId), /// Rejected; expected a version with the given parent version ExpectedParentVersion(VersionId), } + pub(crate) trait SyncServer: Sync + Send { fn get_child_version( &self, diff --git a/sync-server/src/test.rs b/sync-server/src/test.rs new file mode 100644 index 000000000..b9f68a66d --- /dev/null +++ b/sync-server/src/test.rs @@ -0,0 +1,57 @@ +use crate::api::ServerState; +use crate::app_scope; +use crate::server::{ + AddVersionResult, ClientId, GetVersionResult, HistorySegment, SyncServer, VersionId, +}; +use actix_web::{test, App}; +use failure::Fallible; + +#[derive(Default)] +pub(crate) struct TestServer { + /// test server will panic if this is not given + pub expected_client_id: ClientId, + + pub gcv_expected_parent_version_id: VersionId, + pub gcv_result: Option, + + pub av_expected_parent_version_id: VersionId, + pub av_expected_history_segment: HistorySegment, + pub av_result: Option, +} + +impl SyncServer for TestServer { + fn get_child_version( + &self, + client_id: ClientId, + parent_version_id: VersionId, + ) -> Fallible> { + assert_eq!(client_id, self.expected_client_id); + assert_eq!(parent_version_id, self.gcv_expected_parent_version_id); + Ok(self.gcv_result.clone()) + } + + fn add_version( + &self, + client_id: ClientId, + parent_version_id: VersionId, + history_segment: HistorySegment, + ) -> Fallible { + assert_eq!(client_id, self.expected_client_id); + assert_eq!(parent_version_id, self.av_expected_parent_version_id); + assert_eq!(history_segment, self.av_expected_history_segment); + Ok(self.av_result.clone().unwrap()) + } +} + +#[actix_rt::test] +async fn test_index_get() { + let server_box: Box = Box::new(TestServer { + ..Default::default() + }); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let req = test::TestRequest::get().uri("/").to_request(); + let resp = test::call_service(&mut app, req).await; + assert!(resp.status().is_success()); +} From fb22b9686ff32477220d6e6b7d190d8233af6d21 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 19:19:51 -0500 Subject: [PATCH 087/548] refactor sync server to use pluggable storage ..with a fixed implementation of the replica / server protocol logic. There isn't much logic yet, and there's a lot of boilerplate to take care of, so this looks a little lopsided, but I'm confident this is the right structure for this code's future. --- sync-server/src/api/add_version.rs | 110 +++++++++++++++-------- sync-server/src/api/get_child_version.rs | 97 ++++++++++++++------ sync-server/src/api/mod.rs | 13 ++- sync-server/src/main.rs | 27 ++++-- sync-server/src/server/inmemory.rs | 108 ---------------------- sync-server/src/server/mod.rs | 20 ----- sync-server/src/storage/inmemory.rs | 90 +++++++++++++++++++ sync-server/src/storage/mod.rs | 56 ++++++++++++ sync-server/src/test.rs | 57 ------------ 9 files changed, 319 insertions(+), 259 deletions(-) delete mode 100644 sync-server/src/server/inmemory.rs create mode 100644 sync-server/src/storage/inmemory.rs create mode 100644 sync-server/src/storage/mod.rs delete mode 100644 sync-server/src/test.rs diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 4dc8393cf..2413c5fce 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -1,11 +1,13 @@ use crate::api::{ - ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, -}; -use crate::server::{AddVersionResult, ClientId, VersionId}; -use actix_web::{ - error, http::StatusCode, post, web, HttpMessage, HttpRequest, HttpResponse, Result, + failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, + VERSION_ID_HEADER, }; +use crate::server::{AddVersionResult, ClientId, HistorySegment, VersionId, NO_VERSION_ID}; +use crate::storage::{Client, StorageTxn}; +use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; +use failure::Fallible; use futures::StreamExt; +use taskchampion::Uuid; /// Max history segment size: 100MB const MAX_SIZE: usize = 100 * 1024 * 1024; @@ -23,7 +25,7 @@ const MAX_SIZE: usize = 100 * 1024 * 1024; #[post("/client/{client_id}/add-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, - data: web::Data, + server_state: web::Data, web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, mut payload: web::Payload, ) -> Result { @@ -47,9 +49,18 @@ pub(crate) async fn service( return Err(error::ErrorBadRequest("Empty body")); } - let result = data - .add_version(client_id, parent_version_id, body.to_vec()) - .map_err(|e| error::InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?; + // note that we do not open the transaction until the body has been read + // completely, to avoid blocking other storage access while that data is + // in transit. + let mut txn = server_state.txn().map_err(failure_to_ise)?; + + let client = txn + .get_client(client_id) + .map_err(failure_to_ise)? + .ok_or_else(|| error::ErrorNotFound("no such client"))?; + + let result = add_version(txn, client_id, client, parent_version_id, body.to_vec()) + .map_err(failure_to_ise)?; Ok(match result { AddVersionResult::Ok(version_id) => HttpResponse::Ok() .header(VERSION_ID_HEADER, version_id.to_string()) @@ -60,14 +71,37 @@ pub(crate) async fn service( }) } +fn add_version<'a>( + mut txn: Box, + client_id: ClientId, + client: Client, + parent_version_id: VersionId, + history_segment: HistorySegment, +) -> Fallible { + // check if this version is acceptable, under the protection of the transaction + if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { + return Ok(AddVersionResult::ExpectedParentVersion( + client.latest_version_id, + )); + } + + // invent a version ID + let version_id = Uuid::new_v4(); + + // update the DB + txn.add_version(client_id, version_id, parent_version_id, history_segment)?; + txn.set_client_latest_version_id(client_id, version_id)?; + txn.commit()?; + + Ok(AddVersionResult::Ok(version_id)) +} + #[cfg(test)] mod test { - use super::*; use crate::api::ServerState; use crate::app_scope; - use crate::server::SyncServer; - use crate::test::TestServer; - use actix_web::{test, App}; + use crate::storage::{InMemoryStorage, Storage}; + use actix_web::{http::StatusCode, test, App}; use taskchampion::Uuid; #[actix_rt::test] @@ -75,13 +109,15 @@ mod test { let client_id = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - expected_client_id: client_id, - av_expected_parent_version_id: parent_version_id, - av_expected_history_segment: b"abcd".to_vec(), - av_result: Some(AddVersionResult::Ok(version_id)), - ..Default::default() - }); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, Uuid::nil()) + .unwrap(); + } + let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; @@ -96,10 +132,12 @@ mod test { .to_request(); let resp = test::call_service(&mut app, req).await; assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get("X-Version-Id").unwrap(), - &version_id.to_string() - ); + + // the returned version ID is random, but let's check that it's not + // the passed parent version ID, at least + let new_version_id = resp.headers().get("X-Version-Id").unwrap(); + assert!(new_version_id != &version_id.to_string()); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); } @@ -108,13 +146,15 @@ mod test { let client_id = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - expected_client_id: client_id, - av_expected_parent_version_id: parent_version_id, - av_expected_history_segment: b"abcd".to_vec(), - av_result: Some(AddVersionResult::ExpectedParentVersion(version_id)), - ..Default::default() - }); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, version_id) + .unwrap(); + } + let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; @@ -140,9 +180,7 @@ mod test { async fn test_bad_content_type() { let client_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - ..Default::default() - }); + let server_box: Box = Box::new(InMemoryStorage::new()); let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; @@ -160,9 +198,7 @@ mod test { async fn test_empty_body() { let client_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - ..Default::default() - }); + let server_box: Box = Box::new(InMemoryStorage::new()); let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index b16f5219b..84af0b179 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -1,8 +1,11 @@ use crate::api::{ - ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, + failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, + VERSION_ID_HEADER, }; -use crate::server::{ClientId, VersionId}; -use actix_web::{error, get, http::StatusCode, web, HttpResponse, Result}; +use crate::server::{ClientId, GetVersionResult, VersionId}; +use crate::storage::StorageTxn; +use actix_web::{error, get, web, HttpResponse, Result}; +use failure::Fallible; /// Get a child version. /// @@ -14,12 +17,16 @@ use actix_web::{error, get, http::StatusCode, web, HttpResponse, Result}; /// Returns other 4xx or 5xx responses on other errors. #[get("/client/{client_id}/get-child-version/{parent_version_id}")] pub(crate) async fn service( - data: web::Data, + server_state: web::Data, web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, ) -> Result { - let result = data - .get_child_version(client_id, parent_version_id) - .map_err(|e| error::InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?; + let mut txn = server_state.txn().map_err(failure_to_ise)?; + + txn.get_client(client_id) + .map_err(failure_to_ise)? + .ok_or_else(|| error::ErrorNotFound("no such client"))?; + + let result = get_child_version(txn, client_id, parent_version_id).map_err(failure_to_ise)?; if let Some(result) = result { Ok(HttpResponse::Ok() .content_type(HISTORY_SEGMENT_CONTENT_TYPE) @@ -34,14 +41,26 @@ pub(crate) async fn service( } } +fn get_child_version<'a>( + mut txn: Box, + client_id: ClientId, + parent_version_id: VersionId, +) -> Fallible> { + Ok(txn + .get_version_by_parent(client_id, parent_version_id)? + .map(|version| GetVersionResult { + version_id: version.version_id, + parent_version_id: version.parent_version_id, + history_segment: version.history_segment, + })) +} + #[cfg(test)] mod test { - use super::*; use crate::api::ServerState; use crate::app_scope; - use crate::server::{GetVersionResult, SyncServer}; - use crate::test::TestServer; - use actix_web::{test, App}; + use crate::storage::{InMemoryStorage, Storage}; + use actix_web::{http::StatusCode, test, App}; use taskchampion::Uuid; #[actix_rt::test] @@ -49,16 +68,17 @@ mod test { let client_id = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - expected_client_id: client_id, - gcv_expected_parent_version_id: parent_version_id, - gcv_result: Some(GetVersionResult { - version_id: version_id, - parent_version_id: parent_version_id, - history_segment: b"abcd".to_vec(), - }), - ..Default::default() - }); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, Uuid::new_v4()) + .unwrap(); + txn.add_version(client_id, version_id, parent_version_id, b"abcd".to_vec()) + .unwrap(); + } + let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; @@ -88,15 +108,36 @@ mod test { } #[actix_rt::test] - async fn test_not_found() { + async fn test_client_not_found() { let client_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - expected_client_id: client_id, - gcv_expected_parent_version_id: parent_version_id, - gcv_result: None, - ..Default::default() - }); + let server_box: Box = Box::new(InMemoryStorage::new()); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!( + "/client/{}/get-child-version/{}", + client_id, parent_version_id + ); + let req = test::TestRequest::get().uri(&uri).to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + assert_eq!(resp.headers().get("X-Version-Id"), None); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); + } + + #[actix_rt::test] + async fn test_version_not_found() { + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // create the client, but not the version + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, Uuid::new_v4()) + .unwrap(); + } let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index c58d2420b..cddcab59c 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -1,5 +1,5 @@ -use crate::server::SyncServer; -use actix_web::{web, Scope}; +use crate::storage::Storage; +use actix_web::{error, http::StatusCode, web, Scope}; use std::sync::Arc; mod add_version; @@ -15,11 +15,16 @@ pub(crate) const VERSION_ID_HEADER: &str = "X-Version-Id"; /// The header names for parent version ID pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; -/// The type containing a reference to the SyncServer object in the Actix state. -pub(crate) type ServerState = Arc>; +/// The type containing a reference to the Storage object in the Actix state. +pub(crate) type ServerState = Arc>; pub(crate) fn api_scope() -> Scope { web::scope("") .service(get_child_version::service) .service(add_version::service) } + +/// Convert a failure::Error to an Actix ISE +fn failure_to_ise(err: failure::Error) -> impl actix_web::ResponseError { + error::InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR) +} diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 3123c80db..7b91c5e3c 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,12 +1,10 @@ +use crate::storage::{InMemoryStorage, Storage}; use actix_web::{get, web, App, HttpServer, Responder, Scope}; use api::{api_scope, ServerState}; -use server::{InMemorySyncServer, SyncServer}; mod api; mod server; - -#[cfg(test)] -mod test; +mod storage; // TODO: use hawk to sign requests @@ -27,7 +25,7 @@ pub(crate) fn app_scope(server_state: ServerState) -> Scope { #[actix_web::main] async fn main() -> std::io::Result<()> { - let server_box: Box = Box::new(InMemorySyncServer::new()); + let server_box: Box = Box::new(InMemoryStorage::new()); let server_state = ServerState::new(server_box); HttpServer::new(move || App::new().service(app_scope(server_state.clone()))) @@ -35,3 +33,22 @@ async fn main() -> std::io::Result<()> { .run() .await } + +#[cfg(test)] +mod test { + use super::*; + use crate::api::ServerState; + use crate::storage::{InMemoryStorage, Storage}; + use actix_web::{test, App}; + + #[actix_rt::test] + async fn test_index_get() { + let server_box: Box = Box::new(InMemoryStorage::new()); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let req = test::TestRequest::get().uri("/").to_request(); + let resp = test::call_service(&mut app, req).await; + assert!(resp.status().is_success()); + } +} diff --git a/sync-server/src/server/inmemory.rs b/sync-server/src/server/inmemory.rs deleted file mode 100644 index 37302282e..000000000 --- a/sync-server/src/server/inmemory.rs +++ /dev/null @@ -1,108 +0,0 @@ -use super::{ - AddVersionResult, ClientId, GetVersionResult, HistorySegment, SyncServer, VersionId, - NO_VERSION_ID, -}; -use failure::Fallible; -use std::collections::HashMap; -use std::sync::{Mutex, RwLock}; -use taskchampion::Uuid; - -/// An in-memory server backend that can be useful for testing. -pub(crate) struct InMemorySyncServer { - clients: RwLock>>, -} - -struct Version { - version_id: VersionId, - history_segment: HistorySegment, -} - -struct Client { - latest_version_id: VersionId, - // NOTE: indexed by parent_version_id! - versions: HashMap, -} - -impl InMemorySyncServer { - pub(crate) fn new() -> Self { - Self { - clients: RwLock::new(HashMap::new()), - } - } -} - -impl SyncServer for InMemorySyncServer { - fn get_child_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - ) -> Fallible> { - let clients = self.clients.read().expect("poisoned lock"); - if let Some(client) = clients.get(&client_id) { - let client = client.lock().expect("poisoned lock"); - if let Some(version) = client.versions.get(&parent_version_id) { - return Ok(Some(GetVersionResult { - version_id: version.version_id, - parent_version_id, - history_segment: version.history_segment.clone(), - })); - } - } - Ok(None) - } - - fn add_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Fallible { - let mut clients = self.clients.write().expect("poisoned lock"); - if let Some(client) = clients.get_mut(&client_id) { - let mut client = client.lock().expect("poisoned lock"); - if client.latest_version_id != NO_VERSION_ID { - if parent_version_id != client.latest_version_id { - return Ok(AddVersionResult::ExpectedParentVersion( - client.latest_version_id, - )); - } - } - - // invent a new ID for this version - let version_id = Uuid::new_v4(); - - client.versions.insert( - parent_version_id, - Version { - version_id, - history_segment, - }, - ); - client.latest_version_id = version_id; - - Ok(AddVersionResult::Ok(version_id)) - } else { - // new client, so insert a client with just this new version - - let latest_version_id = Uuid::new_v4(); - let mut versions = HashMap::new(); - versions.insert( - parent_version_id, - Version { - version_id: latest_version_id, - history_segment, - }, - ); - - clients.insert( - client_id, - Mutex::new(Client { - latest_version_id, - versions, - }), - ); - - Ok(AddVersionResult::Ok(latest_version_id)) - } - } -} diff --git a/sync-server/src/server/mod.rs b/sync-server/src/server/mod.rs index 7768c0c08..9e2412ba3 100644 --- a/sync-server/src/server/mod.rs +++ b/sync-server/src/server/mod.rs @@ -1,10 +1,5 @@ -use failure::Fallible; use taskchampion::Uuid; -mod inmemory; - -pub(crate) use inmemory::InMemorySyncServer; - /// The distinguished value for "no version" pub const NO_VERSION_ID: VersionId = Uuid::nil(); @@ -28,18 +23,3 @@ pub(crate) enum AddVersionResult { /// Rejected; expected a version with the given parent version ExpectedParentVersion(VersionId), } - -pub(crate) trait SyncServer: Sync + Send { - fn get_child_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - ) -> Fallible>; - - fn add_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Fallible; -} diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs new file mode 100644 index 000000000..91c868d42 --- /dev/null +++ b/sync-server/src/storage/inmemory.rs @@ -0,0 +1,90 @@ +use super::{Client, Storage, StorageTxn, Uuid, Version}; +use failure::Fallible; +use std::collections::HashMap; +use std::sync::{Mutex, MutexGuard}; + +struct Inner { + /// Clients, indexed by client_id + clients: HashMap, + + /// Versions, indexed by (client_id, parent_version_id) + versions: HashMap<(Uuid, Uuid), Version>, +} + +pub(crate) struct InMemoryStorage(Mutex); + +impl InMemoryStorage { + pub(crate) fn new() -> Self { + Self(Mutex::new(Inner { + clients: HashMap::new(), + versions: HashMap::new(), + })) + } +} + +struct InnerTxn<'a>(MutexGuard<'a, Inner>); + +/// In-memory storage for testing and experimentation. +/// +/// NOTE: this does not implement transaction rollback. +impl Storage for InMemoryStorage { + fn txn<'a>(&'a self) -> Fallible> { + Ok(Box::new(InnerTxn(self.0.lock().expect("poisoned lock")))) + } +} + +impl<'a> StorageTxn for InnerTxn<'a> { + fn get_client(&mut self, client_id: Uuid) -> Fallible> { + Ok(self.0.clients.get(&client_id).cloned()) + } + + fn set_client_latest_version_id( + &mut self, + client_id: Uuid, + latest_version_id: Uuid, + ) -> Fallible<()> { + if let Some(client) = self.0.clients.get_mut(&client_id) { + client.latest_version_id = latest_version_id; + } else { + self.0 + .clients + .insert(client_id, Client { latest_version_id }); + } + Ok(()) + } + + fn get_version_by_parent( + &mut self, + client_id: Uuid, + parent_version_id: Uuid, + ) -> Fallible> { + Ok(self + .0 + .versions + .get(&(client_id, parent_version_id)) + .cloned()) + } + + fn add_version( + &mut self, + client_id: Uuid, + version_id: Uuid, + parent_version_id: Uuid, + history_segment: Vec, + ) -> Fallible<()> { + // TODO: verify it doesn't exist (`.entry`?) + let version = Version { + version_id, + parent_version_id, + history_segment, + }; + self.0 + .versions + .insert((client_id, version.parent_version_id), version); + Ok(()) + } + + fn commit(&mut self) -> Fallible<()> { + Ok(()) + } +} diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs new file mode 100644 index 000000000..2b9bb4dc0 --- /dev/null +++ b/sync-server/src/storage/mod.rs @@ -0,0 +1,56 @@ +use failure::Fallible; +use taskchampion::Uuid; + +mod inmemory; +pub(crate) use inmemory::InMemoryStorage; + +#[derive(Clone)] +pub(crate) struct Client { + pub(crate) latest_version_id: Uuid, +} + +#[derive(Clone)] +pub(crate) struct Version { + pub(crate) version_id: Uuid, + pub(crate) parent_version_id: Uuid, + pub(crate) history_segment: Vec, +} + +pub(crate) trait StorageTxn { + /// Get information about the given client + fn get_client(&mut self, client_id: Uuid) -> Fallible>; + + /// Set the client's latest_version_id (creating the client if necessary) + fn set_client_latest_version_id( + &mut self, + client_id: Uuid, + latest_version_id: Uuid, + ) -> Fallible<()>; + + /// Get a version, indexed by parent version id + fn get_version_by_parent( + &mut self, + client_id: Uuid, + parent_version_id: Uuid, + ) -> Fallible>; + + /// Add a version (that must not already exist) + fn add_version( + &mut self, + client_id: Uuid, + version_id: Uuid, + parent_version_id: Uuid, + history_segment: Vec, + ) -> Fallible<()>; + + /// Commit any changes made in the transaction. It is an error to call this more than + /// once. It is safe to skip this call for read-only operations. + fn commit(&mut self) -> Fallible<()>; +} + +/// A trait for objects able to act as storage. Most of the interesting behavior is in the +/// [`crate::storage::StorageTxn`] trait. +pub(crate) trait Storage: Send + Sync { + /// Begin a transaction + fn txn<'a>(&'a self) -> Fallible>; +} diff --git a/sync-server/src/test.rs b/sync-server/src/test.rs deleted file mode 100644 index b9f68a66d..000000000 --- a/sync-server/src/test.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::api::ServerState; -use crate::app_scope; -use crate::server::{ - AddVersionResult, ClientId, GetVersionResult, HistorySegment, SyncServer, VersionId, -}; -use actix_web::{test, App}; -use failure::Fallible; - -#[derive(Default)] -pub(crate) struct TestServer { - /// test server will panic if this is not given - pub expected_client_id: ClientId, - - pub gcv_expected_parent_version_id: VersionId, - pub gcv_result: Option, - - pub av_expected_parent_version_id: VersionId, - pub av_expected_history_segment: HistorySegment, - pub av_result: Option, -} - -impl SyncServer for TestServer { - fn get_child_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - ) -> Fallible> { - assert_eq!(client_id, self.expected_client_id); - assert_eq!(parent_version_id, self.gcv_expected_parent_version_id); - Ok(self.gcv_result.clone()) - } - - fn add_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Fallible { - assert_eq!(client_id, self.expected_client_id); - assert_eq!(parent_version_id, self.av_expected_parent_version_id); - assert_eq!(history_segment, self.av_expected_history_segment); - Ok(self.av_result.clone().unwrap()) - } -} - -#[actix_rt::test] -async fn test_index_get() { - let server_box: Box = Box::new(TestServer { - ..Default::default() - }); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - - let req = test::TestRequest::get().uri("/").to_request(); - let resp = test::call_service(&mut app, req).await; - assert!(resp.status().is_success()); -} From 5b1b911bf7db9419e366270293512002df58d080 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 19:44:30 -0500 Subject: [PATCH 088/548] Move add_version and get_child_version to server module ..and add some tests specifically for those functions, in the absence of all the HTTP nonsense. --- sync-server/src/api/add_version.rs | 30 +--- sync-server/src/api/get_child_version.rs | 18 +-- sync-server/src/server.rs | 197 +++++++++++++++++++++++ sync-server/src/server/mod.rs | 25 --- sync-server/src/storage/mod.rs | 4 +- 5 files changed, 201 insertions(+), 73 deletions(-) create mode 100644 sync-server/src/server.rs delete mode 100644 sync-server/src/server/mod.rs diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 2413c5fce..770ce2abc 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -2,12 +2,9 @@ use crate::api::{ failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{AddVersionResult, ClientId, HistorySegment, VersionId, NO_VERSION_ID}; -use crate::storage::{Client, StorageTxn}; +use crate::server::{add_version, AddVersionResult, ClientId, VersionId}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; -use failure::Fallible; use futures::StreamExt; -use taskchampion::Uuid; /// Max history segment size: 100MB const MAX_SIZE: usize = 100 * 1024 * 1024; @@ -71,31 +68,6 @@ pub(crate) async fn service( }) } -fn add_version<'a>( - mut txn: Box, - client_id: ClientId, - client: Client, - parent_version_id: VersionId, - history_segment: HistorySegment, -) -> Fallible { - // check if this version is acceptable, under the protection of the transaction - if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { - return Ok(AddVersionResult::ExpectedParentVersion( - client.latest_version_id, - )); - } - - // invent a version ID - let version_id = Uuid::new_v4(); - - // update the DB - txn.add_version(client_id, version_id, parent_version_id, history_segment)?; - txn.set_client_latest_version_id(client_id, version_id)?; - txn.commit()?; - - Ok(AddVersionResult::Ok(version_id)) -} - #[cfg(test)] mod test { use crate::api::ServerState; diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 84af0b179..83f9e2224 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -2,10 +2,8 @@ use crate::api::{ failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{ClientId, GetVersionResult, VersionId}; -use crate::storage::StorageTxn; +use crate::server::{get_child_version, ClientId, VersionId}; use actix_web::{error, get, web, HttpResponse, Result}; -use failure::Fallible; /// Get a child version. /// @@ -41,20 +39,6 @@ pub(crate) async fn service( } } -fn get_child_version<'a>( - mut txn: Box, - client_id: ClientId, - parent_version_id: VersionId, -) -> Fallible> { - Ok(txn - .get_version_by_parent(client_id, parent_version_id)? - .map(|version| GetVersionResult { - version_id: version.version_id, - parent_version_id: version.parent_version_id, - history_segment: version.history_segment, - })) -} - #[cfg(test)] mod test { use crate::api::ServerState; diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs new file mode 100644 index 000000000..0a404172d --- /dev/null +++ b/sync-server/src/server.rs @@ -0,0 +1,197 @@ +//! This module implements the core logic of the server: handling transactions, upholding +//! invariants, and so on. +use crate::storage::{Client, StorageTxn}; +use failure::Fallible; +use taskchampion::Uuid; + +/// The distinguished value for "no version" +pub const NO_VERSION_ID: VersionId = Uuid::nil(); + +pub(crate) type HistorySegment = Vec; +pub(crate) type ClientId = Uuid; +pub(crate) type VersionId = Uuid; + +/// Response to get_child_version +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct GetVersionResult { + pub(crate) version_id: Uuid, + pub(crate) parent_version_id: Uuid, + pub(crate) history_segment: HistorySegment, +} + +pub(crate) fn get_child_version<'a>( + mut txn: Box, + client_id: ClientId, + parent_version_id: VersionId, +) -> Fallible> { + Ok(txn + .get_version_by_parent(client_id, parent_version_id)? + .map(|version| GetVersionResult { + version_id: version.version_id, + parent_version_id: version.parent_version_id, + history_segment: version.history_segment, + })) +} + +/// Response to add_version +#[derive(Clone, PartialEq, Debug)] +pub(crate) enum AddVersionResult { + /// OK, version added with the given ID + Ok(VersionId), + /// Rejected; expected a version with the given parent version + ExpectedParentVersion(VersionId), +} + +pub(crate) fn add_version<'a>( + mut txn: Box, + client_id: ClientId, + client: Client, + parent_version_id: VersionId, + history_segment: HistorySegment, +) -> Fallible { + // check if this version is acceptable, under the protection of the transaction + if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { + return Ok(AddVersionResult::ExpectedParentVersion( + client.latest_version_id, + )); + } + + // invent a version ID + let version_id = Uuid::new_v4(); + + // update the DB + txn.add_version(client_id, version_id, parent_version_id, history_segment)?; + txn.set_client_latest_version_id(client_id, version_id)?; + txn.commit()?; + + Ok(AddVersionResult::Ok(version_id)) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::storage::{InMemoryStorage, Storage}; + + #[test] + fn gcv_not_found() -> Fallible<()> { + let storage = InMemoryStorage::new(); + let txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + assert_eq!(get_child_version(txn, client_id, parent_version_id)?, None); + Ok(()) + } + + #[test] + fn gcv_found() -> Fallible<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abcd".to_vec(); + + txn.add_version( + client_id, + version_id, + parent_version_id, + history_segment.clone(), + )?; + + assert_eq!( + get_child_version(txn, client_id, parent_version_id)?, + Some(GetVersionResult { + version_id, + parent_version_id, + history_segment, + }) + ); + Ok(()) + } + + #[test] + fn av_conflict() -> Fallible<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abcd".to_vec(); + let existing_parent_version_id = Uuid::new_v4(); + let client = Client { + latest_version_id: existing_parent_version_id, + }; + + assert_eq!( + add_version( + txn, + client_id, + client, + parent_version_id, + history_segment.clone() + )?, + AddVersionResult::ExpectedParentVersion(existing_parent_version_id) + ); + + // verify that the storage wasn't updated + txn = storage.txn()?; + assert_eq!(txn.get_client(client_id)?, None); + assert_eq!( + txn.get_version_by_parent(client_id, parent_version_id)?, + None + ); + + Ok(()) + } + + fn test_av_success(latest_version_id_nil: bool) -> Fallible<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abcd".to_vec(); + let client = Client { + latest_version_id: if latest_version_id_nil { + Uuid::nil() + } else { + parent_version_id + }, + }; + + let result = add_version( + txn, + client_id, + client, + parent_version_id, + history_segment.clone(), + )?; + if let AddVersionResult::Ok(new_version_id) = result { + // check that it invented a new version ID + assert!(new_version_id != parent_version_id); + + // verify that the storage was updated + txn = storage.txn()?; + let client = txn.get_client(client_id)?.unwrap(); + assert_eq!(client.latest_version_id, new_version_id); + let version = txn + .get_version_by_parent(client_id, parent_version_id)? + .unwrap(); + assert_eq!(version.version_id, new_version_id); + assert_eq!(version.parent_version_id, parent_version_id); + assert_eq!(version.history_segment, history_segment); + } else { + panic!("did not get Ok from add_version"); + } + + Ok(()) + } + + #[test] + fn av_success_with_existing_history() -> Fallible<()> { + test_av_success(true) + } + + #[test] + fn av_success_nil_latest_version_id() -> Fallible<()> { + test_av_success(false) + } +} diff --git a/sync-server/src/server/mod.rs b/sync-server/src/server/mod.rs deleted file mode 100644 index 9e2412ba3..000000000 --- a/sync-server/src/server/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -use taskchampion::Uuid; - -/// The distinguished value for "no version" -pub const NO_VERSION_ID: VersionId = Uuid::nil(); - -pub(crate) type HistorySegment = Vec; -pub(crate) type ClientId = Uuid; -pub(crate) type VersionId = Uuid; - -/// Response to get_child_version -#[derive(Clone)] -pub(crate) struct GetVersionResult { - pub(crate) version_id: Uuid, - pub(crate) parent_version_id: Uuid, - pub(crate) history_segment: HistorySegment, -} - -/// Response to add_version -#[derive(Clone)] -pub(crate) enum AddVersionResult { - /// OK, version added with the given ID - Ok(VersionId), - /// Rejected; expected a version with the given parent version - ExpectedParentVersion(VersionId), -} diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 2b9bb4dc0..2915f3c4d 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -4,12 +4,12 @@ use taskchampion::Uuid; mod inmemory; pub(crate) use inmemory::InMemoryStorage; -#[derive(Clone)] +#[derive(Clone, PartialEq, Debug)] pub(crate) struct Client { pub(crate) latest_version_id: Uuid, } -#[derive(Clone)] +#[derive(Clone, PartialEq, Debug)] pub(crate) struct Version { pub(crate) version_id: Uuid, pub(crate) parent_version_id: Uuid, From b0dd3905e7b0c49824db544d7f9beef1c6a77a92 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 22:30:07 -0500 Subject: [PATCH 089/548] fix crate name for tc-sync-server --- sync-server/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index e9df49ac7..2e0ef9db8 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sync-server" +name = "taskchampion-sync-server" version = "0.1.0" authors = ["Dustin J. Mitchell "] edition = "2018" From 26b17c6dbc112347640f0302af210e0bec29ea3b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 22:34:20 -0500 Subject: [PATCH 090/548] Revert "add signing support" It turns out we don't need this. This reverts commit e92fc0628b613005402799d532a3c3b1748794f8. --- Cargo.lock | 69 ++++-------------------- taskchampion/Cargo.toml | 1 - taskchampion/src/server/mod.rs | 1 - taskchampion/src/server/signing.rs | 87 ------------------------------ 4 files changed, 11 insertions(+), 147 deletions(-) delete mode 100644 taskchampion/src/server/signing.rs diff --git a/Cargo.lock b/Cargo.lock index 103d2e8ee..877cd2b16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,15 +1074,6 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" -[[package]] -name = "js-sys" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1753,21 +1744,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "ring" -version = "0.16.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5911690c9b773bab7e657471afc207f3827b249a657241327e3544d79bcabdd" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi 0.3.9", -] - [[package]] name = "rmp" version = "0.8.9" @@ -1950,12 +1926,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "standback" version = "0.2.13" @@ -2031,17 +2001,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "sync-server" -version = "0.1.0" -dependencies = [ - "actix-rt", - "actix-web", - "failure", - "futures", - "taskchampion", -] - [[package]] name = "synstructure" version = "0.12.4" @@ -2063,7 +2022,6 @@ dependencies = [ "kv", "lmdb-rkv", "proptest", - "ring", "serde", "serde_json", "tempdir", @@ -2082,6 +2040,17 @@ dependencies = [ "taskchampion", ] +[[package]] +name = "taskchampion-sync-server" +version = "0.1.0" +dependencies = [ + "actix-rt", + "actix-web", + "failure", + "futures", + "taskchampion", +] + [[package]] name = "tempdir" version = "0.3.7" @@ -2390,12 +2359,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "url" version = "2.2.0" @@ -2505,16 +2468,6 @@ version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" -[[package]] -name = "web-sys" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "widestring" version = "0.4.3" diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 509a7db8b..1f6681454 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -12,7 +12,6 @@ chrono = { version = "0.4.10", features = ["serde"] } failure = {version = "0.1.5", features = ["derive"] } kv = {version = "0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "0.12.3"} -ring = { version = "0.16.17", features = ["std"] } [dev-dependencies] proptest = "0.9.4" diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 0331bb63c..26e021957 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -2,7 +2,6 @@ pub(crate) mod test; mod local; -mod signing; mod types; pub use local::LocalServer; diff --git a/taskchampion/src/server/signing.rs b/taskchampion/src/server/signing.rs deleted file mode 100644 index e1e49501a..000000000 --- a/taskchampion/src/server/signing.rs +++ /dev/null @@ -1,87 +0,0 @@ -#![allow(dead_code)] // TODO: temporary until this module is used -//! This is a general wrapper around an asymmetric-key signature system. - -use failure::Fallible; -use ring::{ - rand, - signature::{Ed25519KeyPair, KeyPair, Signature, UnparsedPublicKey, ED25519}, -}; - -type PublicKey = Vec; -type PrivateKey = Vec; - -/// Generate a pair of (public, private) key material (in fact the private key is a keypair) -pub fn new_keypair() -> Fallible<(PublicKey, PrivateKey)> { - let rng = rand::SystemRandom::new(); - let key_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng)?; - let key_pair = Ed25519KeyPair::from_pkcs8(key_pkcs8.as_ref())?; - let pub_key = key_pair.public_key(); - Ok(( - pub_key.as_ref().to_vec() as PublicKey, - key_pkcs8.as_ref().to_vec() as PrivateKey, - )) -} - -pub struct Signer { - key_pair: Ed25519KeyPair, -} - -impl Signer { - /// Create a new signer, given a pkcs#8 v2 document containing the keypair. - fn new(priv_key: PrivateKey) -> Fallible { - Ok(Self { - key_pair: Ed25519KeyPair::from_pkcs8(&priv_key)?, - }) - } - - pub fn sign>(&self, message: B) -> Fallible { - Ok(self.key_pair.sign(message.as_ref())) - } -} - -pub struct Verifier { - pub_key: PublicKey, -} - -impl Verifier { - fn new(pub_key: PublicKey) -> Fallible { - Ok(Self { pub_key }) - } - - pub fn verify, B2: AsRef<[u8]>>( - &self, - message: B1, - signature: B2, - ) -> Fallible<()> { - let pub_key = UnparsedPublicKey::new(&ED25519, &self.pub_key); - Ok(pub_key.verify(message.as_ref(), signature.as_ref())?) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_verify_ok() -> Fallible<()> { - let (public, private) = new_keypair()?; - let signer = Signer::new(private)?; - let verifier = Verifier::new(public)?; - - let message = b"Hello, world"; - let signature = signer.sign(message)?; - verifier.verify(message, signature) - } - - #[test] - fn test_verify_bad_message() -> Fallible<()> { - let (public, private) = new_keypair()?; - let signer = Signer::new(private)?; - let verifier = Verifier::new(public)?; - - let message = b"Hello, world"; - let signature = signer.sign(message)?; - assert!(verifier.verify(b"Hello, cruel world", signature).is_err()); - Ok(()) - } -} From 2064057688b875b1c739d5afa585f5feb1a9c105 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 22:36:31 -0500 Subject: [PATCH 091/548] fix clippy warnings I don't know why these are "intermittent" --- taskchampion/src/server/local.rs | 2 +- taskchampion/src/taskstorage/inmemory.rs | 4 ++-- taskchampion/src/taskstorage/kv.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 9659625b3..89775acf1 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -138,7 +138,7 @@ impl<'t> Server for LocalServer<'t> { Ok(GetVersionResult::Version { version_id: version.version_id, parent_version_id: version.parent_version_id, - history_segment: version.history_segment.clone(), + history_segment: version.history_segment, }) } else { Ok(GetVersionResult::NoSuchVersion) diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs index 66a871ed8..2c3899cb6 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/taskstorage/inmemory.rs @@ -82,7 +82,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { } fn base_version(&mut self) -> Fallible { - Ok(self.data_ref().base_version.clone()) + Ok(self.data_ref().base_version) } fn set_base_version(&mut self, version: VersionId) -> Fallible<()> { @@ -140,7 +140,7 @@ impl InMemoryStorage { InMemoryStorage { data: Data { tasks: HashMap::new(), - base_version: DEFAULT_BASE_VERSION.into(), + base_version: DEFAULT_BASE_VERSION, operations: vec![], working_set: vec![None], }, diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index f6ee9a7f0..87d8416c3 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -171,7 +171,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { let bucket = self.uuids_bucket(); let base_version = match self.kvtxn().get(bucket, BASE_VERSION.into()) { Ok(buf) => buf, - Err(Error::NotFound) => return Ok(DEFAULT_BASE_VERSION.into()), + Err(Error::NotFound) => return Ok(DEFAULT_BASE_VERSION), Err(e) => return Err(e.into()), } .inner()? From 0f98727d9b5ca491d04fed38bef8635b684b0a0b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 22:44:13 -0500 Subject: [PATCH 092/548] replica.new_task now invents its own uuid --- cli/src/cmd/add.rs | 8 +++---- taskchampion/src/replica.rs | 42 ++++++++++++++----------------------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/cli/src/cmd/add.rs b/cli/src/cmd/add.rs index a04ad8658..cfc26b5a8 100644 --- a/cli/src/cmd/add.rs +++ b/cli/src/cmd/add.rs @@ -1,6 +1,6 @@ use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; use failure::{format_err, Fallible}; -use taskchampion::{Status, Uuid}; +use taskchampion::Status; use crate::cmd::{ArgMatchResult, CommandInvocation}; @@ -37,11 +37,11 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let uuid = Uuid::new_v4(); - command + let t = command .get_replica() - .new_task(uuid, Status::Pending, self.description.clone()) + .new_task(Status::Pending, self.description.clone()) .unwrap(); + println!("added task {}", t.get_uuid()); Ok(()) } } diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index f141e2348..c33d31028 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -119,13 +119,8 @@ impl Replica { } /// Create a new task. The task must not already exist. - pub fn new_task(&mut self, uuid: Uuid, status: Status, description: String) -> Fallible { - // check that it doesn't exist; this is a convenience check, as the task - // may already exist when this Create operation is finally sync'd with - // operations from other replicas - if self.taskdb.get_task(&uuid)?.is_some() { - return Err(Error::DBError(format!("Task {} already exists", uuid)).into()); - } + pub fn new_task(&mut self, status: Status, description: String) -> Fallible { + let uuid = Uuid::new_v4(); self.taskdb.apply(Operation::Create { uuid })?; let mut task = Task::new(uuid, TaskMap::new()).into_mut(self); task.set_description(description)?; @@ -135,13 +130,13 @@ impl Replica { /// Delete a task. The task must exist. Note that this is different from setting status to /// Deleted; this is the final purge of the task. - pub fn delete_task(&mut self, uuid: Uuid) -> Fallible<()> { + pub fn delete_task(&mut self, uuid: &Uuid) -> Fallible<()> { // check that it already exists; this is a convenience check, as the task may already exist // when this Create operation is finally sync'd with operations from other replicas if self.taskdb.get_task(&uuid)?.is_none() { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } - self.taskdb.apply(Operation::Delete { uuid }) + self.taskdb.apply(Operation::Delete { uuid: *uuid }) } /// Synchronize this replica against the given server. @@ -168,11 +163,8 @@ mod tests { #[test] fn new_task() { let mut rep = Replica::new_inmemory(); - let uuid = Uuid::new_v4(); - let t = rep - .new_task(uuid, Status::Pending, "a task".into()) - .unwrap(); + let t = rep.new_task(Status::Pending, "a task".into()).unwrap(); assert_eq!(t.get_description(), String::from("a task")); assert_eq!(t.get_status(), Status::Pending); assert!(t.get_modified().is_some()); @@ -181,11 +173,8 @@ mod tests { #[test] fn modify_task() { let mut rep = Replica::new_inmemory(); - let uuid = Uuid::new_v4(); - let t = rep - .new_task(uuid, Status::Pending, "a task".into()) - .unwrap(); + let t = rep.new_task(Status::Pending, "a task".into()).unwrap(); let mut t = t.into_mut(&mut rep); t.set_description(String::from("past tense")).unwrap(); @@ -200,7 +189,7 @@ mod tests { assert_eq!(t.get_status(), Status::Completed); // check tha values have changed in storage, too - let t = rep.get_task(&uuid).unwrap().unwrap(); + let t = rep.get_task(&t.get_uuid()).unwrap().unwrap(); assert_eq!(t.get_description(), "past tense"); assert_eq!(t.get_status(), Status::Completed); } @@ -208,10 +197,9 @@ mod tests { #[test] fn delete_task() { let mut rep = Replica::new_inmemory(); - let uuid = Uuid::new_v4(); - rep.new_task(uuid, Status::Pending, "a task".into()) - .unwrap(); + let t = rep.new_task(Status::Pending, "a task".into()).unwrap(); + let uuid = t.get_uuid(); rep.delete_task(uuid).unwrap(); assert_eq!(rep.get_task(&uuid).unwrap(), None); @@ -220,10 +208,11 @@ mod tests { #[test] fn get_and_modify() { let mut rep = Replica::new_inmemory(); - let uuid = Uuid::new_v4(); - rep.new_task(uuid, Status::Pending, "another task".into()) + let t = rep + .new_task(Status::Pending, "another task".into()) .unwrap(); + let uuid = t.get_uuid(); let t = rep.get_task(&uuid).unwrap().unwrap(); assert_eq!(t.get_description(), String::from("another task")); @@ -244,10 +233,11 @@ mod tests { #[test] fn new_pending_adds_to_working_set() { let mut rep = Replica::new_inmemory(); - let uuid = Uuid::new_v4(); - rep.new_task(uuid, Status::Pending, "to-be-pending".into()) + let t = rep + .new_task(Status::Pending, "to-be-pending".into()) .unwrap(); + let uuid = t.get_uuid(); let t = rep.get_working_set_task(1).unwrap().unwrap(); assert_eq!(t.get_status(), Status::Pending); @@ -256,7 +246,7 @@ mod tests { let ws = rep.working_set().unwrap(); assert_eq!(ws.len(), 2); assert!(ws[0].is_none()); - assert_eq!(ws[1].as_ref().unwrap().get_uuid(), &uuid); + assert_eq!(ws[1].as_ref().unwrap().get_uuid(), uuid); assert_eq!(rep.get_working_set_index(t.get_uuid()).unwrap().unwrap(), 1); } From eb47cf4e7f7eb19ab9c0268ab4d8f242a84a3920 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 23:01:44 -0500 Subject: [PATCH 093/548] scrpit to push docs --- README.md | 2 +- docs/.gitignore | 1 + docs/build.sh | 23 +++++++++++++++++++++++ docs/src/usage.md | 2 ++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100755 docs/build.sh diff --git a/README.md b/README.md index 3a65a287f..135f89164 100644 --- a/README.md +++ b/README.md @@ -24,5 +24,5 @@ There are three crates here: ## See Also - * [Documentation](docs/src/SUMMARY.md) (will be published as an mdbook eventually) + * [Documentation](https://djmitche.github.io/taskchampion/) (NOTE: temporary url) * [Progress on the first version](https://github.com/djmitche/taskwarrior-rust/projects/1) diff --git a/docs/.gitignore b/docs/.gitignore index 7585238ef..d2479eb14 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ book +tmp diff --git a/docs/build.sh b/docs/build.sh new file mode 100755 index 000000000..06dc662c7 --- /dev/null +++ b/docs/build.sh @@ -0,0 +1,23 @@ +#! /bin/bash + +REMOTE=origin + +set -e + +if ! [ -f "./src/SUMMARY.md" ]; then + echo "Run this from the docs/ dir" + exit 1 +fi + +if ! [ -d ./tmp ]; then + git worktree add tmp gh-pages +fi + +(cd tmp && git pull $REMOTE gh-pages) + +rm -rf tmp/* +mdbook build +cp -rp book/* tmp +(cd tmp && git add -A) +(cd tmp && git commit -am "update docs") +(cd tmp && git push $REMOTE gh-pages:gh-pages) diff --git a/docs/src/usage.md b/docs/src/usage.md index 39a73fc03..e284815bd 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -2,3 +2,5 @@ The main interface to your tasks is the `task` command, which supports various subcommands. You can find a quick list of all subcommands with `task help`. + +Note that the `task` interface does not match that of TaskWarrior. From c96e40e494b1dda93beb5f9a41c3230981a785cb Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 23:16:39 -0500 Subject: [PATCH 094/548] Create SECURITY.md --- SECURITY.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..3f9c867c2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +## Supported Versions + +This software is currently pre-release, so no versions are formally supported. + +Once 1.0 has been released, only the most recent version will be supported. + +## Reporting a Vulnerability + +To report a vulnerability in this application, contact me directly at `dustin@cs.uchicago.edu`. +You can expect an initial response within a day or two, and a regular email conversational cadence thereafter. From b10dc01213977d274d81823ed1af8dba46165c92 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 23:18:32 -0500 Subject: [PATCH 095/548] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..807def586 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at dustin@cs.uchicago.edu. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq From 367c2fa2bdc1db26232ea6d6730c453c287f7384 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 23:20:45 -0500 Subject: [PATCH 096/548] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..f0c9756e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Dustin J. Mitchell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From efb1bc6a3947b8235f32580ccd6320e170a2e022 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 23:36:54 -0500 Subject: [PATCH 097/548] Create CONTRIBUTING.md --- CONTRIBUTING.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..568878926 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Welcome + +This application is still in a pre-release state. +That means it's very open to contributions, and we'd love to have your help! + +It also means that things are changing quickly, and lots of stuff is planned that's not quite done yet. +If you would like to work on TaskChampion, please contact the developers (via the issue tracker) before spending a lot of time working on a pull request. +Doing so may save you some wasted time and frustration! + +# Other Ways To Help + +The best way to help this project to grow is to help spread awareness of it. +Tell your friends, post to social media, blog about it -- whatever works best! + +Other ideas; + * Improve the documentation where it's unclear or lacking some information + * Build and maintain tools that integrate with TaskChampion + * Devise a nice TaskChampion logo + +# Development Guide + +TaskChampion is a typical Rust application. +To work on TaskChampion, you'll need to [install the latest version of Rust](https://www.rust-lang.org/tools/install). +Once you've done that, run `cargo build` at the top level of this repository to build the binaries. +This will build `task` and `taskchampion-sync-server` executables in the `./target/debug` directory. +You can build optimized versions of these binaries with `cargo build --release`, but the performance difference in the resulting binaries is not noticeable, and the build process will take a long time, so this is not recommended. + +## Running Test + +It's always a good idea to make sure tests run before you start hacking on a project. +Run `cargo test` from the top-level of this repository to run the tests. + +## Read the Source + +Aside from that, start reading the docs and the source to learn more! +The book documentation explains lots of the concepts in the design of TaskChampion. +It is linked from the README. + +There are three crates in this repository. +You may be able to limit the scope of what you need to understand to just one crate. + * `taskchampion` is the core functionality of the application, implemented as a library + * `taskchampion-cli` implements the command-line interface (in `cli/`) + * `taskchampion-sync-server` implements the synchronization server (in `sync-server/`) + + You can generate the documentation for the `taskchampion` crate with `cargo doc --release --open -p taskchampion`. + + ## Making a Pull Request + + We expect contributors to follow the [GitHub Flow](https://guides.github.com/introduction/flow/). + Aside from that, we have no particular requirements on pull requests. + Make your patch, double-check that it's complete (tests? docs? documentation comments?), and make a new pull request. From f5859bc45c7abd226966298d20ae2a6b98478a9b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 23:09:00 -0500 Subject: [PATCH 098/548] fix docs to include all files --- docs/src/SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 91a180ab5..b37148b77 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -9,5 +9,5 @@ - [Tasks](./tasks.md) - [Synchronization and the Sync Server](./sync.md) - [Synchronization Model](./sync-model.md) - * [Server-Replica Protocol](./sync-protocol.md) + - [Server-Replica Protocol](./sync-protocol.md) - [Planned Functionality](./plans.md) From f59355f82782027716904de0416a60d8b161fefa Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 23:13:07 -0500 Subject: [PATCH 099/548] set default theme to ayu --- docs/book.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/book.toml b/docs/book.toml index 0e19be438..7e2fa9820 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -4,3 +4,6 @@ language = "en" multilingual = false src = "src" title = "TaskChampion" + +[output.html] +default-theme = "ayu" From d46f20e75a7899ab6065d7f1cd07a15d2d998888 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 27 Nov 2020 18:16:42 -0500 Subject: [PATCH 100/548] adjust visibility, comment --- cli/src/lib.rs | 2 +- taskchampion/src/server/types.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index a71670ac1..48421f25a 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -6,7 +6,7 @@ mod cmd; mod table; use cmd::ArgMatchResult; -pub use cmd::CommandInvocation; +pub(crate) use cmd::CommandInvocation; /// Parse the given command line and return an as-yet un-executed CommandInvocation. pub fn parse_command_line(iter: I) -> Fallible diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index 473a17e46..b0a28a842 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -11,7 +11,7 @@ pub const NO_VERSION_ID: VersionId = Uuid::nil(); /// data is pre-encoded, and from the protocol level appears as a sequence of bytes. pub type HistorySegment = Vec; -/// VersionAdd is the response type from [`crate:server::Server::add_version`]. +/// VersionAdd is the response type from [`crate::server::Server::add_version`]. #[derive(Debug, PartialEq)] pub enum AddVersionResult { /// OK, version added with the given ID From 3092a6bb7f66f6fbe45f5780129be9e2276da0de Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 27 Nov 2020 20:08:54 -0500 Subject: [PATCH 101/548] do not require taskchampion from sync-server --- Cargo.lock | 2 +- sync-server/Cargo.toml | 2 +- sync-server/src/api/add_version.rs | 2 +- sync-server/src/api/get_child_version.rs | 2 +- sync-server/src/server.rs | 2 +- sync-server/src/storage/mod.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 877cd2b16..97bccecac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2048,7 +2048,7 @@ dependencies = [ "actix-web", "failure", "futures", - "taskchampion", + "uuid", ] [[package]] diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 2e0ef9db8..109f7d393 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -7,9 +7,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +uuid = { version = "0.8.1", features = ["serde", "v4"] } actix-web = "3.3.0" failure = "0.1.8" -taskchampion = { path = "../taskchampion" } futures = "0.3.8" [dev-dependencies] diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 770ce2abc..5c5b7c87f 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -74,7 +74,7 @@ mod test { use crate::app_scope; use crate::storage::{InMemoryStorage, Storage}; use actix_web::{http::StatusCode, test, App}; - use taskchampion::Uuid; + use uuid::Uuid; #[actix_rt::test] async fn test_success() { diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 83f9e2224..cc6aabd16 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -45,7 +45,7 @@ mod test { use crate::app_scope; use crate::storage::{InMemoryStorage, Storage}; use actix_web::{http::StatusCode, test, App}; - use taskchampion::Uuid; + use uuid::Uuid; #[actix_rt::test] async fn test_success() { diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 0a404172d..b804d69a3 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -2,7 +2,7 @@ //! invariants, and so on. use crate::storage::{Client, StorageTxn}; use failure::Fallible; -use taskchampion::Uuid; +use uuid::Uuid; /// The distinguished value for "no version" pub const NO_VERSION_ID: VersionId = Uuid::nil(); diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 2915f3c4d..44949e144 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -1,5 +1,5 @@ use failure::Fallible; -use taskchampion::Uuid; +use uuid::Uuid; mod inmemory; pub(crate) use inmemory::InMemoryStorage; From 2a37f090a50d16fe9461736a23d4c0f3a627a7ce Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 27 Nov 2020 19:47:50 -0500 Subject: [PATCH 102/548] Add RemoteServer to the taskchampion crate --- Cargo.lock | 161 +++++++++++++++++++++++ cli/src/cmd/shared.rs | 7 +- sync-server/src/api/add_version.rs | 21 +-- sync-server/src/api/get_child_version.rs | 6 +- sync-server/src/server.rs | 13 +- sync-server/src/storage/inmemory.rs | 19 ++- sync-server/src/storage/mod.rs | 5 +- taskchampion/Cargo.toml | 1 + taskchampion/src/server/mod.rs | 2 + taskchampion/src/server/remote.rs | 116 ++++++++++++++++ 10 files changed, 322 insertions(+), 29 deletions(-) create mode 100644 taskchampion/src/server/remote.rs diff --git a/Cargo.lock b/Cargo.lock index 877cd2b16..ac239c40b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -541,6 +541,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "chunked_transfer" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7477065d45a8fe57167bf3cf8bcd3729b54cfcb81cca49bda2d038ea89ae82ca" + [[package]] name = "clap" version = "2.33.3" @@ -597,6 +603,22 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie_store" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" +dependencies = [ + "cookie", + "idna", + "log", + "publicsuffix", + "serde", + "serde_json", + "time 0.2.23", + "url", +] + [[package]] name = "copyless" version = "0.1.5" @@ -733,6 +755,15 @@ dependencies = [ "syn", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + [[package]] name = "failure" version = "0.1.8" @@ -1074,6 +1105,15 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +[[package]] +name = "js-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1497,6 +1537,28 @@ dependencies = [ "tempfile", ] +[[package]] +name = "publicsuffix" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b" +dependencies = [ + "error-chain", + "idna", + "lazy_static", + "regex", + "url", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -1744,6 +1806,21 @@ dependencies = [ "quick-error", ] +[[package]] +name = "ring" +version = "0.16.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70017ed5c555d79ee3538fc63ca09c70ad8f317dcadc1adc2c496b60c22bb24f" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + [[package]] name = "rmp" version = "0.8.9" @@ -1792,6 +1869,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" +dependencies = [ + "base64 0.12.3", + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "rusty-fork" version = "0.2.2" @@ -1816,6 +1906,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "semver" version = "0.9.0" @@ -1926,6 +2026,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "standback" version = "0.2.13" @@ -2025,6 +2131,7 @@ dependencies = [ "serde", "serde_json", "tempdir", + "ureq", "uuid", ] @@ -2359,6 +2466,31 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a599426c7388ab189dfd0eeb84c8d879490abc73e3e62a0b6a40e286f6427ab7" +dependencies = [ + "base64 0.13.0", + "chunked_transfer", + "cookie", + "cookie_store", + "log", + "once_cell", + "qstring", + "rustls", + "url", + "webpki", + "webpki-roots", +] + [[package]] name = "url" version = "2.2.0" @@ -2468,6 +2600,35 @@ version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" +[[package]] +name = "web-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" +dependencies = [ + "webpki", +] + [[package]] name = "widestring" version = "0.4.3" diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index 25940bb28..6ae2ff4a3 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -52,8 +52,9 @@ impl CommandInvocation { } pub(super) fn get_server(&self) -> Fallible { - Ok(server::LocalServer::new(Path::new( - "/tmp/task-sync-server", - ))?) + Ok(server::RemoteServer::new( + "http://localhost:8080".into(), + Uuid::parse_str("d5b55cbd-9a82-4860-9a39-41b67893b22f").unwrap(), + )) } } diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 770ce2abc..d15c7d941 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -2,7 +2,7 @@ use crate::api::{ failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{add_version, AddVersionResult, ClientId, VersionId}; +use crate::server::{add_version, AddVersionResult, ClientId, VersionId, NO_VERSION_ID}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use futures::StreamExt; @@ -51,10 +51,15 @@ pub(crate) async fn service( // in transit. let mut txn = server_state.txn().map_err(failure_to_ise)?; - let client = txn - .get_client(client_id) - .map_err(failure_to_ise)? - .ok_or_else(|| error::ErrorNotFound("no such client"))?; + // get, or create, the client + let client = match txn.get_client(client_id).map_err(failure_to_ise)? { + Some(client) => client, + None => { + txn.new_client(client_id, NO_VERSION_ID) + .map_err(failure_to_ise)?; + txn.get_client(client_id).map_err(failure_to_ise)?.unwrap() + } + }; let result = add_version(txn, client_id, client, parent_version_id, body.to_vec()) .map_err(failure_to_ise)?; @@ -86,8 +91,7 @@ mod test { // set up the storage contents.. { let mut txn = server_box.txn().unwrap(); - txn.set_client_latest_version_id(client_id, Uuid::nil()) - .unwrap(); + txn.new_client(client_id, Uuid::nil()).unwrap(); } let server_state = ServerState::new(server_box); @@ -123,8 +127,7 @@ mod test { // set up the storage contents.. { let mut txn = server_box.txn().unwrap(); - txn.set_client_latest_version_id(client_id, version_id) - .unwrap(); + txn.new_client(client_id, version_id).unwrap(); } let server_state = ServerState::new(server_box); diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 83f9e2224..ed6dff3bd 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -57,8 +57,7 @@ mod test { // set up the storage contents.. { let mut txn = server_box.txn().unwrap(); - txn.set_client_latest_version_id(client_id, Uuid::new_v4()) - .unwrap(); + txn.new_client(client_id, Uuid::new_v4()).unwrap(); txn.add_version(client_id, version_id, parent_version_id, b"abcd".to_vec()) .unwrap(); } @@ -119,8 +118,7 @@ mod test { // create the client, but not the version { let mut txn = server_box.txn().unwrap(); - txn.set_client_latest_version_id(client_id, Uuid::new_v4()) - .unwrap(); + txn.new_client(client_id, Uuid::new_v4()).unwrap(); } let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 0a404172d..464bff4e1 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -149,14 +149,15 @@ mod test { let client_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let history_segment = b"abcd".to_vec(); - let client = Client { - latest_version_id: if latest_version_id_nil { - Uuid::nil() - } else { - parent_version_id - }, + let latest_version_id = if latest_version_id_nil { + Uuid::nil() + } else { + parent_version_id }; + txn.new_client(client_id, latest_version_id)?; + let client = txn.get_client(client_id)?.unwrap(); + let result = add_version( txn, client_id, diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs index 91c868d42..cd8943d1e 100644 --- a/sync-server/src/storage/inmemory.rs +++ b/sync-server/src/storage/inmemory.rs @@ -1,5 +1,5 @@ use super::{Client, Storage, StorageTxn, Uuid, Version}; -use failure::Fallible; +use failure::{format_err, Fallible}; use std::collections::HashMap; use std::sync::{Mutex, MutexGuard}; @@ -38,19 +38,26 @@ impl<'a> StorageTxn for InnerTxn<'a> { Ok(self.0.clients.get(&client_id).cloned()) } + fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()> { + if let Some(_) = self.0.clients.get(&client_id) { + return Err(format_err!("Client {} already exists", client_id)); + } + self.0 + .clients + .insert(client_id, Client { latest_version_id }); + Ok(()) + } + fn set_client_latest_version_id( &mut self, client_id: Uuid, latest_version_id: Uuid, ) -> Fallible<()> { if let Some(client) = self.0.clients.get_mut(&client_id) { - client.latest_version_id = latest_version_id; + Ok(client.latest_version_id = latest_version_id) } else { - self.0 - .clients - .insert(client_id, Client { latest_version_id }); + Err(format_err!("Client {} does not exist", client_id)) } - Ok(()) } fn get_version_by_parent( diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 2915f3c4d..022b7d512 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -20,7 +20,10 @@ pub(crate) trait StorageTxn { /// Get information about the given client fn get_client(&mut self, client_id: Uuid) -> Fallible>; - /// Set the client's latest_version_id (creating the client if necessary) + /// Create a new client with the given latest_version_id + fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()>; + + /// Set the client's latest_version_id fn set_client_latest_version_id( &mut self, client_id: Uuid, diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 1f6681454..3ca9135c9 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -12,6 +12,7 @@ chrono = { version = "0.4.10", features = ["serde"] } failure = {version = "0.1.5", features = ["derive"] } kv = {version = "0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "0.12.3"} +ureq = "1.5.2" [dev-dependencies] proptest = "0.9.4" diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 26e021957..78c49c6f6 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -2,7 +2,9 @@ pub(crate) mod test; mod local; +mod remote; mod types; pub use local::LocalServer; +pub use remote::RemoteServer; pub use types::*; diff --git a/taskchampion/src/server/remote.rs b/taskchampion/src/server/remote.rs new file mode 100644 index 000000000..3f429ae06 --- /dev/null +++ b/taskchampion/src/server/remote.rs @@ -0,0 +1,116 @@ +use crate::server::{AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId}; +use failure::{format_err, Fallible}; +use std::io::Read; +use ureq; +use uuid::Uuid; + +pub struct RemoteServer { + origin: String, + client_id: Uuid, + agent: ureq::Agent, +} + +/// A RemoeServer communicates with a remote server over HTTP (such as with +/// taskchampion-sync-server). +impl RemoteServer { + /// Construct a new RemoteServer. The `origin` is the sync server's protocol and hostname + /// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_id to + /// identify this client to the server. Multiple replicas synchronizing the same task history + /// should use the same client_id. + pub fn new(origin: String, client_id: Uuid) -> RemoteServer { + RemoteServer { + origin, + client_id, + agent: ureq::agent(), + } + } +} + +/// Convert a ureq::Response to an Error +fn resp_to_error(resp: ureq::Response) -> failure::Error { + return format_err!( + "error {}: {}", + resp.status(), + resp.into_string() + .unwrap_or_else(|e| format!("(could not read response: {})", e)) + ); +} + +/// Read a UUID-bearing header or fail trying +fn get_uuid_header(resp: &ureq::Response, name: &str) -> Fallible { + let value = resp + .header(name) + .ok_or_else(|| format_err!("Response does not have {} header", name))?; + let value = Uuid::parse_str(value) + .map_err(|e| format_err!("{} header is not a valid UUID: {}", name, e))?; + Ok(value) +} + +/// Get the body of a request as a HistorySegment +fn into_body(resp: ureq::Response) -> Fallible { + if let Some("application/vnd.taskchampion.history-segment") = resp.header("Content-Type") { + let mut reader = resp.into_reader(); + let mut bytes = vec![]; + reader.read_to_end(&mut bytes)?; + Ok(bytes) + } else { + Err(format_err!("Response did not have expected content-type")) + } +} + +impl Server for RemoteServer { + fn add_version( + &mut self, + parent_version_id: VersionId, + history_segment: HistorySegment, + ) -> Fallible { + let url = format!( + "{}/client/{}/add-version/{}", + self.origin, self.client_id, parent_version_id + ); + let resp = self + .agent + .post(&url) + .timeout_connect(10_000) + .timeout_read(60_000) + .set( + "Content-Type", + "application/vnd.taskchampion.history-segment", + ) + .send_bytes(&history_segment); + if resp.ok() { + let version_id = get_uuid_header(&resp, "X-Version-Id")?; + Ok(AddVersionResult::Ok(version_id)) + } else if resp.status() == 409 { + let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; + Ok(AddVersionResult::ExpectedParentVersion(parent_version_id)) + } else { + Err(resp_to_error(resp)) + } + } + + fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible { + let url = format!( + "{}/client/{}/get-child-version/{}", + self.origin, self.client_id, parent_version_id + ); + let resp = self + .agent + .get(&url) + .timeout_connect(10_000) + .timeout_read(60_000) + .call(); + + if resp.ok() { + Ok(GetVersionResult::Version { + version_id: get_uuid_header(&resp, "X-Version-Id")?, + parent_version_id: get_uuid_header(&resp, "X-Parent-Version-Id")?, + history_segment: into_body(resp)?, + }) + } else if resp.status() == 404 { + Ok(GetVersionResult::NoSuchVersion) + } else { + Err(resp_to_error(resp)) + } + } +} From 1a92613ddc84ced94d531b894027267efd51c8a6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 10:02:17 -0500 Subject: [PATCH 103/548] take configuration in env vars temporarily --- cli/src/cmd/shared.rs | 15 ++++++++++----- docs/src/usage.md | 14 ++++++++++++++ taskchampion/src/taskstorage/kv.rs | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index 6ae2ff4a3..474bca280 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -1,6 +1,7 @@ use clap::Arg; use failure::{format_err, Fallible}; -use std::path::Path; +use std::env; +use std::ffi::OsString; use taskchampion::{server, taskstorage, Replica, Task, Uuid}; pub(super) fn task_arg<'a>() -> Arg<'a, 'a> { @@ -46,14 +47,18 @@ impl CommandInvocation { // -- utilities for command invocations pub(super) fn get_replica(&self) -> Replica { - Replica::new(Box::new( - taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(), - )) + // temporarily use $TASK_DB to locate the taskdb + let taskdb_dir = env::var_os("TASK_DB").unwrap_or_else(|| OsString::from("/tmp/tasks")); + Replica::new(Box::new(taskstorage::KVStorage::new(taskdb_dir).unwrap())) } pub(super) fn get_server(&self) -> Fallible { + // temporarily use $SYNC_SERVER_ORIGIN for the sync server + let sync_server_origin = env::var_os("SYNC_SERVER_ORIGIN") + .map(|osstr| osstr.into_string().unwrap()) + .unwrap_or_else(|| String::from("http://localhost:8080")); Ok(server::RemoteServer::new( - "http://localhost:8080".into(), + sync_server_origin, Uuid::parse_str("d5b55cbd-9a82-4860-9a39-41b67893b22f").unwrap(), )) } diff --git a/docs/src/usage.md b/docs/src/usage.md index e284815bd..b16c724e1 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -1,6 +1,20 @@ # Usage +## `task` + The main interface to your tasks is the `task` command, which supports various subcommands. You can find a quick list of all subcommands with `task help`. Note that the `task` interface does not match that of TaskWarrior. + +### Configuration + +Temporarily, configuration is by environment variables. +The directory containing the replica's task data is given by `TASK_DB`, defaulting to `/tmp/tasks`. +the origin of the sync server is given by `SYNC_SERVER_ORIGIN`, defaulting to `http://localhost:8080`. + +## `taskchampion-sync-server` + +Run `taskchampion-sync-server` to start the sync server. +It serves on port 8080 on all interfaces, using an in-memory database (meaning that all data is lost when the process exits). +Requests for previously-unknown clients are automatically added. diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index 87d8416c3..5e1cfde41 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -23,7 +23,7 @@ const NEXT_OPERATION: u64 = 2; const NEXT_WORKING_SET_INDEX: u64 = 3; impl<'t> KVStorage<'t> { - pub fn new(directory: &Path) -> Fallible { + pub fn new>(directory: P) -> Fallible> { let mut config = Config::default(directory); config.bucket("tasks", None); config.bucket("numbers", None); From a665ff83de98f066287cbd4edc9d29625980c365 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 11:17:38 -0500 Subject: [PATCH 104/548] add 'task modify' (that can only modify description right now) --- cli/src/cmd/mod.rs | 6 +++-- cli/src/cmd/modify.rs | 63 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 cli/src/cmd/modify.rs diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index 3e55d0475..2705c90c0 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -9,6 +9,7 @@ mod add; mod gc; mod info; mod list; +mod modify; mod pending; mod sync; @@ -17,9 +18,10 @@ pub(crate) fn subcommands() -> Vec> { vec![ add::cmd(), gc::cmd(), - list::cmd(), - pending::cmd(), info::cmd(), + list::cmd(), + modify::cmd(), + pending::cmd(), sync::cmd(), ] } diff --git a/cli/src/cmd/modify.rs b/cli/src/cmd/modify.rs new file mode 100644 index 000000000..487084a53 --- /dev/null +++ b/cli/src/cmd/modify.rs @@ -0,0 +1,63 @@ +use crate::cmd::shared; +use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; + +use crate::cmd::{ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + task: String, + description: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("modify").about("modifies a task") + .arg(shared::task_arg()) + .arg( + Arg::with_name("description") + .help("task description") + .required(true), + ), + ) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("modify", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { + task: matches.value_of("task").unwrap().into(), + description: matches.value_of("description").unwrap().into(), + })), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica(); + let task = shared::get_task(&mut replica, &self.task)?; + + let mut task = task.into_mut(&mut replica); + task.set_description(self.description.clone())?; + println!("modified task {}", task.get_uuid()); + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!( + vec!["task", "modify", "2", "foo bar"], + |inv: &Invocation| { + assert_eq!(inv.task, "2".to_string()); + assert_eq!(inv.description, "foo bar".to_string()); + } + ); + } +} From 3c976a324e4b760f38fbfa11568a51a575991366 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 11:31:12 -0500 Subject: [PATCH 105/548] Create dependabot.yml --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..02e3e8249 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "rust" + directory: "/" # Location of package manifests + schedule: + interval: "daily" From 091f5b5b1795538fb88d6b626186ee5cff256860 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 15:13:30 -0500 Subject: [PATCH 106/548] use semver ranges for dependencies --- cli/Cargo.toml | 10 +++++----- sync-server/Cargo.toml | 10 +++++----- taskchampion/Cargo.toml | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index cf26fad96..89b1b9ed9 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -5,11 +5,11 @@ authors = ["Dustin J. Mitchell "] edition = "2018" [dependencies] -clap = "~2.33.0" +clap = "^2.33.0" taskchampion = { path = "../taskchampion" } -failure = "0.1.8" -prettytable-rs = "0.8.0" +failure = "^0.1.8" +prettytable-rs = "^0.8.0" [dev-dependencies] -assert_cmd = "1.0.1" -predicates = "1.0.5" +assert_cmd = "^1.0.1" +predicates = "^1.0.5" diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 109f7d393..a72fd691c 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -uuid = { version = "0.8.1", features = ["serde", "v4"] } -actix-web = "3.3.0" -failure = "0.1.8" -futures = "0.3.8" +uuid = { version = "^0.8.1", features = ["serde", "v4"] } +actix-web = "^3.3.0" +failure = "^0.1.8" +futures = "^0.3.8" [dev-dependencies] -actix-rt = "1.1.1" +actix-rt = "^1.1.1" diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 3ca9135c9..5dcbb497b 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -5,15 +5,15 @@ authors = ["Dustin J. Mitchell "] edition = "2018" [dependencies] -uuid = { version = "0.8.1", features = ["serde", "v4"] } -serde = "1.0.104" -serde_json = "1.0" -chrono = { version = "0.4.10", features = ["serde"] } -failure = {version = "0.1.5", features = ["derive"] } -kv = {version = "0.10.0", features = ["msgpack-value"]} -lmdb-rkv = {version = "0.12.3"} -ureq = "1.5.2" +uuid = { version = "^0.8.1", features = ["serde", "v4"] } +serde = "^1.0.104" +serde_json = "^1.0" +chrono = { version = "^0.4.10", features = ["serde"] } +failure = {version = "^0.1.5", features = ["derive"] } +kv = {version = "^0.10.0", features = ["msgpack-value"]} +lmdb-rkv = {version = "^0.12.3"} +ureq = "^1.5.2" [dev-dependencies] -proptest = "0.9.4" -tempdir = "0.3.7" +proptest = "^0.9.4" +tempdir = "^0.3.7" From 87596bb1f0c6979828bef14d17f51a81cd7713fb Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 15:14:49 -0500 Subject: [PATCH 107/548] update Cargo.lock to latest --- Cargo.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0d2e08d1..4147eb7c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,9 +308,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "assert_cmd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c88b9ca26f9c16ec830350d309397e74ee9abdfd8eb1f71cb6ecc71a3fc818da" +checksum = "3dc1679af9a1ab4bea16f228b05d18f8363f8327b1fa8db00d2760cfafc6b61e" dependencies = [ "doc-comment", "predicates", @@ -642,20 +642,20 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" dependencies = [ "autocfg 1.0.1", - "cfg-if 0.1.10", + "cfg-if 1.0.0", "lazy_static", ] [[package]] name = "csv" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4666154fd004af3fd6f1da2e81a96fd5a81927fe8ddb6ecc79e2aa6e138b54" +checksum = "f9d58633299b24b515ac72a3f869f8b91306a3cec616a602843a383acd6f9e97" dependencies = [ "bstr", "csv-core", @@ -1277,9 +1277,9 @@ dependencies = [ [[package]] name = "miow" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" dependencies = [ "kernel32-sys", "net2", @@ -1289,9 +1289,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" +checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02" dependencies = [ "cfg-if 0.1.10", "libc", @@ -1844,11 +1844,11 @@ dependencies = [ [[package]] name = "rust-argon2" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" dependencies = [ - "base64 0.12.3", + "base64 0.13.0", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -2098,9 +2098,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.50" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443b4178719c5a851e1bde36ce12da21d74a0e60b4d982ec3385a933c812f0f6" +checksum = "6c1e438504729046a5cfae47f97c30d6d083c7d91d94603efdae3477fc070d4c" dependencies = [ "proc-macro2", "quote", From 8af7ba286d67c838101e6df603398399df119da2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 16:57:32 -0500 Subject: [PATCH 108/548] Factor replica and sync configuration into simple owned structs --- cli/src/cmd/add.rs | 2 +- cli/src/cmd/gc.rs | 2 +- cli/src/cmd/info.rs | 2 +- cli/src/cmd/list.rs | 2 +- cli/src/cmd/modify.rs | 2 +- cli/src/cmd/pending.rs | 2 +- cli/src/cmd/shared.rs | 27 ++++++++++++++++++--------- cli/src/cmd/sync.rs | 2 +- docs/src/usage.md | 1 + taskchampion/src/config.rs | 26 ++++++++++++++++++++++++++ taskchampion/src/lib.rs | 2 ++ taskchampion/src/replica.rs | 10 ++++++++-- taskchampion/src/server/local.rs | 2 +- taskchampion/src/server/mod.rs | 12 ++++++++++++ taskchampion/src/taskdb.rs | 8 ++++---- taskchampion/src/taskstorage/mod.rs | 2 ++ 16 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 taskchampion/src/config.rs diff --git a/cli/src/cmd/add.rs b/cli/src/cmd/add.rs index cfc26b5a8..88e7a76f5 100644 --- a/cli/src/cmd/add.rs +++ b/cli/src/cmd/add.rs @@ -38,7 +38,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { let t = command - .get_replica() + .get_replica()? .new_task(Status::Pending, self.description.clone()) .unwrap(); println!("added task {}", t.get_uuid()); diff --git a/cli/src/cmd/gc.rs b/cli/src/cmd/gc.rs index d8948a715..15bdd288b 100644 --- a/cli/src/cmd/gc.rs +++ b/cli/src/cmd/gc.rs @@ -20,7 +20,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - command.get_replica().gc()?; + command.get_replica()?.gc()?; println!("garbage collected."); Ok(()) } diff --git a/cli/src/cmd/info.rs b/cli/src/cmd/info.rs index cdc39cc11..f55667dfb 100644 --- a/cli/src/cmd/info.rs +++ b/cli/src/cmd/info.rs @@ -30,7 +30,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let task = shared::get_task(&mut replica, &self.task)?; let uuid = task.get_uuid(); diff --git a/cli/src/cmd/list.rs b/cli/src/cmd/list.rs index d29227c92..72ee206e0 100644 --- a/cli/src/cmd/list.rs +++ b/cli/src/cmd/list.rs @@ -23,7 +23,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let mut t = Table::new(); t.set_format(table::format()); t.set_titles(row![b->"id", b->"description"]); diff --git a/cli/src/cmd/modify.rs b/cli/src/cmd/modify.rs index 487084a53..662941e34 100644 --- a/cli/src/cmd/modify.rs +++ b/cli/src/cmd/modify.rs @@ -36,7 +36,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let task = shared::get_task(&mut replica, &self.task)?; let mut task = task.into_mut(&mut replica); diff --git a/cli/src/cmd/pending.rs b/cli/src/cmd/pending.rs index b55c044d0..f35756b8b 100644 --- a/cli/src/cmd/pending.rs +++ b/cli/src/cmd/pending.rs @@ -25,7 +25,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let working_set = command.get_replica().working_set().unwrap(); + let working_set = command.get_replica()?.working_set().unwrap(); let mut t = Table::new(); t.set_format(table::format()); t.set_titles(row![b->"id", b->"description"]); diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index 474bca280..bcd6fbf0b 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -2,7 +2,7 @@ use clap::Arg; use failure::{format_err, Fallible}; use std::env; use std::ffi::OsString; -use taskchampion::{server, taskstorage, Replica, Task, Uuid}; +use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Task, Uuid}; pub(super) fn task_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("task") @@ -46,20 +46,29 @@ impl CommandInvocation { // -- utilities for command invocations - pub(super) fn get_replica(&self) -> Replica { + pub(super) fn get_replica(&self) -> Fallible { // temporarily use $TASK_DB to locate the taskdb let taskdb_dir = env::var_os("TASK_DB").unwrap_or_else(|| OsString::from("/tmp/tasks")); - Replica::new(Box::new(taskstorage::KVStorage::new(taskdb_dir).unwrap())) + let replica_config = ReplicaConfig { + taskdb_dir: taskdb_dir.into(), + }; + Ok(Replica::from_config(replica_config)?) } - pub(super) fn get_server(&self) -> Fallible { + pub(super) fn get_server(&self) -> Fallible> { // temporarily use $SYNC_SERVER_ORIGIN for the sync server - let sync_server_origin = env::var_os("SYNC_SERVER_ORIGIN") + let origin = env::var_os("SYNC_SERVER_ORIGIN") .map(|osstr| osstr.into_string().unwrap()) .unwrap_or_else(|| String::from("http://localhost:8080")); - Ok(server::RemoteServer::new( - sync_server_origin, - Uuid::parse_str("d5b55cbd-9a82-4860-9a39-41b67893b22f").unwrap(), - )) + let client_id = env::var_os("SYNC_SERVER_CLIENT_ID") + .ok_or_else(|| format_err!("SYNC_SERVER_CLIENT_ID not set"))?; + let client_id = client_id + .into_string() + .map_err(|_| format_err!("SYNC_SERVER_CLIENT_ID is not a valid UUID"))?; + let client_id = Uuid::parse_str(&client_id)?; + Ok(server::from_config(ServerConfig::Remote { + origin, + client_id, + })?) } } diff --git a/cli/src/cmd/sync.rs b/cli/src/cmd/sync.rs index a17920436..37c3a76d6 100644 --- a/cli/src/cmd/sync.rs +++ b/cli/src/cmd/sync.rs @@ -21,7 +21,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let mut server = command.get_server()?; replica.sync(&mut server)?; Ok(()) diff --git a/docs/src/usage.md b/docs/src/usage.md index b16c724e1..47ab9388b 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -12,6 +12,7 @@ Note that the `task` interface does not match that of TaskWarrior. Temporarily, configuration is by environment variables. The directory containing the replica's task data is given by `TASK_DB`, defaulting to `/tmp/tasks`. the origin of the sync server is given by `SYNC_SERVER_ORIGIN`, defaulting to `http://localhost:8080`. +The client ID to use with the sync server is givne by `SYNC_SERVER_CLIENT_ID` (with no default). ## `taskchampion-sync-server` diff --git a/taskchampion/src/config.rs b/taskchampion/src/config.rs new file mode 100644 index 000000000..8a8ee5bcd --- /dev/null +++ b/taskchampion/src/config.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; +use uuid::Uuid; + +/// The configuration required for a replica. Use with [`crate::Replica::from_config`]. +pub struct ReplicaConfig { + /// Path containing the task DB. + pub taskdb_dir: PathBuf, +} + +/// The configuration for a replica's access to a sync server. Use with +/// [`crate::server::from_config`]. +pub enum ServerConfig { + /// A local task database, for situations with a single replica. + Local { + /// Path containing the server's DB + server_dir: PathBuf, + }, + /// A remote taskchampion-sync-server instance + Remote { + /// Sync server "origin"; a URL with schema and hostname but no path or trailing `/` + origin: String, + + /// Client ID to identify this replica to the server + client_id: Uuid, + }, +} diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 932dbdeef..aa2c8bf99 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -23,6 +23,7 @@ for more information about the design and usage of the tool. */ +mod config; mod errors; mod replica; pub mod server; @@ -31,6 +32,7 @@ mod taskdb; pub mod taskstorage; mod utils; +pub use config::{ReplicaConfig, ServerConfig}; pub use replica::Replica; pub use task::Priority; pub use task::Status; diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index c33d31028..525be9888 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -1,8 +1,9 @@ +use crate::config::ReplicaConfig; use crate::errors::Error; use crate::server::Server; use crate::task::{Status, Task}; use crate::taskdb::TaskDB; -use crate::taskstorage::{Operation, TaskMap, TaskStorage}; +use crate::taskstorage::{KVStorage, Operation, TaskMap, TaskStorage}; use chrono::Utc; use failure::Fallible; use std::collections::HashMap; @@ -21,6 +22,11 @@ impl Replica { } } + pub fn from_config(config: ReplicaConfig) -> Fallible { + let storage = Box::new(KVStorage::new(config.taskdb_dir)?); + Ok(Replica::new(storage)) + } + #[cfg(test)] pub fn new_inmemory() -> Replica { Replica::new(Box::new(crate::taskstorage::InMemoryStorage::new())) @@ -140,7 +146,7 @@ impl Replica { } /// Synchronize this replica against the given server. - pub fn sync(&mut self, server: &mut dyn Server) -> Fallible<()> { + pub fn sync(&mut self, server: &mut Box) -> Fallible<()> { self.taskdb.sync(server) } diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 89775acf1..9cc4b2b28 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -25,7 +25,7 @@ pub struct LocalServer<'t> { impl<'t> LocalServer<'t> { /// A test server has no notion of clients, signatures, encryption, etc. - pub fn new(directory: &Path) -> Fallible { + pub fn new>(directory: P) -> Fallible> { let mut config = Config::default(directory); config.bucket("versions", None); config.bucket("numbers", None); diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 78c49c6f6..341428130 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -1,3 +1,6 @@ +use crate::ServerConfig; +use failure::Fallible; + #[cfg(test)] pub(crate) mod test; @@ -8,3 +11,12 @@ mod types; pub use local::LocalServer; pub use remote::RemoteServer; pub use types::*; + +pub fn from_config(config: ServerConfig) -> Fallible> { + Ok(match config { + ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), + ServerConfig::Remote { origin, client_id } => { + Box::new(RemoteServer::new(origin, client_id)) + } + }) +} diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index eb69e35ae..4cdb927ca 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -164,7 +164,7 @@ impl TaskDB { } /// Sync to the given server, pulling remote changes and pushing local changes. - pub fn sync(&mut self, server: &mut dyn Server) -> Fallible<()> { + pub fn sync(&mut self, server: &mut Box) -> Fallible<()> { let mut txn = self.storage.txn()?; // retry synchronizing until the server accepts our version (this allows for races between @@ -542,7 +542,7 @@ mod tests { #[test] fn test_sync() { - let mut server = TestServer::new(); + let mut server: Box = Box::new(TestServer::new()); let mut db1 = newdb(); db1.sync(&mut server).unwrap(); @@ -602,7 +602,7 @@ mod tests { #[test] fn test_sync_create_delete() { - let mut server = TestServer::new(); + let mut server: Box = Box::new(TestServer::new()); let mut db1 = newdb(); db1.sync(&mut server).unwrap(); @@ -692,7 +692,7 @@ mod tests { // and delete operations that results in a task existing in one TaskDB but not existing in // another. So, the generated sequences focus on a single task UUID. fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { - let mut server = TestServer::new(); + let mut server: Box = Box::new(TestServer::new()); let mut dbs = [newdb(), newdb(), newdb()]; for (action, db) in action_sequence { diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index 4ddd7df75..b5f94c8ff 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -2,11 +2,13 @@ use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; +#[cfg(test)] mod inmemory; mod kv; mod operation; pub use self::kv::KVStorage; +#[cfg(test)] pub use inmemory::InMemoryStorage; pub use operation::Operation; From 0e926df578812e57049daa5850b75a30ec942960 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 18:18:43 -0500 Subject: [PATCH 109/548] Add configuration-file support to the 'task' command --- Cargo.lock | 178 ++++++++++++++++++++++++++++++++++++------ cli/Cargo.toml | 2 + cli/src/cmd/shared.rs | 42 ++++++---- cli/src/lib.rs | 1 + cli/src/settings.rs | 35 +++++++++ docs/src/usage.md | 23 ++++-- 6 files changed, 236 insertions(+), 45 deletions(-) create mode 100644 cli/src/settings.rs diff --git a/Cargo.lock b/Cargo.lock index 4147eb7c9..9a683e918 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,7 +74,7 @@ dependencies = [ "pin-project 1.0.2", "rand 0.7.3", "regex", - "serde", + "serde 1.0.117", "serde_json", "serde_urlencoded", "sha-1", @@ -102,7 +102,7 @@ dependencies = [ "http", "log", "regex", - "serde", + "serde 1.0.117", ] [[package]] @@ -241,7 +241,7 @@ dependencies = [ "mime", "pin-project 1.0.2", "regex", - "serde", + "serde 1.0.117", "serde_json", "serde_urlencoded", "socket2", @@ -372,7 +372,7 @@ dependencies = [ "mime", "percent-encoding", "rand 0.7.3", - "serde", + "serde 1.0.117", "serde_json", "serde_urlencoded", ] @@ -479,7 +479,7 @@ dependencies = [ "lazy_static", "memchr", "regex-automata", - "serde", + "serde 1.0.117", ] [[package]] @@ -535,8 +535,8 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits", - "serde", + "num-traits 0.2.14", + "serde 1.0.117", "time 0.1.44", "winapi 0.3.9", ] @@ -580,6 +580,22 @@ dependencies = [ "bitflags", ] +[[package]] +name = "config" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +dependencies = [ + "lazy_static", + "nom", + "rust-ini", + "serde 1.0.117", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "const_fn" version = "0.4.3" @@ -613,7 +629,7 @@ dependencies = [ "idna", "log", "publicsuffix", - "serde", + "serde 1.0.117", "serde_json", "time 0.2.23", "url", @@ -661,7 +677,7 @@ dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde 1.0.117", ] [[package]] @@ -710,6 +726,26 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "dirs" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + [[package]] name = "discard" version = "1.0.4" @@ -804,7 +840,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" dependencies = [ - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -1132,7 +1168,7 @@ checksum = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" dependencies = [ "lmdb-rkv", "rmp-serde", - "serde", + "serde 1.0.117", "thiserror", "toml", ] @@ -1149,12 +1185,35 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 0.1.10", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +[[package]] +name = "linked-hash-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" +dependencies = [ + "serde 0.8.23", + "serde_test", +] + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -1208,7 +1267,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ - "linked-hash-map", + "linked-hash-map 0.5.3", ] [[package]] @@ -1298,6 +1357,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1311,7 +1381,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg 1.0.1", - "num-traits", + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", ] [[package]] @@ -1527,7 +1606,7 @@ dependencies = [ "bitflags", "byteorder", "lazy_static", - "num-traits", + "num-traits 0.2.14", "quick-error", "rand 0.6.5", "rand_chacha 0.1.1", @@ -1828,7 +1907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" dependencies = [ "byteorder", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -1839,7 +1918,7 @@ checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" dependencies = [ "byteorder", "rmp", - "serde", + "serde 1.0.117", ] [[package]] @@ -1854,6 +1933,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + [[package]] name = "rustc-demangle" version = "0.1.18" @@ -1931,6 +2016,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.117" @@ -1940,6 +2031,19 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "linked-hash-map 0.3.0", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + [[package]] name = "serde_derive" version = "1.0.117" @@ -1959,7 +2063,16 @@ checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ "itoa", "ryu", - "serde", + "serde 1.0.117", +] + +[[package]] +name = "serde_test" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" +dependencies = [ + "serde 0.8.23", ] [[package]] @@ -1971,7 +2084,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde", + "serde 1.0.117", ] [[package]] @@ -2041,6 +2154,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stdweb" version = "0.4.20" @@ -2063,7 +2182,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", - "serde", + "serde 1.0.117", "serde_derive", "syn", ] @@ -2077,7 +2196,7 @@ dependencies = [ "base-x", "proc-macro2", "quote", - "serde", + "serde 1.0.117", "serde_derive", "serde_json", "sha1", @@ -2128,7 +2247,7 @@ dependencies = [ "kv", "lmdb-rkv", "proptest", - "serde", + "serde 1.0.117", "serde_json", "tempdir", "ureq", @@ -2141,6 +2260,8 @@ version = "0.1.0" dependencies = [ "assert_cmd", "clap", + "config", + "dirs 3.0.1", "failure", "predicates", "prettytable-rs", @@ -2189,7 +2310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", - "dirs", + "dirs 1.0.5", "winapi 0.3.9", ] @@ -2344,7 +2465,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ - "serde", + "serde 1.0.117", ] [[package]] @@ -2510,7 +2631,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ "rand 0.7.3", - "serde", + "serde 1.0.117", ] [[package]] @@ -2687,3 +2808,12 @@ dependencies = [ "winapi 0.2.8", "winapi-build", ] + +[[package]] +name = "yaml-rust" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +dependencies = [ + "linked-hash-map 0.5.3", +] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 89b1b9ed9..2f8ff3c83 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,6 +9,8 @@ clap = "^2.33.0" taskchampion = { path = "../taskchampion" } failure = "^0.1.8" prettytable-rs = "^0.8.0" +config = "^0.10.1" +dirs = "^3.0.1" [dev-dependencies] assert_cmd = "^1.0.1" diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index bcd6fbf0b..9744ea6f3 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -1,7 +1,8 @@ +use crate::settings; use clap::Arg; +use config::{Config, ConfigError}; use failure::{format_err, Fallible}; -use std::env; -use std::ffi::OsString; +use std::cell::{Ref, RefCell}; use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Task, Uuid}; pub(super) fn task_arg<'a>() -> Arg<'a, 'a> { @@ -33,11 +34,15 @@ pub(super) fn get_task>(replica: &mut Replica, task_arg: S) -> Fal #[derive(Debug)] pub struct CommandInvocation { pub(crate) subcommand: Box, + settings: RefCell, } impl CommandInvocation { pub(crate) fn new(subcommand: Box) -> Self { - Self { subcommand } + Self { + subcommand, + settings: RefCell::new(Config::default()), + } } pub fn run(self) -> Fallible<()> { @@ -46,28 +51,33 @@ impl CommandInvocation { // -- utilities for command invocations + pub(super) fn get_settings(&self) -> Fallible> { + { + // use the special `_loaded" config value to detect whether we have + // loaded the configuration yet + let mut settings = self.settings.borrow_mut(); + if let Err(ConfigError::NotFound(_)) = settings.get_bool("_loaded") { + settings.merge(settings::read_settings()?)?; + settings.set("_loaded", true)?; + } + } + Ok(self.settings.borrow()) + } + pub(super) fn get_replica(&self) -> Fallible { - // temporarily use $TASK_DB to locate the taskdb - let taskdb_dir = env::var_os("TASK_DB").unwrap_or_else(|| OsString::from("/tmp/tasks")); + let settings = self.get_settings()?; let replica_config = ReplicaConfig { - taskdb_dir: taskdb_dir.into(), + taskdb_dir: settings.get_str("data_dir")?.into(), }; Ok(Replica::from_config(replica_config)?) } pub(super) fn get_server(&self) -> Fallible> { - // temporarily use $SYNC_SERVER_ORIGIN for the sync server - let origin = env::var_os("SYNC_SERVER_ORIGIN") - .map(|osstr| osstr.into_string().unwrap()) - .unwrap_or_else(|| String::from("http://localhost:8080")); - let client_id = env::var_os("SYNC_SERVER_CLIENT_ID") - .ok_or_else(|| format_err!("SYNC_SERVER_CLIENT_ID not set"))?; - let client_id = client_id - .into_string() - .map_err(|_| format_err!("SYNC_SERVER_CLIENT_ID is not a valid UUID"))?; + let settings = self.get_settings()?; + let client_id = settings.get_str("server_client_id")?; let client_id = Uuid::parse_str(&client_id)?; Ok(server::from_config(ServerConfig::Remote { - origin, + origin: settings.get_str("server_origin")?, client_id, })?) } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 48421f25a..4474af8f3 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -3,6 +3,7 @@ use failure::Fallible; use std::ffi::OsString; mod cmd; +pub(crate) mod settings; mod table; use cmd::ArgMatchResult; diff --git a/cli/src/settings.rs b/cli/src/settings.rs new file mode 100644 index 000000000..f8a92582e --- /dev/null +++ b/cli/src/settings.rs @@ -0,0 +1,35 @@ +use config::{Config, Environment, File, FileSourceFile}; +use failure::Fallible; +use std::env; +use std::path::PathBuf; + +pub(crate) fn read_settings() -> Fallible { + let mut settings = Config::default(); + + // set up defaults + if let Some(mut dir) = dirs::data_local_dir() { + dir.push("taskchampion"); + settings.set_default( + "data_dir", + // the config crate does not support non-string paths + dir.to_str().expect("data_local_dir is not utf-8"), + )?; + } + + // load either from the path in TASKCHAMPION_CONFIG, or from CONFIG_DIR/taskchampion + if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") { + let config_file: PathBuf = config_file.into(); + let config_file: File = config_file.into(); + settings.merge(config_file.required(true))?; + env::remove_var("TASKCHAMPION_CONFIG"); + } else if let Some(mut dir) = dirs::config_dir() { + dir.push("taskchampion"); + let config_file: File = dir.into(); + settings.merge(config_file.required(false))?; + } + + // merge environment variables + settings.merge(Environment::with_prefix("TASKCHAMPION"))?; + + Ok(settings) +} diff --git a/docs/src/usage.md b/docs/src/usage.md index 47ab9388b..329cdf1eb 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -9,13 +9,26 @@ Note that the `task` interface does not match that of TaskWarrior. ### Configuration -Temporarily, configuration is by environment variables. -The directory containing the replica's task data is given by `TASK_DB`, defaulting to `/tmp/tasks`. -the origin of the sync server is given by `SYNC_SERVER_ORIGIN`, defaulting to `http://localhost:8080`. -The client ID to use with the sync server is givne by `SYNC_SERVER_CLIENT_ID` (with no default). +The `task` command will work out-of-the-box with no configuration file, using default values. + +Configuration is read from `taskchampion.yaml` (or `taskchampion.toml` or `taskchmapion.json` if you prefer) in your config directory. +On Linux systems, that directory is `~/.config`. +On OS X, it's `~/Library/Preferences`. +On Windows, it's `AppData/Roaming` in your home directory. +The path can be overridden by setting `$TASKCHAMPION_CONFIG`. + +Individual configuration parameters can be overridden by environemnt variables, converted to upper-case and prefixed with `TASKCHAMPION_`, e.g., `TASKCHAMPION_DATA_DIR`. +Nested configuration parameters cannot be overridden by environment variables. + +The following configuration parameters are available: + +* `data_dir` - path to a directory containing the replica's task data (which will be created if necessary). + Default: `taskchampion` in the local data directory +* `server_origin` - Origin of the taskchampion sync server, e.g., `https://taskchampion.example.com` +* `server_client_id` - Client ID to identify this replica to the sync server (a UUID) ## `taskchampion-sync-server` Run `taskchampion-sync-server` to start the sync server. It serves on port 8080 on all interfaces, using an in-memory database (meaning that all data is lost when the process exits). -Requests for previously-unknown clients are automatically added. +Requests for previously-unknown clients automatically create the client. From 6d08eacd98b873417435a72eff5c4fa26d338561 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 19:43:30 -0500 Subject: [PATCH 110/548] limit config file usage to just yaml --- Cargo.lock | 107 +++++++++++----------------------------------- cli/Cargo.toml | 2 +- docs/src/usage.md | 2 +- 3 files changed, 27 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a683e918..6b649fe16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,7 +74,7 @@ dependencies = [ "pin-project 1.0.2", "rand 0.7.3", "regex", - "serde 1.0.117", + "serde", "serde_json", "serde_urlencoded", "sha-1", @@ -102,7 +102,7 @@ dependencies = [ "http", "log", "regex", - "serde 1.0.117", + "serde", ] [[package]] @@ -241,7 +241,7 @@ dependencies = [ "mime", "pin-project 1.0.2", "regex", - "serde 1.0.117", + "serde", "serde_json", "serde_urlencoded", "socket2", @@ -372,7 +372,7 @@ dependencies = [ "mime", "percent-encoding", "rand 0.7.3", - "serde 1.0.117", + "serde", "serde_json", "serde_urlencoded", ] @@ -479,7 +479,7 @@ dependencies = [ "lazy_static", "memchr", "regex-automata", - "serde 1.0.117", + "serde", ] [[package]] @@ -535,8 +535,8 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits 0.2.14", - "serde 1.0.117", + "num-traits", + "serde", "time 0.1.44", "winapi 0.3.9", ] @@ -588,11 +588,7 @@ checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" dependencies = [ "lazy_static", "nom", - "rust-ini", - "serde 1.0.117", - "serde-hjson", - "serde_json", - "toml", + "serde", "yaml-rust", ] @@ -629,7 +625,7 @@ dependencies = [ "idna", "log", "publicsuffix", - "serde 1.0.117", + "serde", "serde_json", "time 0.2.23", "url", @@ -677,7 +673,7 @@ dependencies = [ "csv-core", "itoa", "ryu", - "serde 1.0.117", + "serde", ] [[package]] @@ -840,7 +836,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" dependencies = [ - "num-traits 0.2.14", + "num-traits", ] [[package]] @@ -1168,7 +1164,7 @@ checksum = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" dependencies = [ "lmdb-rkv", "rmp-serde", - "serde 1.0.117", + "serde", "thiserror", "toml", ] @@ -1204,16 +1200,6 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" -[[package]] -name = "linked-hash-map" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" -dependencies = [ - "serde 0.8.23", - "serde_test", -] - [[package]] name = "linked-hash-map" version = "0.5.3" @@ -1267,7 +1253,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ - "linked-hash-map 0.5.3", + "linked-hash-map", ] [[package]] @@ -1381,16 +1367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg 1.0.1", - "num-traits 0.2.14", -] - -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -dependencies = [ - "num-traits 0.2.14", + "num-traits", ] [[package]] @@ -1606,7 +1583,7 @@ dependencies = [ "bitflags", "byteorder", "lazy_static", - "num-traits 0.2.14", + "num-traits", "quick-error", "rand 0.6.5", "rand_chacha 0.1.1", @@ -1907,7 +1884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" dependencies = [ "byteorder", - "num-traits 0.2.14", + "num-traits", ] [[package]] @@ -1918,7 +1895,7 @@ checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" dependencies = [ "byteorder", "rmp", - "serde 1.0.117", + "serde", ] [[package]] @@ -1933,12 +1910,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rust-ini" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" - [[package]] name = "rustc-demangle" version = "0.1.18" @@ -2016,12 +1987,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "serde" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" - [[package]] name = "serde" version = "1.0.117" @@ -2031,19 +1996,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-hjson" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" -dependencies = [ - "lazy_static", - "linked-hash-map 0.3.0", - "num-traits 0.1.43", - "regex", - "serde 0.8.23", -] - [[package]] name = "serde_derive" version = "1.0.117" @@ -2063,16 +2015,7 @@ checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ "itoa", "ryu", - "serde 1.0.117", -] - -[[package]] -name = "serde_test" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" -dependencies = [ - "serde 0.8.23", + "serde", ] [[package]] @@ -2084,7 +2027,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.117", + "serde", ] [[package]] @@ -2182,7 +2125,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", - "serde 1.0.117", + "serde", "serde_derive", "syn", ] @@ -2196,7 +2139,7 @@ dependencies = [ "base-x", "proc-macro2", "quote", - "serde 1.0.117", + "serde", "serde_derive", "serde_json", "sha1", @@ -2247,7 +2190,7 @@ dependencies = [ "kv", "lmdb-rkv", "proptest", - "serde 1.0.117", + "serde", "serde_json", "tempdir", "ureq", @@ -2465,7 +2408,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ - "serde 1.0.117", + "serde", ] [[package]] @@ -2631,7 +2574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ "rand 0.7.3", - "serde 1.0.117", + "serde", ] [[package]] @@ -2815,5 +2758,5 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" dependencies = [ - "linked-hash-map 0.5.3", + "linked-hash-map", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 2f8ff3c83..36e313f8e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,7 +9,7 @@ clap = "^2.33.0" taskchampion = { path = "../taskchampion" } failure = "^0.1.8" prettytable-rs = "^0.8.0" -config = "^0.10.1" +config = { version="^0.10.1", default-features=false, features=["yaml"] } dirs = "^3.0.1" [dev-dependencies] diff --git a/docs/src/usage.md b/docs/src/usage.md index 329cdf1eb..37123bfd2 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -11,7 +11,7 @@ Note that the `task` interface does not match that of TaskWarrior. The `task` command will work out-of-the-box with no configuration file, using default values. -Configuration is read from `taskchampion.yaml` (or `taskchampion.toml` or `taskchmapion.json` if you prefer) in your config directory. +Configuration is read from `taskchampion.yaml` in your config directory. On Linux systems, that directory is `~/.config`. On OS X, it's `~/Library/Preferences`. On Windows, it's `AppData/Roaming` in your home directory. From 0e4ab4b88f48354923fca96e6c4eeed26a58e7c3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 22:46:13 -0500 Subject: [PATCH 111/548] Add start and stop commands --- cli/src/cmd/debug.rs | 57 ++++++++++++ cli/src/cmd/info.rs | 1 + cli/src/cmd/list.rs | 8 +- cli/src/cmd/mod.rs | 6 ++ cli/src/cmd/pending.rs | 8 +- cli/src/cmd/start.rs | 48 ++++++++++ cli/src/cmd/stop.rs | 48 ++++++++++ docs/src/tasks.md | 1 + taskchampion/src/task.rs | 192 ++++++++++++++++++++++++++++++++++++++- 9 files changed, 360 insertions(+), 9 deletions(-) create mode 100644 cli/src/cmd/debug.rs create mode 100644 cli/src/cmd/start.rs create mode 100644 cli/src/cmd/stop.rs diff --git a/cli/src/cmd/debug.rs b/cli/src/cmd/debug.rs new file mode 100644 index 000000000..f81f605b7 --- /dev/null +++ b/cli/src/cmd/debug.rs @@ -0,0 +1,57 @@ +use crate::table; +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; +use prettytable::{cell, row, Table}; + +use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + task: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("debug") + .about("debug info for the given task") + .arg(shared::task_arg())) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("debug", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { + task: matches.value_of("task").unwrap().into(), + })), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica(); + let task = shared::get_task(&mut replica, &self.task)?; + + let mut t = Table::new(); + t.set_format(table::format()); + t.set_titles(row![b->"key", b->"value"]); + for (k, v) in task.get_taskmap().iter() { + t.add_row(row![k, v]); + } + t.printstd(); + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "debug", "1"], |inv: &Invocation| { + assert_eq!(inv.task, "1".to_string()); + }); + } +} diff --git a/cli/src/cmd/info.rs b/cli/src/cmd/info.rs index cdc39cc11..cb677fb8a 100644 --- a/cli/src/cmd/info.rs +++ b/cli/src/cmd/info.rs @@ -42,6 +42,7 @@ subcommand_invocation! { } t.add_row(row![b->"Description", task.get_description()]); t.add_row(row![b->"Status", task.get_status()]); + t.add_row(row![b->"Active", task.is_active()]); t.printstd(); Ok(()) } diff --git a/cli/src/cmd/list.rs b/cli/src/cmd/list.rs index d29227c92..4a95f85eb 100644 --- a/cli/src/cmd/list.rs +++ b/cli/src/cmd/list.rs @@ -26,13 +26,17 @@ subcommand_invocation! { let mut replica = command.get_replica(); let mut t = Table::new(); t.set_format(table::format()); - t.set_titles(row![b->"id", b->"description"]); + t.set_titles(row![b->"id", b->"act", b->"description"]); for (uuid, task) in replica.all_tasks().unwrap() { let mut id = uuid.to_string(); if let Some(i) = replica.get_working_set_index(&uuid)? { id = i.to_string(); } - t.add_row(row![id, task.get_description()]); + let active = match task.is_active() { + true => "*", + false => "", + }; + t.add_row(row![id, active, task.get_description()]); } t.printstd(); Ok(()) diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index 2705c90c0..d58237816 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -6,22 +6,28 @@ mod macros; mod shared; mod add; +mod debug; mod gc; mod info; mod list; mod modify; mod pending; +mod start; +mod stop; mod sync; /// Get a list of all subcommands in this crate pub(crate) fn subcommands() -> Vec> { vec![ add::cmd(), + debug::cmd(), gc::cmd(), info::cmd(), list::cmd(), modify::cmd(), pending::cmd(), + start::cmd(), + stop::cmd(), sync::cmd(), ] } diff --git a/cli/src/cmd/pending.rs b/cli/src/cmd/pending.rs index b55c044d0..b2a93df24 100644 --- a/cli/src/cmd/pending.rs +++ b/cli/src/cmd/pending.rs @@ -28,10 +28,14 @@ subcommand_invocation! { let working_set = command.get_replica().working_set().unwrap(); let mut t = Table::new(); t.set_format(table::format()); - t.set_titles(row![b->"id", b->"description"]); + t.set_titles(row![b->"id", b->"act", b->"description"]); for (i, item) in working_set.iter().enumerate() { if let Some(ref task) = item { - t.add_row(row![i, task.get_description()]); + let active = match task.is_active() { + true => "*", + false => "", + }; + t.add_row(row![i, active, task.get_description()]); } } t.printstd(); diff --git a/cli/src/cmd/start.rs b/cli/src/cmd/start.rs new file mode 100644 index 000000000..95365003c --- /dev/null +++ b/cli/src/cmd/start.rs @@ -0,0 +1,48 @@ +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; + +use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + task: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("start") + .about("start the given task") + .arg(shared::task_arg())) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("start", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { + task: matches.value_of("task").unwrap().into(), + })), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica(); + let task = shared::get_task(&mut replica, &self.task)?; + task.into_mut(&mut replica).start()?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "start", "1"], |inv: &Invocation| { + assert_eq!(inv.task, "1".to_string()); + }); + } +} diff --git a/cli/src/cmd/stop.rs b/cli/src/cmd/stop.rs new file mode 100644 index 000000000..d61eb8b51 --- /dev/null +++ b/cli/src/cmd/stop.rs @@ -0,0 +1,48 @@ +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; + +use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + task: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("stop") + .about("stop the given task") + .arg(shared::task_arg())) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("stop", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { + task: matches.value_of("task").unwrap().into(), + })), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica(); + let task = shared::get_task(&mut replica, &self.task)?; + task.into_mut(&mut replica).stop()?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "stop", "1"], |inv: &Invocation| { + assert_eq!(inv.task, "1".to_string()); + }); + } +} diff --git a/docs/src/tasks.md b/docs/src/tasks.md index 5727237dc..d53715bbd 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -30,6 +30,7 @@ The following keys, and key formats, are defined: * `status` - one of `P` for a pending task (the default), `C` for completed or `D` for deleted * `description` - the one-line summary of the task * `modified` - the time of the last modification of this task +* `start.` - either an empty string (representing work on the task to the task that has not been stopped) or a timestamp (representing the time that work stopped) The following are not yet implemented: diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 332eb57b1..a5569161c 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -118,6 +118,10 @@ impl Task { &self.uuid } + pub fn get_taskmap(&self) -> &TaskMap { + &self.taskmap + } + /// Prepare to mutate this task, requiring a mutable Replica /// in order to update the data it contains. pub fn into_mut(self, replica: &mut Replica) -> TaskMut { @@ -142,6 +146,16 @@ impl Task { .unwrap_or("") } + /// Determine whether this task is active -- that is, that it has been started + /// and not stopped. + pub fn is_active(&self) -> bool { + self.taskmap + .iter() + .filter(|(k, v)| k.starts_with("start.") && v.is_empty()) + .next() + .is_some() + } + pub fn get_modified(&self) -> Option> { self.get_timestamp("modified") } @@ -184,6 +198,33 @@ impl<'r> TaskMut<'r> { self.set_timestamp("modified", Some(modified)) } + /// Start the task by creating "start. Fallible<()> { + if self.is_active() { + return Ok(()); + } + let k = format!("start.{}", Utc::now().timestamp()); + self.set_string(k.as_ref(), Some(String::from(""))) + } + + /// Stop the task by adding the current timestamp to all un-resolved "start." keys. + pub fn stop(&mut self) -> Fallible<()> { + let keys = self + .taskmap + .iter() + .filter(|(k, v)| k.starts_with("start.") && v.is_empty()) + .map(|(k, _)| k) + .cloned() + .collect::>(); + let now = Utc::now(); + for key in keys { + println!("{}", key); + self.set_timestamp(&key, Some(now))?; + } + Ok(()) + } + // -- utility functions fn lastmod(&mut self) -> Fallible<()> { @@ -212,11 +253,26 @@ impl<'r> TaskMut<'r> { fn set_timestamp(&mut self, property: &str, value: Option>) -> Fallible<()> { self.lastmod()?; - self.replica.update_task( - self.task.uuid, - property, - value.map(|v| format!("{}", v.timestamp())), - ) + if let Some(value) = value { + let ts = format!("{}", value.timestamp()); + self.replica + .update_task(self.task.uuid, property, Some(ts.clone()))?; + self.task.taskmap.insert(property.to_string(), ts); + } else { + self.replica + .update_task::<_, &str>(self.task.uuid, property, None)?; + self.task.taskmap.remove(property); + } + Ok(()) + } + + /// Used by tests to ensure that updates are properly written + #[cfg(test)] + fn reload(&mut self) -> Fallible<()> { + let uuid = self.uuid; + let task = self.replica.get_task(&uuid)?.unwrap(); + self.task.taskmap = task.taskmap; + Ok(()) } } @@ -232,6 +288,132 @@ impl<'r> std::ops::Deref for TaskMut<'r> { mod test { use super::*; + fn with_mut_task(f: F) { + let mut replica = Replica::new_inmemory(); + let task = replica.new_task(Status::Pending, "test".into()).unwrap(); + let task = task.into_mut(&mut replica); + f(task) + } + + #[test] + fn test_is_active_never_started() { + let task = Task::new(Uuid::new_v4(), TaskMap::new()); + assert!(!task.is_active()); + } + + #[test] + fn test_is_active() { + let task = Task::new( + Uuid::new_v4(), + vec![(String::from("start.1234"), String::from(""))] + .drain(..) + .collect(), + ); + + assert!(task.is_active()); + } + + #[test] + fn test_is_active_stopped() { + let task = Task::new( + Uuid::new_v4(), + vec![(String::from("start.1234"), String::from("1235"))] + .drain(..) + .collect(), + ); + + assert!(!task.is_active()); + } + + fn count_taskmap(task: &TaskMut, f: fn(&(&String, &String)) -> bool) -> usize { + task.taskmap.iter().filter(f).count() + } + + #[test] + fn test_start() { + with_mut_task(|mut task| { + task.start().unwrap(); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), + 1 + ); + task.reload().unwrap(); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), + 1 + ); + + // second start doesn't change anything.. + task.start().unwrap(); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), + 1 + ); + task.reload().unwrap(); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), + 1 + ); + }); + } + + #[test] + fn test_stop() { + with_mut_task(|mut task| { + task.start().unwrap(); + task.stop().unwrap(); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), + 0 + ); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && !v.is_empty()), + 1 + ); + task.reload().unwrap(); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), + 0 + ); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && !v.is_empty()), + 1 + ); + }); + } + + #[test] + fn test_stop_multiple() { + with_mut_task(|mut task| { + // simulate a task that has (through the synchronization process) been started twice + task.task + .taskmap + .insert(String::from("start.1234"), String::from("")); + task.task + .taskmap + .insert(String::from("start.5678"), String::from("")); + + task.stop().unwrap(); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), + 0 + ); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && !v.is_empty()), + 2 + ); + task.reload().unwrap(); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), + 0 + ); + assert_eq!( + count_taskmap(&task, |(k, v)| k.starts_with("start.") && !v.is_empty()), + 2 + ); + }); + } + #[test] fn test_priority() { assert_eq!(Priority::L.to_taskmap(), "L"); From d832b0b8598a8ce1f19c2dcd2fbc42c388b8a744 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 11:18:29 -0500 Subject: [PATCH 112/548] fix merge error --- cli/src/cmd/debug.rs | 2 +- cli/src/cmd/start.rs | 2 +- cli/src/cmd/stop.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/cmd/debug.rs b/cli/src/cmd/debug.rs index f81f605b7..8ebee75c6 100644 --- a/cli/src/cmd/debug.rs +++ b/cli/src/cmd/debug.rs @@ -30,7 +30,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let task = shared::get_task(&mut replica, &self.task)?; let mut t = Table::new(); diff --git a/cli/src/cmd/start.rs b/cli/src/cmd/start.rs index 95365003c..5e09a5c13 100644 --- a/cli/src/cmd/start.rs +++ b/cli/src/cmd/start.rs @@ -28,7 +28,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let task = shared::get_task(&mut replica, &self.task)?; task.into_mut(&mut replica).start()?; Ok(()) diff --git a/cli/src/cmd/stop.rs b/cli/src/cmd/stop.rs index d61eb8b51..baf278734 100644 --- a/cli/src/cmd/stop.rs +++ b/cli/src/cmd/stop.rs @@ -28,7 +28,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let task = shared::get_task(&mut replica, &self.task)?; task.into_mut(&mut replica).stop()?; Ok(()) From 42d988d601e774a0ea20c90408b7ebb7d179b1d5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 14:05:51 -0500 Subject: [PATCH 113/548] fix clippy warnings --- sync-server/src/storage/inmemory.rs | 5 +++-- taskchampion/src/server/remote.rs | 1 - taskchampion/src/task.rs | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs index cd8943d1e..68f25cf92 100644 --- a/sync-server/src/storage/inmemory.rs +++ b/sync-server/src/storage/inmemory.rs @@ -39,7 +39,7 @@ impl<'a> StorageTxn for InnerTxn<'a> { } fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()> { - if let Some(_) = self.0.clients.get(&client_id) { + if self.0.clients.get(&client_id).is_some() { return Err(format_err!("Client {} already exists", client_id)); } self.0 @@ -54,7 +54,8 @@ impl<'a> StorageTxn for InnerTxn<'a> { latest_version_id: Uuid, ) -> Fallible<()> { if let Some(client) = self.0.clients.get_mut(&client_id) { - Ok(client.latest_version_id = latest_version_id) + client.latest_version_id = latest_version_id; + Ok(()) } else { Err(format_err!("Client {} does not exist", client_id)) } diff --git a/taskchampion/src/server/remote.rs b/taskchampion/src/server/remote.rs index 3f429ae06..9392e055f 100644 --- a/taskchampion/src/server/remote.rs +++ b/taskchampion/src/server/remote.rs @@ -1,7 +1,6 @@ use crate::server::{AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId}; use failure::{format_err, Fallible}; use std::io::Read; -use ureq; use uuid::Uuid; pub struct RemoteServer { diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index a5569161c..20ac753d5 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -151,9 +151,7 @@ impl Task { pub fn is_active(&self) -> bool { self.taskmap .iter() - .filter(|(k, v)| k.starts_with("start.") && v.is_empty()) - .next() - .is_some() + .any(|(k, v)| k.starts_with("start.") && v.is_empty()) } pub fn get_modified(&self) -> Option> { From 29ab99339700c44a1fa761c815ce10c0093663b8 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 11:23:19 -0500 Subject: [PATCH 114/548] add done --- cli/src/cmd/done.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++ cli/src/cmd/list.rs | 4 ++++ cli/src/cmd/mod.rs | 2 ++ 3 files changed, 57 insertions(+) create mode 100644 cli/src/cmd/done.rs diff --git a/cli/src/cmd/done.rs b/cli/src/cmd/done.rs new file mode 100644 index 000000000..713a747a8 --- /dev/null +++ b/cli/src/cmd/done.rs @@ -0,0 +1,51 @@ +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; +use taskchampion::Status; + +use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + task: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("done") + .about("finish the given task (status Completed)") + .arg(shared::task_arg())) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("done", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { + task: matches.value_of("task").unwrap().into(), + })), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica()?; + let task = shared::get_task(&mut replica, &self.task)?; + let mut task = task.into_mut(&mut replica); + task.stop()?; + task.set_status(Status::Completed)?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "done", "1"], |inv: &Invocation| { + assert_eq!(inv.task, "1".to_string()); + }); + } +} diff --git a/cli/src/cmd/list.rs b/cli/src/cmd/list.rs index 4dddd5265..7b1b5078d 100644 --- a/cli/src/cmd/list.rs +++ b/cli/src/cmd/list.rs @@ -2,6 +2,7 @@ use crate::table; use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; use failure::Fallible; use prettytable::{cell, row, Table}; +use taskchampion::Status; use crate::cmd::{ArgMatchResult, CommandInvocation}; @@ -28,6 +29,9 @@ subcommand_invocation! { t.set_format(table::format()); t.set_titles(row![b->"id", b->"act", b->"description"]); for (uuid, task) in replica.all_tasks().unwrap() { + if task.get_status() != Status::Pending { + continue; + } let mut id = uuid.to_string(); if let Some(i) = replica.get_working_set_index(&uuid)? { id = i.to_string(); diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index d58237816..c7b992b5c 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -7,6 +7,7 @@ mod shared; mod add; mod debug; +mod done; mod gc; mod info; mod list; @@ -21,6 +22,7 @@ pub(crate) fn subcommands() -> Vec> { vec![ add::cmd(), debug::cmd(), + done::cmd(), gc::cmd(), info::cmd(), list::cmd(), From af7363f3aa28b50ded9099b65ea14c3501419143 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 13:45:58 -0500 Subject: [PATCH 115/548] add delete subcommand --- cli/src/cmd/delete.rs | 52 +++++++++++++++++++++++++++++++++++++++++++ cli/src/cmd/mod.rs | 2 ++ 2 files changed, 54 insertions(+) create mode 100644 cli/src/cmd/delete.rs diff --git a/cli/src/cmd/delete.rs b/cli/src/cmd/delete.rs new file mode 100644 index 000000000..1d36833ec --- /dev/null +++ b/cli/src/cmd/delete.rs @@ -0,0 +1,52 @@ +use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; +use taskchampion::Status; + +use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + task: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("delete") + .about("mark the given task as deleted") + .arg(shared::task_arg())) + } + + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("delete", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { + task: matches.value_of("task").unwrap().into(), + })), + _ => ArgMatchResult::None, + } + } +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica()?; + let task = shared::get_task(&mut replica, &self.task)?; + let mut task = task.into_mut(&mut replica); + task.stop()?; + task.set_status(Status::Deleted)?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!(vec!["task", "delete", "1"], |inv: &Invocation| { + assert_eq!(inv.task, "1".to_string()); + }); + } +} + diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index c7b992b5c..17275c52e 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -7,6 +7,7 @@ mod shared; mod add; mod debug; +mod delete; mod done; mod gc; mod info; @@ -22,6 +23,7 @@ pub(crate) fn subcommands() -> Vec> { vec![ add::cmd(), debug::cmd(), + delete::cmd(), done::cmd(), gc::cmd(), info::cmd(), From 1b7dbd07150d4ef071b3d8abda355f8cca772112 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 13:46:52 -0500 Subject: [PATCH 116/548] remove delete_task from the Replica API so it's not misused --- cli/src/cmd/delete.rs | 1 - taskchampion/src/replica.rs | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cli/src/cmd/delete.rs b/cli/src/cmd/delete.rs index 1d36833ec..67acf4693 100644 --- a/cli/src/cmd/delete.rs +++ b/cli/src/cmd/delete.rs @@ -49,4 +49,3 @@ mod test { }); } } - diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 525be9888..6e4550ebd 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -135,8 +135,10 @@ impl Replica { } /// Delete a task. The task must exist. Note that this is different from setting status to - /// Deleted; this is the final purge of the task. - pub fn delete_task(&mut self, uuid: &Uuid) -> Fallible<()> { + /// Deleted; this is the final purge of the task. This is not a public method as deletion + /// should only occur through expiration. + #[allow(dead_code)] + fn delete_task(&mut self, uuid: &Uuid) -> Fallible<()> { // check that it already exists; this is a convenience check, as the task may already exist // when this Create operation is finally sync'd with operations from other replicas if self.taskdb.get_task(&uuid)?.is_none() { From 786d5b6a17325ee88c7b2504a8fd338b9614f9b1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 17:40:07 -0500 Subject: [PATCH 117/548] Implement actual on-disk storage for sync-server. --- Cargo.lock | 4 + docs/src/usage.md | 4 +- sync-server/Cargo.toml | 4 + sync-server/src/main.rs | 42 +++++- sync-server/src/storage/kv.rs | 244 +++++++++++++++++++++++++++++++++ sync-server/src/storage/mod.rs | 10 +- 6 files changed, 299 insertions(+), 9 deletions(-) create mode 100644 sync-server/src/storage/kv.rs diff --git a/Cargo.lock b/Cargo.lock index 6b649fe16..5d4662bb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2217,8 +2217,12 @@ version = "0.1.0" dependencies = [ "actix-rt", "actix-web", + "clap", "failure", "futures", + "kv", + "serde", + "tempdir", "uuid", ] diff --git a/docs/src/usage.md b/docs/src/usage.md index 37123bfd2..f60e07e07 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -30,5 +30,5 @@ The following configuration parameters are available: ## `taskchampion-sync-server` Run `taskchampion-sync-server` to start the sync server. -It serves on port 8080 on all interfaces, using an in-memory database (meaning that all data is lost when the process exits). -Requests for previously-unknown clients automatically create the client. +Use `--port` to specify the port it should listen on, and `--data-dir` to specify the directory which it should store its data. +It only serves HTTP; the expectation is that a frontend proxy will be used for HTTPS support. diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index a72fd691c..7047a3028 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -11,6 +11,10 @@ uuid = { version = "^0.8.1", features = ["serde", "v4"] } actix-web = "^3.3.0" failure = "^0.1.8" futures = "^0.3.8" +serde = "^1.0.104" +kv = {version = "^0.10.0", features = ["msgpack-value"]} +clap = "^2.33.0" [dev-dependencies] actix-rt = "^1.1.1" +tempdir = "^0.3.7" diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 7b91c5e3c..17821e36e 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,6 +1,8 @@ -use crate::storage::{InMemoryStorage, Storage}; +use crate::storage::{KVStorage, Storage}; use actix_web::{get, web, App, HttpServer, Responder, Scope}; use api::{api_scope, ServerState}; +use clap::Arg; +use failure::Fallible; mod api; mod server; @@ -24,14 +26,44 @@ pub(crate) fn app_scope(server_state: ServerState) -> Scope { } #[actix_web::main] -async fn main() -> std::io::Result<()> { - let server_box: Box = Box::new(InMemoryStorage::new()); +async fn main() -> Fallible<()> { + let matches = clap::App::new("taskchampion-sync-server") + .version("0.1.0") + .about("Server for TaskChampion") + .arg( + Arg::with_name("port") + .short("p") + .long("port") + .value_name("PORT") + .help("Port on which to serve") + .default_value("8080") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("data-dir") + .short("d") + .long("data-dir") + .value_name("DIR") + .help("Directory in which to store data") + .default_value("/var/lib/taskchampion-sync-server") + .takes_value(true) + .required(true), + ) + .get_matches(); + + let data_dir = matches.value_of("data-dir").unwrap(); + let port = matches.value_of("port").unwrap(); + + let server_box: Box = Box::new(KVStorage::new(data_dir)?); let server_state = ServerState::new(server_box); + println!("Serving on port {}", port); HttpServer::new(move || App::new().service(app_scope(server_state.clone()))) - .bind("127.0.0.1:8080")? + .bind(format!("0.0.0.0:{}", port))? .run() - .await + .await?; + Ok(()) } #[cfg(test)] diff --git a/sync-server/src/storage/kv.rs b/sync-server/src/storage/kv.rs new file mode 100644 index 000000000..c704955c4 --- /dev/null +++ b/sync-server/src/storage/kv.rs @@ -0,0 +1,244 @@ +#![allow(dead_code, unused_variables)] // temporary +use super::{Client, Storage, StorageTxn, Uuid, Version}; +use failure::Fallible; +use kv::msgpack::Msgpack; +use kv::{Bucket, Config, Error, Serde, Store, ValueBuf}; +use std::path::Path; + +/// Key for versions: concatenation of client_id and parent_version_id +type VersionKey = [u8; 32]; + +fn version_key(client_id: Uuid, parent_version_id: Uuid) -> VersionKey { + let mut key = [0u8; 32]; + key[..16].clone_from_slice(client_id.as_bytes()); + key[16..].clone_from_slice(parent_version_id.as_bytes()); + key +} + +/// Key for clients: just the client_id +type ClientKey = [u8; 16]; + +fn client_key(client_id: Uuid) -> ClientKey { + *client_id.as_bytes() +} + +/// KVStorage is an on-disk storage backend which uses LMDB via the `kv` crate. +pub(crate) struct KVStorage<'t> { + store: Store, + clients_bucket: Bucket<'t, ClientKey, ValueBuf>>, + versions_bucket: Bucket<'t, VersionKey, ValueBuf>>, +} + +impl<'t> KVStorage<'t> { + pub fn new>(directory: P) -> Fallible> { + let mut config = Config::default(directory); + config.bucket("clients", None); + config.bucket("versions", None); + + let store = Store::new(config)?; + + let clients_bucket = + store.bucket::>>(Some("clients"))?; + let versions_bucket = + store.bucket::>>(Some("versions"))?; + + Ok(KVStorage { + store, + clients_bucket, + versions_bucket, + }) + } +} + +impl<'t> Storage for KVStorage<'t> { + fn txn<'a>(&'a self) -> Fallible> { + Ok(Box::new(Txn { + storage: self, + txn: Some(self.store.write_txn()?), + })) + } +} + +struct Txn<'t> { + storage: &'t KVStorage<'t>, + txn: Option>, +} + +impl<'t> Txn<'t> { + // get the underlying kv Txn + fn kvtxn(&mut self) -> &mut kv::Txn<'t> { + if let Some(ref mut txn) = self.txn { + txn + } else { + panic!("cannot use transaction after commit"); + } + } + + fn clients_bucket(&self) -> &'t Bucket<'t, ClientKey, ValueBuf>> { + &self.storage.clients_bucket + } + fn versions_bucket(&self) -> &'t Bucket<'t, VersionKey, ValueBuf>> { + &self.storage.versions_bucket + } +} + +impl<'t> StorageTxn for Txn<'t> { + fn get_client(&mut self, client_id: Uuid) -> Fallible> { + let key = client_key(client_id); + let bucket = self.clients_bucket(); + let kvtxn = self.kvtxn(); + + let client = match kvtxn.get(&bucket, key) { + Ok(buf) => buf, + Err(Error::NotFound) => return Ok(None), + Err(e) => return Err(e.into()), + } + .inner()? + .to_serde(); + Ok(Some(client)) + } + + fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()> { + let key = client_key(client_id); + let bucket = self.clients_bucket(); + let kvtxn = self.kvtxn(); + let client = Client { latest_version_id }; + kvtxn.set(&bucket, key, Msgpack::to_value_buf(client)?)?; + Ok(()) + } + + fn set_client_latest_version_id( + &mut self, + client_id: Uuid, + latest_version_id: Uuid, + ) -> Fallible<()> { + // implementation is the same as new_client.. + self.new_client(client_id, latest_version_id) + } + + fn get_version_by_parent( + &mut self, + client_id: Uuid, + parent_version_id: Uuid, + ) -> Fallible> { + let key = version_key(client_id, parent_version_id); + let bucket = self.versions_bucket(); + let kvtxn = self.kvtxn(); + let version = match kvtxn.get(&bucket, key) { + Ok(buf) => buf, + Err(Error::NotFound) => return Ok(None), + Err(e) => return Err(e.into()), + } + .inner()? + .to_serde(); + Ok(Some(version)) + } + + fn add_version( + &mut self, + client_id: Uuid, + version_id: Uuid, + parent_version_id: Uuid, + history_segment: Vec, + ) -> Fallible<()> { + let key = version_key(client_id, parent_version_id); + let bucket = self.versions_bucket(); + let kvtxn = self.kvtxn(); + let version = Version { + version_id, + parent_version_id, + history_segment, + }; + kvtxn.set(&bucket, key, Msgpack::to_value_buf(version)?)?; + Ok(()) + } + + fn commit(&mut self) -> Fallible<()> { + if let Some(kvtxn) = self.txn.take() { + kvtxn.commit()?; + } else { + panic!("transaction already committed"); + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use failure::Fallible; + use tempdir::TempDir; + + #[test] + fn test_get_client_empty() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let storage = KVStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + let maybe_client = txn.get_client(Uuid::new_v4())?; + assert!(maybe_client.is_none()); + Ok(()) + } + + #[test] + fn test_client_storage() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let storage = KVStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + + let client_id = Uuid::new_v4(); + let latest_version_id = Uuid::new_v4(); + txn.new_client(client_id, latest_version_id)?; + + let client = txn.get_client(client_id)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + + let latest_version_id = Uuid::new_v4(); + txn.set_client_latest_version_id(client_id, latest_version_id)?; + + let client = txn.get_client(client_id)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + + Ok(()) + } + + #[test] + fn test_gvbp_empty() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let storage = KVStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + let maybe_version = txn.get_version_by_parent(Uuid::new_v4(), Uuid::new_v4())?; + assert!(maybe_version.is_none()); + Ok(()) + } + + #[test] + fn test_add_version_and_gvbp() -> Fallible<()> { + let tmp_dir = TempDir::new("test")?; + let storage = KVStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abc".to_vec(); + txn.add_version( + client_id, + version_id, + parent_version_id, + history_segment.clone(), + )?; + let version = txn + .get_version_by_parent(client_id, parent_version_id)? + .unwrap(); + + assert_eq!( + version, + Version { + version_id, + parent_version_id, + history_segment, + } + ); + Ok(()) + } +} diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 1462a3769..2a95917b9 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -1,15 +1,21 @@ use failure::Fallible; +use serde::{Deserialize, Serialize}; use uuid::Uuid; +#[cfg(test)] mod inmemory; +#[cfg(test)] pub(crate) use inmemory::InMemoryStorage; -#[derive(Clone, PartialEq, Debug)] +mod kv; +pub(crate) use self::kv::KVStorage; + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub(crate) struct Client { pub(crate) latest_version_id: Uuid, } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub(crate) struct Version { pub(crate) version_id: Uuid, pub(crate) parent_version_id: Uuid, From 0a1ee470f785663847743517bed4d74950f5b00e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 18:18:28 -0500 Subject: [PATCH 118/548] use log and env_logger, and add some logging around sync --- Cargo.lock | 42 +++++++++++++++++++++++++++++++++++++ cli/Cargo.toml | 2 ++ cli/src/bin/task.rs | 1 + cli/src/cmd/shared.rs | 11 ++++++---- cli/src/settings.rs | 2 ++ docs/src/usage.md | 8 +++++++ sync-server/Cargo.toml | 2 ++ sync-server/src/main.rs | 3 ++- sync-server/src/server.rs | 11 ++++++++++ taskchampion/Cargo.toml | 1 + taskchampion/src/replica.rs | 6 +++++- taskchampion/src/task.rs | 4 ++++ taskchampion/src/taskdb.rs | 30 +++++++++++++++++++------- 13 files changed, 109 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d4662bb0..ed6533c58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -787,6 +787,19 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "error-chain" version = "0.12.4" @@ -1080,6 +1093,12 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +[[package]] +name = "humantime" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" + [[package]] name = "idna" version = "0.2.0" @@ -2189,6 +2208,7 @@ dependencies = [ "failure", "kv", "lmdb-rkv", + "log", "proptest", "serde", "serde_json", @@ -2205,7 +2225,9 @@ dependencies = [ "clap", "config", "dirs 3.0.1", + "env_logger", "failure", + "log", "predicates", "prettytable-rs", "taskchampion", @@ -2218,9 +2240,11 @@ dependencies = [ "actix-rt", "actix-web", "clap", + "env_logger", "failure", "futures", "kv", + "log", "serde", "tempdir", "uuid", @@ -2261,6 +2285,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -2731,6 +2764,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 36e313f8e..61c79b052 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,6 +11,8 @@ failure = "^0.1.8" prettytable-rs = "^0.8.0" config = { version="^0.10.1", default-features=false, features=["yaml"] } dirs = "^3.0.1" +log = "^0.4.11" +env_logger = "^0.8.2" [dev-dependencies] assert_cmd = "^1.0.1" diff --git a/cli/src/bin/task.rs b/cli/src/bin/task.rs index e90ead9d2..ab21c54bc 100644 --- a/cli/src/bin/task.rs +++ b/cli/src/bin/task.rs @@ -17,6 +17,7 @@ fn bail(err: E, output: Output, code: i32) -> ! { } fn main() { + env_logger::init(); let command = match parse_command_line(std::env::args_os()) { Ok(command) => command, Err(err) => { diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index 9744ea6f3..006b08ac5 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -66,9 +66,9 @@ impl CommandInvocation { pub(super) fn get_replica(&self) -> Fallible { let settings = self.get_settings()?; - let replica_config = ReplicaConfig { - taskdb_dir: settings.get_str("data_dir")?.into(), - }; + let taskdb_dir = settings.get_str("data_dir")?.into(); + log::debug!("Replica data_dir: {:?}", taskdb_dir); + let replica_config = ReplicaConfig { taskdb_dir }; Ok(Replica::from_config(replica_config)?) } @@ -76,8 +76,11 @@ impl CommandInvocation { let settings = self.get_settings()?; let client_id = settings.get_str("server_client_id")?; let client_id = Uuid::parse_str(&client_id)?; + let origin = settings.get_str("server_origin")?; + log::debug!("Using sync-server with origin {}", origin); + log::debug!("Sync client ID: {}", client_id); Ok(server::from_config(ServerConfig::Remote { - origin: settings.get_str("server_origin")?, + origin, client_id, })?) } diff --git a/cli/src/settings.rs b/cli/src/settings.rs index f8a92582e..02e554c76 100644 --- a/cli/src/settings.rs +++ b/cli/src/settings.rs @@ -18,12 +18,14 @@ pub(crate) fn read_settings() -> Fallible { // load either from the path in TASKCHAMPION_CONFIG, or from CONFIG_DIR/taskchampion if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") { + log::debug!("Loading configuration from {:?}", config_file); let config_file: PathBuf = config_file.into(); let config_file: File = config_file.into(); settings.merge(config_file.required(true))?; env::remove_var("TASKCHAMPION_CONFIG"); } else if let Some(mut dir) = dirs::config_dir() { dir.push("taskchampion"); + log::debug!("Loading configuration from {:?} (optional)", dir); let config_file: File = dir.into(); settings.merge(config_file.required(false))?; } diff --git a/docs/src/usage.md b/docs/src/usage.md index f60e07e07..84ee5626c 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -32,3 +32,11 @@ The following configuration parameters are available: Run `taskchampion-sync-server` to start the sync server. Use `--port` to specify the port it should listen on, and `--data-dir` to specify the directory which it should store its data. It only serves HTTP; the expectation is that a frontend proxy will be used for HTTPS support. + +## Debugging + +Both `task` and `taskchampio-sync-server` use [env-logger](https://docs.rs/env_logger) and can be configured to log at various levels with the `RUST_LOG` environment variable. +For example: +```shell +$ RUST_LOG=taskchampion=trace task add foo +``` diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 7047a3028..23f8df85e 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -14,6 +14,8 @@ futures = "^0.3.8" serde = "^1.0.104" kv = {version = "^0.10.0", features = ["msgpack-value"]} clap = "^2.33.0" +log = "^0.4.11" +env_logger = "^0.8.2" [dev-dependencies] actix-rt = "^1.1.1" diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 17821e36e..68260f1c7 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -27,6 +27,7 @@ pub(crate) fn app_scope(server_state: ServerState) -> Scope { #[actix_web::main] async fn main() -> Fallible<()> { + env_logger::init(); let matches = clap::App::new("taskchampion-sync-server") .version("0.1.0") .about("Server for TaskChampion") @@ -58,7 +59,7 @@ async fn main() -> Fallible<()> { let server_box: Box = Box::new(KVStorage::new(data_dir)?); let server_state = ServerState::new(server_box); - println!("Serving on port {}", port); + log::warn!("Serving on port {}", port); HttpServer::new(move || App::new().service(app_scope(server_state.clone()))) .bind(format!("0.0.0.0:{}", port))? .run() diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index da8960c0f..a518189ce 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -49,8 +49,15 @@ pub(crate) fn add_version<'a>( parent_version_id: VersionId, history_segment: HistorySegment, ) -> Fallible { + log::debug!( + "add_version(client_id: {}, parent_version_id: {})", + client_id, + parent_version_id, + ); + // check if this version is acceptable, under the protection of the transaction if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { + log::debug!("add_version request rejected: mismatched latest_version_id"); return Ok(AddVersionResult::ExpectedParentVersion( client.latest_version_id, )); @@ -58,6 +65,10 @@ pub(crate) fn add_version<'a>( // invent a version ID let version_id = Uuid::new_v4(); + log::debug!( + "add_version request accepted: new version_id: {}", + version_id + ); // update the DB txn.add_version(client_id, version_id, parent_version_id, history_segment)?; diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 5dcbb497b..ed9382dbe 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -13,6 +13,7 @@ failure = {version = "^0.1.5", features = ["derive"] } kv = {version = "^0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "^0.12.3"} ureq = "^1.5.2" +log = "^0.4.11" [dev-dependencies] proptest = "^0.9.4" diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 6e4550ebd..34e3b6f31 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -6,6 +6,7 @@ use crate::taskdb::TaskDB; use crate::taskstorage::{KVStorage, Operation, TaskMap, TaskStorage}; use chrono::Utc; use failure::Fallible; +use log::trace; use std::collections::HashMap; use uuid::Uuid; @@ -128,6 +129,7 @@ impl Replica { pub fn new_task(&mut self, status: Status, description: String) -> Fallible { let uuid = Uuid::new_v4(); self.taskdb.apply(Operation::Create { uuid })?; + trace!("task {} created", uuid); let mut task = Task::new(uuid, TaskMap::new()).into_mut(self); task.set_description(description)?; task.set_status(status)?; @@ -144,7 +146,9 @@ impl Replica { if self.taskdb.get_task(&uuid)?.is_none() { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } - self.taskdb.apply(Operation::Delete { uuid: *uuid }) + self.taskdb.apply(Operation::Delete { uuid: *uuid })?; + trace!("task {} deleted", uuid); + Ok(()) } /// Synchronize this replica against the given server. diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 20ac753d5..49ba279db 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -2,6 +2,7 @@ use crate::replica::Replica; use crate::taskstorage::TaskMap; use chrono::prelude::*; use failure::Fallible; +use log::trace; use uuid::Uuid; pub type Timestamp = DateTime; @@ -230,6 +231,7 @@ impl<'r> TaskMut<'r> { let now = format!("{}", Utc::now().timestamp()); self.replica .update_task(self.task.uuid, "modified", Some(now.clone()))?; + trace!("task {}: set property modified={:?}", self.task.uuid, now); self.task.taskmap.insert(String::from("modified"), now); self.updated_modified = true; } @@ -242,8 +244,10 @@ impl<'r> TaskMut<'r> { .update_task(self.task.uuid, property, value.as_ref())?; if let Some(v) = value { + trace!("task {}: set property {}={:?}", self.task.uuid, property, v); self.task.taskmap.insert(property.to_string(), v); } else { + trace!("task {}: remove property {}", self.task.uuid, property); self.task.taskmap.remove(property); } Ok(()) diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 4cdb927ca..0dd759814 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -2,6 +2,7 @@ use crate::errors::Error; use crate::server::{AddVersionResult, GetVersionResult, Server}; use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; use failure::{format_err, Fallible}; +use log::{info, trace, warn}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::str; @@ -172,10 +173,12 @@ impl TaskDB { // version twice, then we have diverged. let mut requested_parent_version_id = None; loop { + trace!("beginning sync outer loop"); let mut base_version_id = txn.base_version()?; // first pull changes and "rebase" on top of them loop { + trace!("beginning sync inner loop"); if let GetVersionResult::Version { version_id, history_segment, @@ -184,14 +187,14 @@ impl TaskDB { { let version_str = str::from_utf8(&history_segment).unwrap(); let version: Version = serde_json::from_str(version_str).unwrap(); - println!("applying version {:?} from server", version_id); // apply this verison and update base_version in storage + info!("applying version {:?} from server", version_id); TaskDB::apply_version(txn.as_mut(), version)?; txn.set_base_version(version_id)?; base_version_id = version_id; } else { - println!("no child versions of {:?}", base_version_id); + info!("no child versions of {:?}", base_version_id); // at the moment, no more child versions, so we can try adding our own break; } @@ -199,24 +202,26 @@ impl TaskDB { let operations: Vec = txn.operations()?.to_vec(); if operations.is_empty() { - println!("no changes to push to server"); + info!("no changes to push to server"); // nothing to sync back to the server.. break; } + trace!("sending {} operations to the server", operations.len()); + // now make a version of our local changes and push those let new_version = Version { operations }; let history_segment = serde_json::to_string(&new_version).unwrap().into(); - println!("sending new version to server"); + info!("sending new version to server"); match server.add_version(base_version_id, history_segment)? { AddVersionResult::Ok(new_version_id) => { - println!("version {:?} received by server", new_version_id); + info!("version {:?} received by server", new_version_id); txn.set_base_version(new_version_id)?; txn.set_operations(vec![])?; break; } AddVersionResult::ExpectedParentVersion(parent_version_id) => { - println!( + info!( "new version rejected; must be based on {:?}", parent_version_id ); @@ -264,22 +269,31 @@ impl TaskDB { // it. If it happens for server op, then we must copy the remaining local ops. let mut local_operations: Vec = txn.operations()?; for server_op in version.operations.drain(..) { + trace!( + "rebasing local operations onto server operation {:?}", + server_op + ); let mut new_local_ops = Vec::with_capacity(local_operations.len()); let mut svr_op = Some(server_op); for local_op in local_operations.drain(..) { if let Some(o) = svr_op { - let (new_server_op, new_local_op) = Operation::transform(o, local_op); + let (new_server_op, new_local_op) = Operation::transform(o, local_op.clone()); + trace!("local operation {:?} -> {:?}", local_op, new_local_op); svr_op = new_server_op; if let Some(o) = new_local_op { new_local_ops.push(o); } } else { + trace!( + "local operation {:?} unchanged (server operation consumed)", + local_op + ); new_local_ops.push(local_op); } } if let Some(o) = svr_op { if let Err(e) = TaskDB::apply_op(txn, &o) { - println!("Invalid operation when syncing: {} (ignored)", e); + warn!("Invalid operation when syncing: {} (ignored)", e); } } local_operations = new_local_ops; From 08faac957a2148ce360e40a8071f1e2c19296b8a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 20:14:06 -0500 Subject: [PATCH 119/548] Add a getting-started section --- docs/src/SUMMARY.md | 23 ++++++++-------- docs/src/internals.md | 5 ++++ docs/src/welcome.md | 61 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 docs/src/internals.md create mode 100644 docs/src/welcome.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index b37148b77..40871f638 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,13 +1,14 @@ # Summary -- [Installation](./installation.md) -- [Usage](./usage.md) ---- -- [Data Model](./data-model.md) - - [Replica Storage](./storage.md) - - [Task Database](./taskdb.md) - - [Tasks](./tasks.md) -- [Synchronization and the Sync Server](./sync.md) - - [Synchronization Model](./sync-model.md) - - [Server-Replica Protocol](./sync-protocol.md) - - [Planned Functionality](./plans.md) +- [Welcome to TaskChampion](./welcome.md) + - [Installation](./installation.md) + - [Usage](./usage.md) +- [Internal Details](./internals.md) + - [Data Model](./data-model.md) + - [Replica Storage](./storage.md) + - [Task Database](./taskdb.md) + - [Tasks](./tasks.md) + - [Synchronization and the Sync Server](./sync.md) + - [Synchronization Model](./sync-model.md) + - [Server-Replica Protocol](./sync-protocol.md) + - [Planned Functionality](./plans.md) diff --git a/docs/src/internals.md b/docs/src/internals.md new file mode 100644 index 000000000..7cc36ac29 --- /dev/null +++ b/docs/src/internals.md @@ -0,0 +1,5 @@ +# Internal Details + +The following sections get into the details of how TaskChampion works. +None of this information is necessary to use TaskChampion, but might be helpful in understanding its behavior. +Developers of TaskChampion and of tools that integrate with TaskChampion should be familiar with this information. diff --git a/docs/src/welcome.md b/docs/src/welcome.md new file mode 100644 index 000000000..948ceac93 --- /dev/null +++ b/docs/src/welcome.md @@ -0,0 +1,61 @@ +# TaskChampion + +TaskChampion is a person task-tracking tool. +It works from the command line, with simple commands like `task add "fix the kitchen sink"`. +It can synchronize tasks on multiple devices, and does so in an "offline" mode so you can update your tasks even when you can't reach the server. +If you've heard of [TaskWarrior](https://taskwarrior.org/), this tool is very similar, but actively maintained and with a more reliable synchronization system. + +## Getting Started + +> NOTE: TaskChampion is still in development and not yet feature-complete. +> This section is limited to completed functionality. + +Once you've [installed TaskChampion](./installation.md), your interface will be via the `task` command. +Start by adding a task: + +```shell +$ task add "learn how to use taskchampion" +added task ba57deaf-f97b-4e9c-b9ab-04bc1ecb22b8 +``` + +You can see all of your pending tasks with `task pending`, or just `task` for short: + +```shell +$ task + id act description + 1 learn how to use taskchampion +``` + +Tell TaskChampion you're working on the task, using the shorthand id: + +```shell +$ task start 1 +``` + +and when you're done with the task, mark it as complete: + +```shell +$ task done 1 +``` + +## Synchronizing + +Even if you don't have a server, it's a good idea to sync your task database periodically. +This acts as a backup and also enables some internal house-cleaning. + +```shell +$ task sync +``` + +Typically sync is run from a crontab, on whatever schedule fits your needs. + +To synchronize multiple replicas of your tasks, you will need a sync server and a client ID on that server. +Configure these in `~/.config/taskchampion.yml`, for example: + +```yaml +server_client_id: "f8d4d09d-f6c7-4dd2-ab50-634ed20a3ff2" +server_origin: "https://taskchampion.example.com" +``` + +The next run of `task sync` will upload your task history to that server. +Configuring another device identically and running `task sync` will download that task history, and continue to stay in sync with subsequent runs of the command. From 8f361c28b70c03b81e7698c4cf3a6c1d83a13459 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 20:34:43 -0500 Subject: [PATCH 120/548] remove unnecessary warning suppression --- RELEASING.md | 3 +++ sync-server/src/storage/kv.rs | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 RELEASING.md diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 000000000..c44ac719c --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,3 @@ +# Release process + +1. diff --git a/sync-server/src/storage/kv.rs b/sync-server/src/storage/kv.rs index c704955c4..d258da8f3 100644 --- a/sync-server/src/storage/kv.rs +++ b/sync-server/src/storage/kv.rs @@ -1,4 +1,3 @@ -#![allow(dead_code, unused_variables)] // temporary use super::{Client, Storage, StorageTxn, Uuid, Version}; use failure::Fallible; use kv::msgpack::Msgpack; From dd01c985eca54934a65decf9d6c47a7e1cd70191 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 20:34:59 -0500 Subject: [PATCH 121/548] fix typo --- docs/src/welcome.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/welcome.md b/docs/src/welcome.md index 948ceac93..c0e45c16a 100644 --- a/docs/src/welcome.md +++ b/docs/src/welcome.md @@ -1,6 +1,6 @@ # TaskChampion -TaskChampion is a person task-tracking tool. +TaskChampion is a personal task-tracking tool. It works from the command line, with simple commands like `task add "fix the kitchen sink"`. It can synchronize tasks on multiple devices, and does so in an "offline" mode so you can update your tasks even when you can't reach the server. If you've heard of [TaskWarrior](https://taskwarrior.org/), this tool is very similar, but actively maintained and with a more reliable synchronization system. From 81cee8d3750841c7a137f696ab56b8d392f8ff4c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 20:23:52 -0500 Subject: [PATCH 122/548] use CARGO_PKG_VERSION for version --- cli/src/lib.rs | 2 +- sync-server/src/main.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 4474af8f3..6c63d1674 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -18,7 +18,7 @@ where let subcommands = cmd::subcommands(); let mut app = App::new("TaskChampion") - .version("0.1") + .version(env!("CARGO_PKG_VERSION")) .about("Personal task-tracking") .setting(AppSettings::ColoredHelp); diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 68260f1c7..6c5fde701 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -12,8 +12,7 @@ mod storage; #[get("/")] async fn index() -> impl Responder { - // TODO: add version here - "TaskChampion sync server" + format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION")) } /// Return a scope defining the URL rules for this server, with access to @@ -29,7 +28,7 @@ pub(crate) fn app_scope(server_state: ServerState) -> Scope { async fn main() -> Fallible<()> { env_logger::init(); let matches = clap::App::new("taskchampion-sync-server") - .version("0.1.0") + .version(env!("CARGO_PKG_VERSION")) .about("Server for TaskChampion") .arg( Arg::with_name("port") From 95ada3d2bbcc6767c48e6295e2ffc94b59c74ef8 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 20:31:28 -0500 Subject: [PATCH 123/548] Add metadata for the taskchampion crate --- taskchampion/Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index ed9382dbe..249b10546 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -2,6 +2,12 @@ name = "taskchampion" version = "0.1.0" authors = ["Dustin J. Mitchell "] +description = "Personal task-tracking" +homepage = "https://djmitche.github.io/taskchampion/" +documentation = "https://docs.rs/crate/taskchampion" +repository = "https://github.com/djmitche/taskchampion" +readme = "../README.md" +license = "MIT" edition = "2018" [dependencies] From 0e2e60372d0aab7cd296a3defa1c1df42461016a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 20:42:10 -0500 Subject: [PATCH 124/548] release process --- RELEASING.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index c44ac719c..dde7e13ef 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,3 +1,13 @@ # Release process -1. +1. Run `cargo test` +1. Run `cargo clean && cargo clippy` +1. Run `mdbook test docs` +1. Update `version` in `*/Cargo.toml`. All versions should match. Commit the change with comment `vX.Y.Z`. +1. Run `cargo build --release` +1. Run `git tag vX.Y.Z` +1. Run `git push --tags upstream` +1. Run `( cd docs; ./build.sh )` +1. Run `cargo publish -p taskchampion` +1. Navigate to the tag in the GitHub releases UI and create a release with general comments about the changes in the release +1. Upload `./target/release/task` and `./target/release/task-sync-server` to the release From 451690afb7ce8ca4ec1945d928dc8f7772d3b676 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 20:59:09 -0500 Subject: [PATCH 125/548] v0.2.0 --- cli/Cargo.toml | 2 +- sync-server/Cargo.toml | 2 +- taskchampion/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 61c79b052..d1f5f99c4 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskchampion-cli" -version = "0.1.0" +version = "0.2.0" authors = ["Dustin J. Mitchell "] edition = "2018" diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 23f8df85e..bebab6a02 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskchampion-sync-server" -version = "0.1.0" +version = "0.2.0" authors = ["Dustin J. Mitchell "] edition = "2018" diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 249b10546..fe0899d00 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskchampion" -version = "0.1.0" +version = "0.2.0" authors = ["Dustin J. Mitchell "] description = "Personal task-tracking" homepage = "https://djmitche.github.io/taskchampion/" From e6abfc5f567ee002c60a30147b2abf8aab74f473 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 20:59:52 -0500 Subject: [PATCH 126/548] more RELEASING stuff --- RELEASING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASING.md b/RELEASING.md index dde7e13ef..a4cd8e04f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,11 +1,13 @@ # Release process +1. Run `git pull upstream main` 1. Run `cargo test` 1. Run `cargo clean && cargo clippy` 1. Run `mdbook test docs` 1. Update `version` in `*/Cargo.toml`. All versions should match. Commit the change with comment `vX.Y.Z`. 1. Run `cargo build --release` 1. Run `git tag vX.Y.Z` +1. Run `git push upstream` 1. Run `git push --tags upstream` 1. Run `( cd docs; ./build.sh )` 1. Run `cargo publish -p taskchampion` From a1747546d6042b5c79eddaaddfa4b8e543850794 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 21:09:22 -0500 Subject: [PATCH 127/548] Update RELEASING.md --- RELEASING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index a4cd8e04f..97286ab89 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -4,8 +4,9 @@ 1. Run `cargo test` 1. Run `cargo clean && cargo clippy` 1. Run `mdbook test docs` -1. Update `version` in `*/Cargo.toml`. All versions should match. Commit the change with comment `vX.Y.Z`. +1. Update `version` in `*/Cargo.toml`. All versions should match. 1. Run `cargo build --release` +1. Commit the changes (Cargo.lock will change too) with comment `vX.Y.Z`. 1. Run `git tag vX.Y.Z` 1. Run `git push upstream` 1. Run `git push --tags upstream` From 8601c0cb671f6166e2a744fb72f6d514b2ca05f5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 21:09:28 -0500 Subject: [PATCH 128/548] update Cargo.lock --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed6533c58..9b4189824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2202,7 +2202,7 @@ dependencies = [ [[package]] name = "taskchampion" -version = "0.1.0" +version = "0.2.0" dependencies = [ "chrono", "failure", @@ -2219,7 +2219,7 @@ dependencies = [ [[package]] name = "taskchampion-cli" -version = "0.1.0" +version = "0.2.0" dependencies = [ "assert_cmd", "clap", @@ -2235,7 +2235,7 @@ dependencies = [ [[package]] name = "taskchampion-sync-server" -version = "0.1.0" +version = "0.2.0" dependencies = [ "actix-rt", "actix-web", From 0d5635f0923d00ebc2494f87db2f017ab72f3676 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 21:10:49 -0500 Subject: [PATCH 129/548] Update RELEASING.md --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 97286ab89..160f2c230 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -11,6 +11,6 @@ 1. Run `git push upstream` 1. Run `git push --tags upstream` 1. Run `( cd docs; ./build.sh )` -1. Run `cargo publish -p taskchampion` +1. Run `(cd taskchampion; cargo publish)` (note that the other crates do not get published) 1. Navigate to the tag in the GitHub releases UI and create a release with general comments about the changes in the release 1. Upload `./target/release/task` and `./target/release/task-sync-server` to the release From de03209285351011bf02193f758b37279a680f81 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 29 Nov 2020 21:26:39 -0500 Subject: [PATCH 130/548] bits of docs --- taskchampion/src/lib.rs | 2 +- taskchampion/src/replica.rs | 14 ++++++++++++++ taskchampion/src/server/mod.rs | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index aa2c8bf99..64c6cb238 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -18,7 +18,7 @@ The [`server`](crate::server) module defines the interface a server must meet. # See Also -See the [TaskChampion Book](https://github.com/djmitche/taskchampion/blob/main/docs/src/SUMMARY.md) +See the [TaskChampion Book](http://djmitche.github.com/taskchampion) for more information about the design and usage of the tool. */ diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 34e3b6f31..1de446e26 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -12,6 +12,18 @@ use uuid::Uuid; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. +/// +/// ## Tasks +/// +/// Tasks are uniquely identified by UUIDs. +/// Most task modifications are performed via the [`crate::Task`] and [`crate::TaskMut`] types. +/// +/// ## Working Set +/// +/// A replica maintains a "working set" of tasks that are of current concern to the user, +/// specifically pending tasks. These are indexed with small, easy-to-type integers. Newly +/// pending tasks are automatically added to the working set, and the working set is "renumbered" +/// during the garbage-collection process. pub struct Replica { taskdb: TaskDB, } @@ -23,6 +35,8 @@ impl Replica { } } + /// Construct a new replica from a configuration object. This is the common way + /// to create a new object. pub fn from_config(config: ReplicaConfig) -> Fallible { let storage = Box::new(KVStorage::new(config.taskdb_dir)?); Ok(Replica::new(storage)) diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 341428130..936e441d8 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -12,6 +12,7 @@ pub use local::LocalServer; pub use remote::RemoteServer; pub use types::*; +/// Create a new server based on the given configuration. pub fn from_config(config: ServerConfig) -> Fallible> { Ok(match config { ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), From ee341ac7d52f2f6cd13688eaad85e5dcca48cd6a Mon Sep 17 00:00:00 2001 From: Simon Fraser Date: Thu, 17 Dec 2020 12:59:50 +0000 Subject: [PATCH 131/548] subcommands: prepend and append to description This currently uses format!() which may not be the best option. Fixes #88 --- cli/src/cmd/append.rs | 64 ++++++++++++++++++++++++++++++++++++++++++ cli/src/cmd/mod.rs | 4 +++ cli/src/cmd/prepend.rs | 64 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 cli/src/cmd/append.rs create mode 100644 cli/src/cmd/prepend.rs diff --git a/cli/src/cmd/append.rs b/cli/src/cmd/append.rs new file mode 100644 index 000000000..e32470475 --- /dev/null +++ b/cli/src/cmd/append.rs @@ -0,0 +1,64 @@ +use crate::cmd::shared; +use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; + +use crate::cmd::{ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + task: String, + description: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("append").about("appends to a task description") + .arg(shared::task_arg()) + .arg( + Arg::with_name("description") + .help("extra task description") + .required(true), + ), + ) + } + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("append", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { + task: matches.value_of("task").unwrap().into(), + description: matches.value_of("description").unwrap().into(), + })), + _ => ArgMatchResult::None, + } + } + +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica()?; + let task = shared::get_task(&mut replica, &self.task)?; + + let mut task = task.into_mut(&mut replica); + + let new_description = format!("{} {}", task.get_description(), self.description.clone()); + task.set_description(new_description)?; + println!("appended to task {}", task.get_uuid()); + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!( + vec!["task", "append", "1", "foo bar"], + |inv: &Invocation| { + assert_eq!(inv.description, "foo bar".to_string()); + } + ); + } +} diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index 17275c52e..e4e3851ea 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -6,6 +6,7 @@ mod macros; mod shared; mod add; +mod append; mod debug; mod delete; mod done; @@ -14,6 +15,7 @@ mod info; mod list; mod modify; mod pending; +mod prepend; mod start; mod stop; mod sync; @@ -22,6 +24,7 @@ mod sync; pub(crate) fn subcommands() -> Vec> { vec![ add::cmd(), + append::cmd(), debug::cmd(), delete::cmd(), done::cmd(), @@ -30,6 +33,7 @@ pub(crate) fn subcommands() -> Vec> { list::cmd(), modify::cmd(), pending::cmd(), + prepend::cmd(), start::cmd(), stop::cmd(), sync::cmd(), diff --git a/cli/src/cmd/prepend.rs b/cli/src/cmd/prepend.rs new file mode 100644 index 000000000..f827ac33a --- /dev/null +++ b/cli/src/cmd/prepend.rs @@ -0,0 +1,64 @@ +use crate::cmd::shared; +use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; +use failure::Fallible; + +use crate::cmd::{ArgMatchResult, CommandInvocation}; + +#[derive(Debug)] +struct Invocation { + task: String, + description: String, +} + +define_subcommand! { + fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { + app.subcommand( + ClapSubCommand::with_name("prepend").about("add to the start of a task description") + .arg(shared::task_arg()) + .arg( + Arg::with_name("description") + .help("extra task description") + .required(true), + ), + ) + } + fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { + match matches.subcommand() { + ("prepend", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { + task: matches.value_of("task").unwrap().into(), + description: matches.value_of("description").unwrap().into(), + })), + _ => ArgMatchResult::None, + } + } + +} + +subcommand_invocation! { + fn run(&self, command: &CommandInvocation) -> Fallible<()> { + let mut replica = command.get_replica()?; + let task = shared::get_task(&mut replica, &self.task)?; + + let mut task = task.into_mut(&mut replica); + + let new_description = format!("{} {}", self.description.clone(), task.get_description()); + task.set_description(new_description)?; + println!("prepended to task {}", task.get_uuid()); + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_command() { + with_subcommand_invocation!( + vec!["task", "prepend", "1", "foo bar"], + |inv: &Invocation| { + assert_eq!(inv.description, "foo bar".to_string()); + } + ); + } +} From 2c579b9f0109df2a10e765db7fe70a542aa714de Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 3 Dec 2020 06:58:10 +0000 Subject: [PATCH 132/548] Switch to a command-line API closer to TaskWarrior * Use a parser (rather than clap) to process the command line * Outline some generic support for filtering, reporting, modifying, etc. * Break argument parsing strictly from invocation, to allow independent testing --- Cargo.lock | 53 ++- cli/Cargo.toml | 23 +- cli/src/argparse/args.rs | 232 ++++++++++++ cli/src/argparse/command.rs | 62 ++++ cli/src/argparse/filter.rs | 105 ++++++ cli/src/argparse/mod.rs | 27 ++ cli/src/argparse/modification.rs | 119 +++++++ cli/src/argparse/report.rs | 33 ++ cli/src/argparse/subcommand.rs | 526 ++++++++++++++++++++++++++++ cli/src/bin/task.rs | 44 +-- cli/src/cmd/add.rs | 59 ---- cli/src/cmd/append.rs | 64 ---- cli/src/cmd/debug.rs | 57 --- cli/src/cmd/delete.rs | 51 --- cli/src/cmd/done.rs | 51 --- cli/src/cmd/gc.rs | 37 -- cli/src/cmd/info.rs | 61 ---- cli/src/cmd/list.rs | 58 --- cli/src/cmd/macros.rs | 45 --- cli/src/cmd/mod.rs | 73 ---- cli/src/cmd/modify.rs | 63 ---- cli/src/cmd/pending.rs | 59 ---- cli/src/cmd/prepend.rs | 64 ---- cli/src/cmd/shared.rs | 87 ----- cli/src/cmd/start.rs | 48 --- cli/src/cmd/stop.rs | 48 --- cli/src/cmd/sync.rs | 39 --- cli/src/invocation/cmd/add.rs | 34 ++ cli/src/invocation/cmd/gc.rs | 21 ++ cli/src/invocation/cmd/help.rs | 28 ++ cli/src/invocation/cmd/info.rs | 49 +++ cli/src/invocation/cmd/list.rs | 45 +++ cli/src/invocation/cmd/mod.rs | 13 + cli/src/invocation/cmd/modify.rs | 49 +++ cli/src/invocation/cmd/sync.rs | 26 ++ cli/src/invocation/cmd/test.rs | 14 + cli/src/invocation/cmd/version.rs | 16 + cli/src/invocation/filter.rs | 38 ++ cli/src/invocation/mod.rs | 109 ++++++ cli/src/invocation/modify.rs | 33 ++ cli/src/lib.rs | 107 +++--- cli/src/macros.rs | 12 + cli/tests/cli.rs | 6 +- taskchampion/src/task.rs | 2 +- taskchampion/src/taskstorage/mod.rs | 2 - 45 files changed, 1720 insertions(+), 1072 deletions(-) create mode 100644 cli/src/argparse/args.rs create mode 100644 cli/src/argparse/command.rs create mode 100644 cli/src/argparse/filter.rs create mode 100644 cli/src/argparse/mod.rs create mode 100644 cli/src/argparse/modification.rs create mode 100644 cli/src/argparse/report.rs create mode 100644 cli/src/argparse/subcommand.rs delete mode 100644 cli/src/cmd/add.rs delete mode 100644 cli/src/cmd/append.rs delete mode 100644 cli/src/cmd/debug.rs delete mode 100644 cli/src/cmd/delete.rs delete mode 100644 cli/src/cmd/done.rs delete mode 100644 cli/src/cmd/gc.rs delete mode 100644 cli/src/cmd/info.rs delete mode 100644 cli/src/cmd/list.rs delete mode 100644 cli/src/cmd/macros.rs delete mode 100644 cli/src/cmd/mod.rs delete mode 100644 cli/src/cmd/modify.rs delete mode 100644 cli/src/cmd/pending.rs delete mode 100644 cli/src/cmd/prepend.rs delete mode 100644 cli/src/cmd/shared.rs delete mode 100644 cli/src/cmd/start.rs delete mode 100644 cli/src/cmd/stop.rs delete mode 100644 cli/src/cmd/sync.rs create mode 100644 cli/src/invocation/cmd/add.rs create mode 100644 cli/src/invocation/cmd/gc.rs create mode 100644 cli/src/invocation/cmd/help.rs create mode 100644 cli/src/invocation/cmd/info.rs create mode 100644 cli/src/invocation/cmd/list.rs create mode 100644 cli/src/invocation/cmd/mod.rs create mode 100644 cli/src/invocation/cmd/modify.rs create mode 100644 cli/src/invocation/cmd/sync.rs create mode 100644 cli/src/invocation/cmd/test.rs create mode 100644 cli/src/invocation/cmd/version.rs create mode 100644 cli/src/invocation/filter.rs create mode 100644 cli/src/invocation/mod.rs create mode 100644 cli/src/invocation/modify.rs create mode 100644 cli/src/macros.rs diff --git a/Cargo.lock b/Cargo.lock index 9b4189824..9f40e8688 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -430,6 +430,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -587,7 +599,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" dependencies = [ "lazy_static", - "nom", + "nom 5.1.2", "serde", "yaml-rust", ] @@ -890,6 +902,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "funty" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8" + [[package]] name = "futures" version = "0.3.8" @@ -1373,6 +1391,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "nom" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88034cfd6b4a0d54dd14f4a507eceee36c0b70e5a02236c4e4df571102be17f0" +dependencies = [ + "bitvec", + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1649,6 +1679,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.4.6" @@ -2200,6 +2236,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e" + [[package]] name = "taskchampion" version = "0.2.0" @@ -2222,15 +2264,16 @@ name = "taskchampion-cli" version = "0.2.0" dependencies = [ "assert_cmd", - "clap", "config", "dirs 3.0.1", "env_logger", "failure", "log", + "nom 6.0.1", "predicates", "prettytable-rs", "taskchampion", + "tempdir", ] [[package]] @@ -2798,6 +2841,12 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "yaml-rust" version = "0.4.4" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d1f5f99c4..58d154a94 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,19 +1,26 @@ [package] -name = "taskchampion-cli" -version = "0.2.0" authors = ["Dustin J. Mitchell "] edition = "2018" +name = "taskchampion-cli" +version = "0.2.0" [dependencies] -clap = "^2.33.0" -taskchampion = { path = "../taskchampion" } -failure = "^0.1.8" -prettytable-rs = "^0.8.0" -config = { version="^0.10.1", default-features=false, features=["yaml"] } dirs = "^3.0.1" -log = "^0.4.11" env_logger = "^0.8.2" +failure = "^0.1.8" +log = "^0.4.11" +nom = "*" +prettytable-rs = "^0.8.0" + +[dependencies.config] +default-features = false +features = ["yaml"] +version = "^0.10.1" + +[dependencies.taskchampion] +path = "../taskchampion" [dev-dependencies] assert_cmd = "^1.0.1" predicates = "^1.0.5" +tempdir = "^0.3.7" diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs new file mode 100644 index 000000000..c59a4d730 --- /dev/null +++ b/cli/src/argparse/args.rs @@ -0,0 +1,232 @@ +//! Parsers for argument lists -- arrays of strings +use super::ArgList; +use nom::bytes::complete::tag as nomtag; +use nom::{ + branch::*, + character::complete::*, + combinator::*, + error::{Error, ErrorKind}, + multi::*, + sequence::*, + Err, IResult, +}; + +/// Recognizes any argument +pub(super) fn any(input: &str) -> IResult<&str, &str> { + rest(input) +} + +/// Recognizes a literal string +pub(super) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, &str> { + move |input: &str| all_consuming(nomtag(literal))(input) +} + +/// Recognizes a comma-separated list of ID's (integers or UUID prefixes) +pub(super) fn id_list(input: &str) -> IResult<&str, Vec<&str>> { + fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> { + move |input: &str| recognize(many_m_n(n, n, one_of(&b"0123456789abcdefABCDEF"[..])))(input) + } + all_consuming(separated_list1( + char(','), + alt(( + recognize(tuple(( + hex_n(8), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + char('-'), + hex_n(12), + ))), + recognize(tuple(( + hex_n(8), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + ))), + recognize(tuple((hex_n(8), char('-'), hex_n(4), char('-'), hex_n(4)))), + recognize(tuple((hex_n(8), char('-'), hex_n(4)))), + hex_n(8), + digit1, + )), + ))(input) +} + +/// Recognizes a tag prefixed with `+` and returns the tag value +#[allow(dead_code)] // tags not implemented yet +pub(super) fn plus_tag(input: &str) -> IResult<&str, &str> { + fn to_tag(input: (char, &str)) -> Result<&str, ()> { + Ok(input.1) + } + map_res( + all_consuming(tuple((char('+'), recognize(pair(alpha1, alphanumeric0))))), + to_tag, + )(input) +} + +/// Recognizes a tag prefixed with `-` and returns the tag value +#[allow(dead_code)] // tags not implemented yet +pub(super) fn minus_tag(input: &str) -> IResult<&str, &str> { + fn to_tag(input: (char, &str)) -> Result<&str, ()> { + Ok(input.1) + } + map_res( + all_consuming(tuple((char('-'), recognize(pair(alpha1, alphanumeric0))))), + to_tag, + )(input) +} + +/// Recognizes a tag prefixed with either `-` or `+`, returning true for + and false for - +#[allow(dead_code)] // tags not implemented yet +pub(super) fn tag(input: &str) -> IResult<&str, (bool, &str)> { + fn to_plus(input: &str) -> Result<(bool, &str), ()> { + Ok((true, input)) + } + fn to_minus(input: &str) -> Result<(bool, &str), ()> { + Ok((false, input)) + } + alt((map_res(plus_tag, to_plus), map_res(minus_tag, to_minus)))(input) +} + +/// Consume a single argument from an argument list that matches the given string parser (one +/// of the other functions in this module). The given parser must consume the entire input. +pub(super) fn arg_matching<'a, O, F>(f: F) -> impl Fn(ArgList<'a>) -> IResult +where + F: Fn(&'a str) -> IResult<&'a str, O>, +{ + move |input: ArgList<'a>| { + if let Some(arg) = input.get(0) { + return match f(arg) { + Ok(("", rv)) => Ok((&input[1..], rv)), + // single-arg parsers must consume the entire arg + Ok((unconsumed, _)) => panic!("unconsumed argument input {}", unconsumed), + // single-arg parsers are all complete parsers + Err(Err::Incomplete(_)) => unreachable!(), + // for error and failure, rewrite to an error at this position in the arugment list + Err(Err::Error(Error { input: _, code })) => Err(Err::Error(Error { input, code })), + Err(Err::Failure(Error { input: _, code })) => { + Err(Err::Failure(Error { input, code })) + } + }; + } + + Err(Err::Error(Error { + input, + // since we're using nom's built-in Error, our choices here are limited, but tihs + // occurs when there's no argument where one is expected, so Eof seems appropriate + code: ErrorKind::Eof, + })) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_arg_matching() { + assert_eq!( + arg_matching(tag)(argv!["+foo", "bar"]).unwrap(), + (argv!["bar"], (true, "foo")) + ); + assert_eq!( + arg_matching(tag)(argv!["-foo", "bar"]).unwrap(), + (argv!["bar"], (false, "foo")) + ); + assert!(arg_matching(tag)(argv!["foo", "bar"]).is_err()); + } + + #[test] + fn test_plus_tag() { + assert_eq!(plus_tag("+abc").unwrap().1, "abc"); + assert_eq!(plus_tag("+abc123").unwrap().1, "abc123"); + assert!(plus_tag("-abc123").is_err()); + assert!(plus_tag("+abc123 ").is_err()); + assert!(plus_tag(" +abc123").is_err()); + assert!(plus_tag("+1abc").is_err()); + } + + #[test] + fn test_minus_tag() { + assert_eq!(minus_tag("-abc").unwrap().1, "abc"); + assert_eq!(minus_tag("-abc123").unwrap().1, "abc123"); + assert!(minus_tag("+abc123").is_err()); + assert!(minus_tag("-abc123 ").is_err()); + assert!(minus_tag(" -abc123").is_err()); + assert!(minus_tag("-1abc").is_err()); + } + + #[test] + fn test_tag() { + assert_eq!(tag("-abc").unwrap().1, (false, "abc")); + assert_eq!(tag("+abc123").unwrap().1, (true, "abc123")); + assert!(tag("+abc123 --").is_err()); + assert!(tag("-abc123 ").is_err()); + assert!(tag(" -abc123").is_err()); + assert!(tag("-1abc").is_err()); + } + + #[test] + fn test_literal() { + assert_eq!(literal("list")("list").unwrap().1, "list"); + assert!(literal("list")("listicle").is_err()); + assert!(literal("list")(" list ").is_err()); + assert!(literal("list")("LiSt").is_err()); + assert!(literal("list")("denylist").is_err()); + } + + #[test] + fn test_id_list_single() { + assert_eq!(id_list("123").unwrap().1, vec!["123".to_owned()]); + } + + #[test] + fn test_id_list_uuids() { + assert_eq!(id_list("12341234").unwrap().1, vec!["12341234".to_owned()]); + assert_eq!(id_list("1234abcd").unwrap().1, vec!["1234abcd".to_owned()]); + assert_eq!(id_list("abcd1234").unwrap().1, vec!["abcd1234".to_owned()]); + assert_eq!( + id_list("abcd1234-1234").unwrap().1, + vec!["abcd1234-1234".to_owned()] + ); + assert_eq!( + id_list("abcd1234-1234-2345").unwrap().1, + vec!["abcd1234-1234-2345".to_owned()] + ); + assert_eq!( + id_list("abcd1234-1234-2345-3456").unwrap().1, + vec!["abcd1234-1234-2345-3456".to_owned()] + ); + assert_eq!( + id_list("abcd1234-1234-2345-3456-0123456789ab").unwrap().1, + vec!["abcd1234-1234-2345-3456-0123456789ab".to_owned()] + ); + } + + #[test] + fn test_id_list_invalid_partial_uuids() { + assert!(id_list("abcd123").is_err()); + assert!(id_list("abcd12345").is_err()); + assert!(id_list("abcd1234-").is_err()); + assert!(id_list("abcd1234-123").is_err()); + assert!(id_list("abcd1234-1234-").is_err()); + assert!(id_list("abcd1234-12345-").is_err()); + assert!(id_list("abcd1234-1234-2345-3456-0123456789ab-").is_err()); + } + + #[test] + fn test_id_list_uuids_mixed() { + assert_eq!(id_list("abcd1234,abcd1234-1234,abcd1234-1234-2345,abcd1234-1234-2345-3456,abcd1234-1234-2345-3456-0123456789ab").unwrap().1, + vec!["abcd1234".to_owned(), + "abcd1234-1234".to_owned(), + "abcd1234-1234-2345".to_owned(), + "abcd1234-1234-2345-3456".to_owned(), + "abcd1234-1234-2345-3456-0123456789ab".to_owned(), + ]); + } +} diff --git a/cli/src/argparse/command.rs b/cli/src/argparse/command.rs new file mode 100644 index 000000000..7d55616e7 --- /dev/null +++ b/cli/src/argparse/command.rs @@ -0,0 +1,62 @@ +use super::args::*; +use super::{ArgList, Subcommand}; +use failure::{format_err, Fallible}; +use nom::{combinator::*, sequence::*, Err, IResult}; + +/// A command is the overall command that the CLI should execute. +/// +/// It consists of some information common to all commands and a `Subcommand` identifying the +/// particular kind of behavior desired. +#[derive(Debug, PartialEq)] +pub(crate) struct Command { + pub(crate) command_name: String, + pub(crate) subcommand: Subcommand, +} + +impl Command { + pub(super) fn parse(input: ArgList) -> IResult { + fn to_command(input: (&str, Subcommand)) -> Result { + let command = Command { + command_name: input.0.to_owned(), + subcommand: input.1, + }; + Ok(command) + } + map_res( + all_consuming(tuple((arg_matching(any), Subcommand::parse))), + to_command, + )(input) + } + + /// Parse a command from the given list of strings. + pub fn from_argv(argv: &[&str]) -> Fallible { + match Command::parse(argv) { + Ok((&[], cmd)) => Ok(cmd), + Ok((trailing, _)) => Err(format_err!( + "command line has trailing arguments: {:?}", + trailing + )), + Err(Err::Incomplete(_)) => unreachable!(), + Err(Err::Error(e)) => Err(format_err!("command line not recognized: {:?}", e)), + Err(Err::Failure(e)) => Err(format_err!("command line not recognized: {:?}", e)), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + // NOTE: most testing of specific subcommands is handled in `subcommand.rs`. + + #[test] + fn test_version() { + assert_eq!( + Command::from_argv(argv!["task", "version"]).unwrap(), + Command { + subcommand: Subcommand::Version, + command_name: "task".to_owned(), + } + ); + } +} diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs new file mode 100644 index 000000000..24e0519eb --- /dev/null +++ b/cli/src/argparse/filter.rs @@ -0,0 +1,105 @@ +use super::args::{arg_matching, id_list}; +use super::ArgList; +use nom::{combinator::*, multi::fold_many0, IResult}; + +/// A filter represents a selection of a particular set of tasks. +#[derive(Debug, PartialEq, Default, Clone)] +pub(crate) struct Filter { + /// A list of numeric IDs or prefixes of UUIDs + pub(crate) id_list: Option>, +} + +enum FilterArg { + IdList(Vec), +} + +impl Filter { + pub(super) fn parse(input: ArgList) -> IResult { + fn fold(mut acc: Filter, mod_arg: FilterArg) -> Filter { + match mod_arg { + FilterArg::IdList(mut id_list) => { + if let Some(ref mut existing) = acc.id_list { + // given multiple ID lists, concatenate them to represent + // an "OR" between them. + existing.append(&mut id_list); + } else { + acc.id_list = Some(id_list); + } + } + } + acc + } + fold_many0( + Self::id_list, + Filter { + ..Default::default() + }, + fold, + )(input) + } + + fn id_list(input: ArgList) -> IResult { + fn to_filterarg(mut input: Vec<&str>) -> Result { + Ok(FilterArg::IdList( + input.drain(..).map(str::to_owned).collect(), + )) + } + map_res(arg_matching(id_list), to_filterarg)(input) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_empty() { + let (input, filter) = Filter::parse(argv![]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + filter, + Filter { + ..Default::default() + } + ); + } + + #[test] + fn test_id_list_single() { + let (input, filter) = Filter::parse(argv!["1"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + filter, + Filter { + id_list: Some(vec!["1".to_owned()]), + ..Default::default() + } + ); + } + + #[test] + fn test_id_list_commas() { + let (input, filter) = Filter::parse(argv!["1,2,3"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + filter, + Filter { + id_list: Some(vec!["1".to_owned(), "2".to_owned(), "3".to_owned()]), + ..Default::default() + } + ); + } + + #[test] + fn test_id_list_uuids() { + let (input, filter) = Filter::parse(argv!["1,abcd1234"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + filter, + Filter { + id_list: Some(vec!["1".to_owned(), "abcd1234".to_owned()]), + ..Default::default() + } + ); + } +} diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs new file mode 100644 index 000000000..27444466e --- /dev/null +++ b/cli/src/argparse/mod.rs @@ -0,0 +1,27 @@ +/*! + +This module is responsible for parsing command lines (`Arglist`, an alias for `&[&str]`) into `Command` instances. +It removes some redundancy from the command line, for example combining the multiple ways to modify a task into a single `Modification` struct. + +The module is organized as a nom parser over ArgList, and each struct has a `parse` method to parse such a list. + +The exception to this rule is the `args` sub-module, which contains string parsers that are applied to indivdual command-line elements. + +All of the structs produced by this module are fully-owned, data-only structs. +That is, they contain no references, and have no methods to aid in their execution -- that is the `invocation` module's job. + +*/ +mod args; +mod command; +mod filter; +mod modification; +mod report; +mod subcommand; + +pub(crate) use command::Command; +pub(crate) use filter::Filter; +pub(crate) use modification::{DescriptionMod, Modification}; +pub(crate) use report::Report; +pub(crate) use subcommand::Subcommand; + +type ArgList<'a> = &'a [&'a str]; diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs new file mode 100644 index 000000000..6778ba30b --- /dev/null +++ b/cli/src/argparse/modification.rs @@ -0,0 +1,119 @@ +use super::args::{any, arg_matching}; +use super::ArgList; +use nom::{combinator::*, multi::fold_many0, IResult}; +use taskchampion::Status; + +#[derive(Debug, PartialEq, Clone)] +pub enum DescriptionMod { + /// do not change the description + None, + + /// Prepend the given value to the description, with a space separator + Prepend(String), + + /// Append the given value to the description, with a space separator + Append(String), + + /// Set the description + Set(String), +} + +impl Default for DescriptionMod { + fn default() -> Self { + Self::None + } +} + +/// A modification represents a change to a task: adding or removing tags, setting the +/// description, and so on. +#[derive(Debug, PartialEq, Clone, Default)] +pub struct Modification { + /// Change the description + pub description: DescriptionMod, + + /// Set the status + pub status: Option, + + /// Set the "active" status, that is, start (true) or stop (false) the task. + pub active: Option, +} + +/// A single argument that is part of a modification, used internally to this module +enum ModArg<'a> { + Description(&'a str), +} + +impl Modification { + pub(super) fn parse(input: ArgList) -> IResult { + fn fold(mut acc: Modification, mod_arg: ModArg) -> Modification { + match mod_arg { + ModArg::Description(description) => { + if let DescriptionMod::Set(existing) = acc.description { + acc.description = + DescriptionMod::Set(format!("{} {}", existing, description)); + } else { + acc.description = DescriptionMod::Set(description.to_string()); + } + } + } + acc + } + fold_many0( + Self::description, + Modification { + ..Default::default() + }, + fold, + )(input) + } + + fn description(input: ArgList) -> IResult { + fn to_modarg(input: &str) -> Result { + Ok(ModArg::Description(input)) + } + map_res(arg_matching(any), to_modarg)(input) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_empty() { + let (input, modification) = Modification::parse(argv![]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + modification, + Modification { + ..Default::default() + } + ); + } + + #[test] + fn test_single_arg_description() { + let (input, modification) = Modification::parse(argv!["newdesc"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + modification, + Modification { + description: DescriptionMod::Set("newdesc".to_owned()), + ..Default::default() + } + ); + } + + #[test] + fn test_multi_arg_description() { + let (input, modification) = Modification::parse(argv!["new", "desc", "fun"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + modification, + Modification { + description: DescriptionMod::Set("new desc fun".to_owned()), + ..Default::default() + } + ); + } +} diff --git a/cli/src/argparse/report.rs b/cli/src/argparse/report.rs new file mode 100644 index 000000000..b800cafb6 --- /dev/null +++ b/cli/src/argparse/report.rs @@ -0,0 +1,33 @@ +use super::{ArgList, Filter}; +use nom::IResult; + +/// A report specifies a filter as well as a sort order and information about which +/// task attributes to display +#[derive(Debug, PartialEq, Default)] +pub(crate) struct Report { + pub filter: Filter, +} + +impl Report { + pub(super) fn parse(input: ArgList) -> IResult { + let (input, filter) = Filter::parse(input)?; + Ok((input, Report { filter })) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_empty() { + let (input, report) = Report::parse(argv![]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + report, + Report { + ..Default::default() + } + ); + } +} diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs new file mode 100644 index 000000000..8c71832c9 --- /dev/null +++ b/cli/src/argparse/subcommand.rs @@ -0,0 +1,526 @@ +use super::args::*; +use super::{ArgList, DescriptionMod, Filter, Modification, Report}; +use nom::{branch::alt, combinator::*, sequence::*, IResult}; +use taskchampion::Status; + +/// A subcommand is the specific operation that the CLI should execute. +#[derive(Debug, PartialEq)] +pub(crate) enum Subcommand { + /// Display the tool version + Version, + + /// Display the help output + Help { + /// Give the summary help (fitting on a few lines) + summary: bool, + }, + + /// Add a new task + Add { + modification: Modification, + }, + + /// Modify existing tasks + Modify { + filter: Filter, + modification: Modification, + }, + + /// Lists (reports) + List { + report: Report, + }, + + /// Per-task information (typically one task) + Info { + filter: Filter, + debug: bool, + }, + + /// Basic operations without args + Gc, + Sync, +} + +impl Subcommand { + pub(super) fn parse(input: ArgList) -> IResult { + alt(( + Self::version, + Self::help, + Self::add, + Self::modify_prepend_append, + Self::start_stop_done, + Self::list, + Self::info, + Self::gc, + Self::sync, + ))(input) + } + + fn version(input: ArgList) -> IResult { + fn to_subcommand(_: &str) -> Result { + Ok(Subcommand::Version) + } + map_res( + alt(( + arg_matching(literal("version")), + arg_matching(literal("--version")), + )), + to_subcommand, + )(input) + } + + fn help(input: ArgList) -> IResult { + fn to_subcommand(input: &str) -> Result { + Ok(Subcommand::Help { + summary: input == "-h", + }) + } + map_res( + alt(( + arg_matching(literal("help")), + arg_matching(literal("--help")), + arg_matching(literal("-h")), + )), + to_subcommand, + )(input) + } + + fn add(input: ArgList) -> IResult { + fn to_subcommand(input: (&str, Modification)) -> Result { + Ok(Subcommand::Add { + modification: input.1, + }) + } + map_res( + pair(arg_matching(literal("add")), Modification::parse), + to_subcommand, + )(input) + } + + fn modify_prepend_append(input: ArgList) -> IResult { + fn to_subcommand(input: (Filter, &str, Modification)) -> Result { + let filter = input.0; + let mut modification = input.2; + + match input.1 { + "prepend" => { + if let DescriptionMod::Set(s) = modification.description { + modification.description = DescriptionMod::Prepend(s) + } + } + "append" => { + if let DescriptionMod::Set(s) = modification.description { + modification.description = DescriptionMod::Append(s) + } + } + _ => {} + } + + Ok(Subcommand::Modify { + filter, + modification, + }) + } + map_res( + tuple(( + Filter::parse, + alt(( + arg_matching(literal("modify")), + arg_matching(literal("prepend")), + arg_matching(literal("append")), + )), + Modification::parse, + )), + to_subcommand, + )(input) + } + + fn start_stop_done(input: ArgList) -> IResult { + // start, stop, and done are special cases of modify + fn to_subcommand(input: (Filter, &str, Modification)) -> Result { + let filter = input.0; + let mut modification = input.2; + match input.1 { + "start" => modification.active = Some(true), + "stop" => modification.active = Some(false), + "done" => modification.status = Some(Status::Completed), + _ => unreachable!(), + } + Ok(Subcommand::Modify { + filter, + modification, + }) + } + map_res( + tuple(( + Filter::parse, + alt(( + arg_matching(literal("start")), + arg_matching(literal("stop")), + arg_matching(literal("done")), + )), + Modification::parse, + )), + to_subcommand, + )(input) + } + + fn list(input: ArgList) -> IResult { + fn to_subcommand(input: (Report, &str)) -> Result { + Ok(Subcommand::List { report: input.0 }) + } + map_res( + pair(Report::parse, arg_matching(literal("list"))), + to_subcommand, + )(input) + } + + fn info(input: ArgList) -> IResult { + fn to_subcommand(input: (Filter, &str)) -> Result { + let debug = input.1 == "debug"; + Ok(Subcommand::Info { + filter: input.0, + debug, + }) + } + map_res( + pair( + Filter::parse, + alt(( + arg_matching(literal("info")), + arg_matching(literal("debug")), + )), + ), + to_subcommand, + )(input) + } + + fn gc(input: ArgList) -> IResult { + fn to_subcommand(_: &str) -> Result { + Ok(Subcommand::Gc) + } + map_res(arg_matching(literal("gc")), to_subcommand)(input) + } + + fn sync(input: ArgList) -> IResult { + fn to_subcommand(_: &str) -> Result { + Ok(Subcommand::Sync) + } + map_res(arg_matching(literal("sync")), to_subcommand)(input) + } +} + +#[cfg(test)] +mod test { + use super::*; + + const EMPTY: Vec<&str> = vec![]; + + #[test] + fn test_version() { + assert_eq!( + Subcommand::parse(argv!["version"]).unwrap(), + (&EMPTY[..], Subcommand::Version) + ); + } + + #[test] + fn test_dd_version() { + assert_eq!( + Subcommand::parse(argv!["--version"]).unwrap(), + (&EMPTY[..], Subcommand::Version) + ); + } + + #[test] + fn test_d_h() { + assert_eq!( + Subcommand::parse(argv!["-h"]).unwrap(), + (&EMPTY[..], Subcommand::Help { summary: true }) + ); + } + + #[test] + fn test_help() { + assert_eq!( + Subcommand::parse(argv!["help"]).unwrap(), + (&EMPTY[..], Subcommand::Help { summary: false }) + ); + } + + #[test] + fn test_dd_help() { + assert_eq!( + Subcommand::parse(argv!["--help"]).unwrap(), + (&EMPTY[..], Subcommand::Help { summary: false }) + ); + } + + #[test] + fn test_add_description() { + let subcommand = Subcommand::Add { + modification: Modification { + description: DescriptionMod::Set("foo".to_owned()), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["add", "foo"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_add_description_multi() { + let subcommand = Subcommand::Add { + modification: Modification { + description: DescriptionMod::Set("foo bar".to_owned()), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["add", "foo", "bar"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_modify_description_multi() { + let subcommand = Subcommand::Modify { + filter: Filter { + id_list: Some(vec!["123".to_owned()]), + }, + modification: Modification { + description: DescriptionMod::Set("foo bar".to_owned()), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "modify", "foo", "bar"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_append() { + let subcommand = Subcommand::Modify { + filter: Filter { + id_list: Some(vec!["123".to_owned()]), + }, + modification: Modification { + description: DescriptionMod::Append("foo bar".to_owned()), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "append", "foo", "bar"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_prepend() { + let subcommand = Subcommand::Modify { + filter: Filter { + id_list: Some(vec!["123".to_owned()]), + }, + modification: Modification { + description: DescriptionMod::Prepend("foo bar".to_owned()), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "prepend", "foo", "bar"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_done() { + let subcommand = Subcommand::Modify { + filter: Filter { + id_list: Some(vec!["123".to_owned()]), + }, + modification: Modification { + status: Some(Status::Completed), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "done"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_done_modify() { + let subcommand = Subcommand::Modify { + filter: Filter { + id_list: Some(vec!["123".to_owned()]), + }, + modification: Modification { + description: DescriptionMod::Set("now-finished".to_owned()), + status: Some(Status::Completed), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "done", "now-finished"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_start() { + let subcommand = Subcommand::Modify { + filter: Filter { + id_list: Some(vec!["123".to_owned()]), + }, + modification: Modification { + active: Some(true), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "start"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_start_modify() { + let subcommand = Subcommand::Modify { + filter: Filter { + id_list: Some(vec!["123".to_owned()]), + }, + modification: Modification { + active: Some(true), + description: DescriptionMod::Set("mod".to_owned()), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "start", "mod"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_stop() { + let subcommand = Subcommand::Modify { + filter: Filter { + id_list: Some(vec!["123".to_owned()]), + }, + modification: Modification { + active: Some(false), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "stop"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_stop_modify() { + let subcommand = Subcommand::Modify { + filter: Filter { + id_list: Some(vec!["123".to_owned()]), + }, + modification: Modification { + description: DescriptionMod::Set("mod".to_owned()), + active: Some(false), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "stop", "mod"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_list() { + let subcommand = Subcommand::List { + report: Report { + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["list"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_list_filter() { + let subcommand = Subcommand::List { + report: Report { + filter: Filter { + id_list: Some(vec!["12".to_owned(), "13".to_owned()]), + }, + }, + }; + assert_eq!( + Subcommand::parse(argv!["12,13", "list"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_info_filter() { + let subcommand = Subcommand::Info { + debug: false, + filter: Filter { + id_list: Some(vec!["12".to_owned(), "13".to_owned()]), + }, + }; + assert_eq!( + Subcommand::parse(argv!["12,13", "info"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_debug_filter() { + let subcommand = Subcommand::Info { + debug: true, + filter: Filter { + id_list: Some(vec!["12".to_owned()]), + }, + }; + assert_eq!( + Subcommand::parse(argv!["12", "debug"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_gc() { + let subcommand = Subcommand::Gc; + assert_eq!( + Subcommand::parse(argv!["gc"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_gc_extra_args() { + let subcommand = Subcommand::Gc; + assert_eq!( + Subcommand::parse(argv!["gc", "foo"]).unwrap(), + (&vec!["foo"][..], subcommand) + ); + } + + #[test] + fn test_sync() { + let subcommand = Subcommand::Sync; + assert_eq!( + Subcommand::parse(argv!["sync"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } +} diff --git a/cli/src/bin/task.rs b/cli/src/bin/task.rs index ab21c54bc..8d8a756cb 100644 --- a/cli/src/bin/task.rs +++ b/cli/src/bin/task.rs @@ -1,44 +1,8 @@ -use clap::{Error as ClapError, ErrorKind}; use std::process::exit; -use taskchampion_cli::parse_command_line; -enum Output { - Stdout, - Stderr, -} -use Output::*; - -fn bail(err: E, output: Output, code: i32) -> ! { - match output { - Stdout => println!("{}", err), - Stderr => eprintln!("{}", err), - } - exit(code) -} - -fn main() { - env_logger::init(); - let command = match parse_command_line(std::env::args_os()) { - Ok(command) => command, - Err(err) => { - match err.downcast::() { - Ok(err) => { - if err.kind == ErrorKind::HelpDisplayed - || err.kind == ErrorKind::VersionDisplayed - { - // --help and --version go to stdout and succeed - bail(err, Stdout, 0) - } else { - // other clap errors exit with failure - bail(err, Stderr, 1) - } - } - Err(err) => bail(err, Stderr, 1), - } - } - }; - - if let Err(err) = command.run() { - bail(err, Stderr, 1) +pub fn main() { + if let Err(err) = taskchampion_cli::main() { + eprintln!("{}", err); + exit(1); } } diff --git a/cli/src/cmd/add.rs b/cli/src/cmd/add.rs deleted file mode 100644 index 88e7a76f5..000000000 --- a/cli/src/cmd/add.rs +++ /dev/null @@ -1,59 +0,0 @@ -use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; -use failure::{format_err, Fallible}; -use taskchampion::Status; - -use crate::cmd::{ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - description: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("add").about("adds a task").arg( - Arg::with_name("description") - .help("task description") - .required(true), - ), - ) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("add", Some(matches)) => { - // TODO: .unwrap() would be safe here as description is required above - let description: String = match matches.value_of("description") { - Some(v) => v.into(), - None => return ArgMatchResult::Err(format_err!("no description provided")), - }; - ArgMatchResult::Ok(Box::new(Invocation { description })) - } - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let t = command - .get_replica()? - .new_task(Status::Pending, self.description.clone()) - .unwrap(); - println!("added task {}", t.get_uuid()); - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "add", "foo bar"], |inv: &Invocation| { - assert_eq!(inv.description, "foo bar".to_string()); - }); - } -} diff --git a/cli/src/cmd/append.rs b/cli/src/cmd/append.rs deleted file mode 100644 index e32470475..000000000 --- a/cli/src/cmd/append.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::cmd::shared; -use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; - -use crate::cmd::{ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - task: String, - description: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("append").about("appends to a task description") - .arg(shared::task_arg()) - .arg( - Arg::with_name("description") - .help("extra task description") - .required(true), - ), - ) - } - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("append", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { - task: matches.value_of("task").unwrap().into(), - description: matches.value_of("description").unwrap().into(), - })), - _ => ArgMatchResult::None, - } - } - -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let task = shared::get_task(&mut replica, &self.task)?; - - let mut task = task.into_mut(&mut replica); - - let new_description = format!("{} {}", task.get_description(), self.description.clone()); - task.set_description(new_description)?; - println!("appended to task {}", task.get_uuid()); - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!( - vec!["task", "append", "1", "foo bar"], - |inv: &Invocation| { - assert_eq!(inv.description, "foo bar".to_string()); - } - ); - } -} diff --git a/cli/src/cmd/debug.rs b/cli/src/cmd/debug.rs deleted file mode 100644 index 8ebee75c6..000000000 --- a/cli/src/cmd/debug.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::table; -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; -use prettytable::{cell, row, Table}; - -use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - task: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("debug") - .about("debug info for the given task") - .arg(shared::task_arg())) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("debug", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { - task: matches.value_of("task").unwrap().into(), - })), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let task = shared::get_task(&mut replica, &self.task)?; - - let mut t = Table::new(); - t.set_format(table::format()); - t.set_titles(row![b->"key", b->"value"]); - for (k, v) in task.get_taskmap().iter() { - t.add_row(row![k, v]); - } - t.printstd(); - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "debug", "1"], |inv: &Invocation| { - assert_eq!(inv.task, "1".to_string()); - }); - } -} diff --git a/cli/src/cmd/delete.rs b/cli/src/cmd/delete.rs deleted file mode 100644 index 67acf4693..000000000 --- a/cli/src/cmd/delete.rs +++ /dev/null @@ -1,51 +0,0 @@ -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; -use taskchampion::Status; - -use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - task: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("delete") - .about("mark the given task as deleted") - .arg(shared::task_arg())) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("delete", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { - task: matches.value_of("task").unwrap().into(), - })), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let task = shared::get_task(&mut replica, &self.task)?; - let mut task = task.into_mut(&mut replica); - task.stop()?; - task.set_status(Status::Deleted)?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "delete", "1"], |inv: &Invocation| { - assert_eq!(inv.task, "1".to_string()); - }); - } -} diff --git a/cli/src/cmd/done.rs b/cli/src/cmd/done.rs deleted file mode 100644 index 713a747a8..000000000 --- a/cli/src/cmd/done.rs +++ /dev/null @@ -1,51 +0,0 @@ -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; -use taskchampion::Status; - -use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - task: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("done") - .about("finish the given task (status Completed)") - .arg(shared::task_arg())) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("done", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { - task: matches.value_of("task").unwrap().into(), - })), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let task = shared::get_task(&mut replica, &self.task)?; - let mut task = task.into_mut(&mut replica); - task.stop()?; - task.set_status(Status::Completed)?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "done", "1"], |inv: &Invocation| { - assert_eq!(inv.task, "1".to_string()); - }); - } -} diff --git a/cli/src/cmd/gc.rs b/cli/src/cmd/gc.rs deleted file mode 100644 index 15bdd288b..000000000 --- a/cli/src/cmd/gc.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::cmd::{ArgMatchResult, CommandInvocation}; -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; - -#[derive(Debug)] -struct Invocation {} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand(ClapSubCommand::with_name("gc").about("run garbage collection")) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("gc", _) => ArgMatchResult::Ok(Box::new(Invocation {})), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - command.get_replica()?.gc()?; - println!("garbage collected."); - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "gc"], |_inv| {}); - } -} diff --git a/cli/src/cmd/info.rs b/cli/src/cmd/info.rs deleted file mode 100644 index 919e47a76..000000000 --- a/cli/src/cmd/info.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::table; -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; -use prettytable::{cell, row, Table}; - -use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - task: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("info") - .about("info on the given task") - .arg(shared::task_arg())) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("info", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { - task: matches.value_of("task").unwrap().into(), - })), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let task = shared::get_task(&mut replica, &self.task)?; - let uuid = task.get_uuid(); - - let mut t = Table::new(); - t.set_format(table::format()); - t.add_row(row![b->"Uuid", uuid]); - if let Some(i) = replica.get_working_set_index(uuid)? { - t.add_row(row![b->"Id", i]); - } - t.add_row(row![b->"Description", task.get_description()]); - t.add_row(row![b->"Status", task.get_status()]); - t.add_row(row![b->"Active", task.is_active()]); - t.printstd(); - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "info", "1"], |inv: &Invocation| { - assert_eq!(inv.task, "1".to_string()); - }); - } -} diff --git a/cli/src/cmd/list.rs b/cli/src/cmd/list.rs deleted file mode 100644 index 7b1b5078d..000000000 --- a/cli/src/cmd/list.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::table; -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; -use prettytable::{cell, row, Table}; -use taskchampion::Status; - -use crate::cmd::{ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation {} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand(ClapSubCommand::with_name("list").about("lists tasks")) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("list", _) => ArgMatchResult::Ok(Box::new(Invocation {})), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let mut t = Table::new(); - t.set_format(table::format()); - t.set_titles(row![b->"id", b->"act", b->"description"]); - for (uuid, task) in replica.all_tasks().unwrap() { - if task.get_status() != Status::Pending { - continue; - } - let mut id = uuid.to_string(); - if let Some(i) = replica.get_working_set_index(&uuid)? { - id = i.to_string(); - } - let active = match task.is_active() { - true => "*", - false => "", - }; - t.add_row(row![id, active, task.get_description()]); - } - t.printstd(); - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "list"], |_inv| {}); - } -} diff --git a/cli/src/cmd/macros.rs b/cli/src/cmd/macros.rs deleted file mode 100644 index e0fbcd47c..000000000 --- a/cli/src/cmd/macros.rs +++ /dev/null @@ -1,45 +0,0 @@ -/// Define a Command type implementing SubCommand with the enclosed methods (`decorate_app` and -/// `arg_match`), along with a module-level `cmd` function as the parent module expects. -macro_rules! define_subcommand { - ($($f:item) +) => { - struct Command; - - pub(super) fn cmd() -> Box { - Box::new(Command) - } - - impl crate::cmd::SubCommand for Command { - $($f)+ - } - } -} - -/// Define an Invocation type implementing SubCommandInvocation with the enclosed methods. -macro_rules! subcommand_invocation { - ($($f:item) +) => { - impl crate::cmd::SubCommandInvocation for Invocation { - $($f)+ - - #[cfg(test)] - fn as_any(&self) -> &dyn std::any::Any { - self - } - } - } - -} - -/// Parse the first argument as a command line and convert the result to an Invocation (which must -/// be in scope). If the conversion works, calls the second argument with it. -#[cfg(test)] -macro_rules! with_subcommand_invocation { - ($args:expr, $check:expr) => { - let parsed = crate::parse_command_line($args).unwrap(); - let si = parsed - .subcommand - .as_any() - .downcast_ref::() - .expect("SubComand is not of the expected type"); - ($check)(si); - }; -} diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs deleted file mode 100644 index e4e3851ea..000000000 --- a/cli/src/cmd/mod.rs +++ /dev/null @@ -1,73 +0,0 @@ -use clap::{App, ArgMatches}; -use failure::{Error, Fallible}; - -#[macro_use] -mod macros; -mod shared; - -mod add; -mod append; -mod debug; -mod delete; -mod done; -mod gc; -mod info; -mod list; -mod modify; -mod pending; -mod prepend; -mod start; -mod stop; -mod sync; - -/// Get a list of all subcommands in this crate -pub(crate) fn subcommands() -> Vec> { - vec![ - add::cmd(), - append::cmd(), - debug::cmd(), - delete::cmd(), - done::cmd(), - gc::cmd(), - info::cmd(), - list::cmd(), - modify::cmd(), - pending::cmd(), - prepend::cmd(), - start::cmd(), - stop::cmd(), - sync::cmd(), - ] -} - -/// The result of a [`crate::cmd::SubCommand::arg_match`] call -pub(crate) enum ArgMatchResult { - /// No match - None, - - /// A good match - Ok(Box), - - /// A match, but an issue with the command line - Err(Error), -} - -/// A subcommand represents a defined subcommand, and is typically a singleton. -pub(crate) trait SubCommand { - /// Decorate the given [`clap::App`] appropriately for this subcommand - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a>; - - /// If this ArgMatches is for this command, return an appropriate invocation. - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult; -} - -/// A subcommand invocation is specialized to a subcommand -pub(crate) trait SubCommandInvocation: std::fmt::Debug { - fn run(&self, command: &CommandInvocation) -> Fallible<()>; - - // tests use downcasting, which requires a function to cast to Any - #[cfg(test)] - fn as_any(&self) -> &dyn std::any::Any; -} - -pub use shared::CommandInvocation; diff --git a/cli/src/cmd/modify.rs b/cli/src/cmd/modify.rs deleted file mode 100644 index 662941e34..000000000 --- a/cli/src/cmd/modify.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::cmd::shared; -use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; - -use crate::cmd::{ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - task: String, - description: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("modify").about("modifies a task") - .arg(shared::task_arg()) - .arg( - Arg::with_name("description") - .help("task description") - .required(true), - ), - ) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("modify", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { - task: matches.value_of("task").unwrap().into(), - description: matches.value_of("description").unwrap().into(), - })), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let task = shared::get_task(&mut replica, &self.task)?; - - let mut task = task.into_mut(&mut replica); - task.set_description(self.description.clone())?; - println!("modified task {}", task.get_uuid()); - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!( - vec!["task", "modify", "2", "foo bar"], - |inv: &Invocation| { - assert_eq!(inv.task, "2".to_string()); - assert_eq!(inv.description, "foo bar".to_string()); - } - ); - } -} diff --git a/cli/src/cmd/pending.rs b/cli/src/cmd/pending.rs deleted file mode 100644 index f4f99974a..000000000 --- a/cli/src/cmd/pending.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::table; -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; -use prettytable::{cell, row, Table}; - -use crate::cmd::{ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation {} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand(ClapSubCommand::with_name("pending").about("lists pending tasks")) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("pending", _) => ArgMatchResult::Ok(Box::new(Invocation {})), - // default to this command when no subcommand is given - ("", _) => ArgMatchResult::Ok(Box::new(Invocation {})), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let working_set = command.get_replica()?.working_set().unwrap(); - let mut t = Table::new(); - t.set_format(table::format()); - t.set_titles(row![b->"id", b->"act", b->"description"]); - for (i, item) in working_set.iter().enumerate() { - if let Some(ref task) = item { - let active = match task.is_active() { - true => "*", - false => "", - }; - t.add_row(row![i, active, task.get_description()]); - } - } - t.printstd(); - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "pending"], |_inv| {}); - } - - #[test] - fn parse_command_default() { - with_subcommand_invocation!(vec!["task"], |_inv| {}); - } -} diff --git a/cli/src/cmd/prepend.rs b/cli/src/cmd/prepend.rs deleted file mode 100644 index f827ac33a..000000000 --- a/cli/src/cmd/prepend.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::cmd::shared; -use clap::{App, Arg, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; - -use crate::cmd::{ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - task: String, - description: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("prepend").about("add to the start of a task description") - .arg(shared::task_arg()) - .arg( - Arg::with_name("description") - .help("extra task description") - .required(true), - ), - ) - } - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("prepend", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { - task: matches.value_of("task").unwrap().into(), - description: matches.value_of("description").unwrap().into(), - })), - _ => ArgMatchResult::None, - } - } - -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let task = shared::get_task(&mut replica, &self.task)?; - - let mut task = task.into_mut(&mut replica); - - let new_description = format!("{} {}", self.description.clone(), task.get_description()); - task.set_description(new_description)?; - println!("prepended to task {}", task.get_uuid()); - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!( - vec!["task", "prepend", "1", "foo bar"], - |inv: &Invocation| { - assert_eq!(inv.description, "foo bar".to_string()); - } - ); - } -} diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs deleted file mode 100644 index 006b08ac5..000000000 --- a/cli/src/cmd/shared.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::settings; -use clap::Arg; -use config::{Config, ConfigError}; -use failure::{format_err, Fallible}; -use std::cell::{Ref, RefCell}; -use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Task, Uuid}; - -pub(super) fn task_arg<'a>() -> Arg<'a, 'a> { - Arg::with_name("task") - .help("task id or uuid") - .required(true) -} - -pub(super) fn get_task>(replica: &mut Replica, task_arg: S) -> Fallible { - let task_arg = task_arg.as_ref(); - - // first try treating task as a working-set reference - if let Ok(i) = task_arg.parse::() { - if let Some(task) = replica.get_working_set_task(i)? { - return Ok(task); - } - } - - if let Ok(uuid) = Uuid::parse_str(task_arg) { - if let Some(task) = replica.get_task(&uuid)? { - return Ok(task); - } - } - - Err(format_err!("Cannot interpret {:?} as a task", task_arg)) -} - -/// A command invocation contains all of the necessary regarding a single invocation of the CLI. -#[derive(Debug)] -pub struct CommandInvocation { - pub(crate) subcommand: Box, - settings: RefCell, -} - -impl CommandInvocation { - pub(crate) fn new(subcommand: Box) -> Self { - Self { - subcommand, - settings: RefCell::new(Config::default()), - } - } - - pub fn run(self) -> Fallible<()> { - self.subcommand.run(&self) - } - - // -- utilities for command invocations - - pub(super) fn get_settings(&self) -> Fallible> { - { - // use the special `_loaded" config value to detect whether we have - // loaded the configuration yet - let mut settings = self.settings.borrow_mut(); - if let Err(ConfigError::NotFound(_)) = settings.get_bool("_loaded") { - settings.merge(settings::read_settings()?)?; - settings.set("_loaded", true)?; - } - } - Ok(self.settings.borrow()) - } - - pub(super) fn get_replica(&self) -> Fallible { - let settings = self.get_settings()?; - let taskdb_dir = settings.get_str("data_dir")?.into(); - log::debug!("Replica data_dir: {:?}", taskdb_dir); - let replica_config = ReplicaConfig { taskdb_dir }; - Ok(Replica::from_config(replica_config)?) - } - - pub(super) fn get_server(&self) -> Fallible> { - let settings = self.get_settings()?; - let client_id = settings.get_str("server_client_id")?; - let client_id = Uuid::parse_str(&client_id)?; - let origin = settings.get_str("server_origin")?; - log::debug!("Using sync-server with origin {}", origin); - log::debug!("Sync client ID: {}", client_id); - Ok(server::from_config(ServerConfig::Remote { - origin, - client_id, - })?) - } -} diff --git a/cli/src/cmd/start.rs b/cli/src/cmd/start.rs deleted file mode 100644 index 5e09a5c13..000000000 --- a/cli/src/cmd/start.rs +++ /dev/null @@ -1,48 +0,0 @@ -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; - -use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - task: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("start") - .about("start the given task") - .arg(shared::task_arg())) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("start", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { - task: matches.value_of("task").unwrap().into(), - })), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let task = shared::get_task(&mut replica, &self.task)?; - task.into_mut(&mut replica).start()?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "start", "1"], |inv: &Invocation| { - assert_eq!(inv.task, "1".to_string()); - }); - } -} diff --git a/cli/src/cmd/stop.rs b/cli/src/cmd/stop.rs deleted file mode 100644 index baf278734..000000000 --- a/cli/src/cmd/stop.rs +++ /dev/null @@ -1,48 +0,0 @@ -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; - -use crate::cmd::{shared, ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation { - task: String, -} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand( - ClapSubCommand::with_name("stop") - .about("stop the given task") - .arg(shared::task_arg())) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("stop", Some(matches)) => ArgMatchResult::Ok(Box::new(Invocation { - task: matches.value_of("task").unwrap().into(), - })), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let task = shared::get_task(&mut replica, &self.task)?; - task.into_mut(&mut replica).stop()?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "stop", "1"], |inv: &Invocation| { - assert_eq!(inv.task, "1".to_string()); - }); - } -} diff --git a/cli/src/cmd/sync.rs b/cli/src/cmd/sync.rs deleted file mode 100644 index 37c3a76d6..000000000 --- a/cli/src/cmd/sync.rs +++ /dev/null @@ -1,39 +0,0 @@ -use clap::{App, ArgMatches, SubCommand as ClapSubCommand}; -use failure::Fallible; - -use crate::cmd::{ArgMatchResult, CommandInvocation}; - -#[derive(Debug)] -struct Invocation {} - -define_subcommand! { - fn decorate_app<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> { - app.subcommand(ClapSubCommand::with_name("sync").about("sync with the server")) - } - - fn arg_match<'a>(&self, matches: &ArgMatches<'a>) -> ArgMatchResult { - match matches.subcommand() { - ("sync", _) => ArgMatchResult::Ok(Box::new(Invocation {})), - _ => ArgMatchResult::None, - } - } -} - -subcommand_invocation! { - fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica()?; - let mut server = command.get_server()?; - replica.sync(&mut server)?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_command() { - with_subcommand_invocation!(vec!["task", "sync"], |_inv| {}); - } -} diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs new file mode 100644 index 000000000..94ac03227 --- /dev/null +++ b/cli/src/invocation/cmd/add.rs @@ -0,0 +1,34 @@ +use crate::argparse::{DescriptionMod, Modification}; +use failure::Fallible; +use taskchampion::{Replica, Status}; + +pub(crate) fn execute(replica: &mut Replica, modification: Modification) -> Fallible<()> { + let description = match modification.description { + DescriptionMod::Set(ref s) => s.clone(), + _ => "(no description)".to_owned(), + }; + let t = replica.new_task(Status::Pending, description).unwrap(); + println!("added task {}", t.get_uuid()); + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::cmd::test::test_replica; + + #[test] + fn test_add() { + let mut replica = test_replica(); + let modification = Modification { + description: DescriptionMod::Set("my description".to_owned()), + ..Default::default() + }; + execute(&mut replica, modification).unwrap(); + + // check that the task appeared.. + let task = replica.get_working_set_task(1).unwrap().unwrap(); + assert_eq!(task.get_description(), "my description"); + assert_eq!(task.get_status(), Status::Pending); + } +} diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs new file mode 100644 index 000000000..0898c5b1b --- /dev/null +++ b/cli/src/invocation/cmd/gc.rs @@ -0,0 +1,21 @@ +use failure::Fallible; +use taskchampion::Replica; + +pub(crate) fn execute(replica: &mut Replica) -> Fallible<()> { + replica.gc()?; + println!("garbage collected."); + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::cmd::test::test_replica; + + #[test] + fn test_gc() { + let mut replica = test_replica(); + execute(&mut replica).unwrap(); + // this mostly just needs to not fail! + } +} diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs new file mode 100644 index 000000000..eca51efc6 --- /dev/null +++ b/cli/src/invocation/cmd/help.rs @@ -0,0 +1,28 @@ +use failure::Fallible; + +pub(crate) fn execute(command_name: String, summary: bool) -> Fallible<()> { + println!( + "TaskChampion {}: Personal task-tracking", + env!("CARGO_PKG_VERSION") + ); + if !summary { + println!(); + println!("USAGE: {} [args]\n(help output TODO)", command_name); // TODO + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_summary() { + execute("task".to_owned(), true).unwrap(); + } + + #[test] + fn test_long() { + execute("task".to_owned(), false).unwrap(); + } +} diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs new file mode 100644 index 000000000..20887587e --- /dev/null +++ b/cli/src/invocation/cmd/info.rs @@ -0,0 +1,49 @@ +use crate::argparse::Filter; +use crate::invocation::filtered_tasks; +use crate::table; +use failure::Fallible; +use prettytable::{cell, row, Table}; +use taskchampion::Replica; + +pub(crate) fn execute(replica: &mut Replica, filter: Filter, debug: bool) -> Fallible<()> { + for task in filtered_tasks(replica, &filter)? { + let uuid = task.get_uuid(); + + let mut t = Table::new(); + t.set_format(table::format()); + if debug { + t.set_titles(row![b->"key", b->"value"]); + for (k, v) in task.get_taskmap().iter() { + t.add_row(row![k, v]); + } + } else { + t.add_row(row![b->"Uuid", uuid]); + if let Some(i) = replica.get_working_set_index(uuid)? { + t.add_row(row![b->"Id", i]); + } + t.add_row(row![b->"Description", task.get_description()]); + t.add_row(row![b->"Status", task.get_status()]); + t.add_row(row![b->"Active", task.is_active()]); + } + t.printstd(); + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::cmd::test::test_replica; + + #[test] + fn test_info() { + let mut replica = test_replica(); + let filter = Filter { + ..Default::default() + }; + let debug = false; + execute(&mut replica, filter, debug).unwrap(); + // output is to stdout, so this is as much as we can check + } +} diff --git a/cli/src/invocation/cmd/list.rs b/cli/src/invocation/cmd/list.rs new file mode 100644 index 000000000..774200f21 --- /dev/null +++ b/cli/src/invocation/cmd/list.rs @@ -0,0 +1,45 @@ +use crate::argparse::Report; +use crate::invocation::filtered_tasks; +use crate::table; +use failure::Fallible; +use prettytable::{cell, row, Table}; +use taskchampion::Replica; + +pub(crate) fn execute(replica: &mut Replica, report: Report) -> Fallible<()> { + let mut t = Table::new(); + t.set_format(table::format()); + t.set_titles(row![b->"id", b->"act", b->"description"]); + for task in filtered_tasks(replica, &report.filter)? { + let uuid = task.get_uuid(); + let mut id = uuid.to_string(); + if let Some(i) = replica.get_working_set_index(&uuid)? { + id = i.to_string(); + } + let active = match task.is_active() { + true => "*", + false => "", + }; + t.add_row(row![id, active, task.get_description()]); + } + t.printstd(); + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::argparse::Filter; + use crate::invocation::cmd::test::test_replica; + + #[test] + fn test_list() { + let mut replica = test_replica(); + let report = Report { + filter: Filter { + ..Default::default() + }, + }; + execute(&mut replica, report).unwrap(); + // output is to stdout, so this is as much as we can check + } +} diff --git a/cli/src/invocation/cmd/mod.rs b/cli/src/invocation/cmd/mod.rs new file mode 100644 index 000000000..1637968ef --- /dev/null +++ b/cli/src/invocation/cmd/mod.rs @@ -0,0 +1,13 @@ +//! Responsible for executing commands as parsed by [`crate::argparse`]. + +pub(crate) mod add; +pub(crate) mod gc; +pub(crate) mod help; +pub(crate) mod info; +pub(crate) mod list; +pub(crate) mod modify; +pub(crate) mod sync; +pub(crate) mod version; + +#[cfg(test)] +mod test; diff --git a/cli/src/invocation/cmd/modify.rs b/cli/src/invocation/cmd/modify.rs new file mode 100644 index 000000000..48e122437 --- /dev/null +++ b/cli/src/invocation/cmd/modify.rs @@ -0,0 +1,49 @@ +use crate::argparse::{Filter, Modification}; +use crate::invocation::{apply_modification, filtered_tasks}; +use failure::Fallible; +use taskchampion::Replica; + +pub(crate) fn execute( + replica: &mut Replica, + filter: Filter, + modification: Modification, +) -> Fallible<()> { + for task in filtered_tasks(replica, &filter)? { + let mut task = task.into_mut(replica); + + apply_modification(&mut task, &modification)?; + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::argparse::DescriptionMod; + use crate::invocation::cmd::test::test_replica; + use taskchampion::Status; + + #[test] + fn test_modify() { + let mut replica = test_replica(); + + let task = replica + .new_task(Status::Pending, "old description".to_owned()) + .unwrap(); + + let filter = Filter { + ..Default::default() + }; + let modification = Modification { + description: DescriptionMod::Set("new description".to_owned()), + ..Default::default() + }; + execute(&mut replica, filter, modification).unwrap(); + + // check that the task appeared.. + let task = replica.get_task(task.get_uuid()).unwrap().unwrap(); + assert_eq!(task.get_description(), "new description"); + assert_eq!(task.get_status(), Status::Pending); + } +} diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs new file mode 100644 index 000000000..6ce65b88b --- /dev/null +++ b/cli/src/invocation/cmd/sync.rs @@ -0,0 +1,26 @@ +use failure::Fallible; +use taskchampion::{server::Server, Replica}; + +pub(crate) fn execute(replica: &mut Replica, server: &mut Box) -> Fallible<()> { + replica.sync(server)?; + println!("sync complete."); + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::cmd::test::{test_replica, test_server}; + use tempdir::TempDir; + + #[test] + fn test_add() { + let mut replica = test_replica(); + let server_dir = TempDir::new("test").unwrap(); + let mut server = test_server(&server_dir); + + // this just has to not fail -- the details of the actual sync are + // tested thoroughly in the taskchampion crate + execute(&mut replica, &mut server).unwrap(); + } +} diff --git a/cli/src/invocation/cmd/test.rs b/cli/src/invocation/cmd/test.rs new file mode 100644 index 000000000..1ff17fd68 --- /dev/null +++ b/cli/src/invocation/cmd/test.rs @@ -0,0 +1,14 @@ +use taskchampion::{server, taskstorage, Replica, ServerConfig}; +use tempdir::TempDir; + +pub(super) fn test_replica() -> Replica { + let storage = taskstorage::InMemoryStorage::new(); + Replica::new(Box::new(storage)) +} + +pub(super) fn test_server(dir: &TempDir) -> Box { + server::from_config(ServerConfig::Local { + server_dir: dir.path().to_path_buf(), + }) + .unwrap() +} diff --git a/cli/src/invocation/cmd/version.rs b/cli/src/invocation/cmd/version.rs new file mode 100644 index 000000000..ace90b2ea --- /dev/null +++ b/cli/src/invocation/cmd/version.rs @@ -0,0 +1,16 @@ +use failure::Fallible; + +pub(crate) fn execute() -> Fallible<()> { + println!("TaskChampion {}", env!("CARGO_PKG_VERSION")); + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_version() { + execute().unwrap(); + } +} diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs new file mode 100644 index 000000000..b5814dbc4 --- /dev/null +++ b/cli/src/invocation/filter.rs @@ -0,0 +1,38 @@ +use crate::argparse::Filter; +use failure::Fallible; +use taskchampion::{Replica, Task}; + +/// Return the tasks matching the given filter. +pub(super) fn filtered_tasks( + replica: &mut Replica, + filter: &Filter, +) -> Fallible> { + // For the moment, this gets the entire set of tasks and then iterates + // over the result. A few optimizations are possible: + // + // - id_list could be better parsed (id, uuid-fragment, uuid) in argparse + // - depending on the nature of the filter, we could just scan the working set + // - we could produce the tasks on-demand (but at the cost of holding a ref + // to the replica, preventing modifying tasks..) + let mut res = vec![]; + 'task: for (uuid, task) in replica.all_tasks()?.drain() { + if let Some(ref ids) = filter.id_list { + for id in ids { + if let Ok(index) = id.parse::() { + if replica.get_working_set_index(&uuid)? == Some(index) { + res.push(task); + continue 'task; + } + } else if uuid.to_string().starts_with(id) { + res.push(task); + continue 'task; + } + } + } else { + // default to returning all tasks + res.push(task); + continue 'task; + } + } + Ok(res.into_iter()) +} diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs new file mode 100644 index 000000000..42818c142 --- /dev/null +++ b/cli/src/invocation/mod.rs @@ -0,0 +1,109 @@ +//! The invocation module handles invoking the commands parsed by the argparse module. + +use crate::argparse::{Command, Subcommand}; +use config::Config; +use failure::Fallible; +use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Uuid}; + +mod cmd; +mod filter; +mod modify; + +use filter::filtered_tasks; +use modify::apply_modification; + +/// Invoke the given Command in the context of the given settings +pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { + log::debug!("command: {:?}", command); + log::debug!("settings: {:?}", settings); + + // This function examines the command and breaks out the necessary bits to call one of the + // `execute` functions in a submodule of `cmd`. + + // match the subcommands that do not require a replica first, before + // getting the replica + match command { + Command { + subcommand: Subcommand::Help { summary }, + command_name, + } => return cmd::help::execute(command_name, summary), + Command { + subcommand: Subcommand::Version, + .. + } => return cmd::version::execute(), + _ => {} + }; + + let mut replica = get_replica(&settings)?; + match command { + Command { + subcommand: Subcommand::Add { modification }, + .. + } => return cmd::add::execute(&mut replica, modification), + + Command { + subcommand: + Subcommand::Modify { + filter, + modification, + }, + .. + } => return cmd::modify::execute(&mut replica, filter, modification), + + Command { + subcommand: Subcommand::List { report }, + .. + } => return cmd::list::execute(&mut replica, report), + + Command { + subcommand: Subcommand::Info { filter, debug }, + .. + } => return cmd::info::execute(&mut replica, filter, debug), + + Command { + subcommand: Subcommand::Gc, + .. + } => return cmd::gc::execute(&mut replica), + + Command { + subcommand: Subcommand::Sync, + .. + } => { + let mut server = get_server(&settings)?; + return cmd::sync::execute(&mut replica, &mut server); + } + + // handled in the first match, but here to ensure this match is exhaustive + Command { + subcommand: Subcommand::Help { .. }, + .. + } => unreachable!(), + Command { + subcommand: Subcommand::Version, + .. + } => unreachable!(), + } +} + +// utilities for invoke + +/// Get the replica for this invocation +fn get_replica(settings: &Config) -> Fallible { + let taskdb_dir = settings.get_str("data_dir")?.into(); + log::debug!("Replica data_dir: {:?}", taskdb_dir); + let replica_config = ReplicaConfig { taskdb_dir }; + Ok(Replica::from_config(replica_config)?) +} + +/// Get the server for this invocation +fn get_server(settings: &Config) -> Fallible> { + let client_id = settings.get_str("server_client_id")?; + let client_id = Uuid::parse_str(&client_id)?; + let origin = settings.get_str("server_origin")?; + log::debug!("Using sync-server with origin {}", origin); + log::debug!("Sync client ID: {}", client_id); + Ok(server::from_config(ServerConfig::Remote { + origin, + client_id, + })?) +} diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs new file mode 100644 index 000000000..a1fa7c70b --- /dev/null +++ b/cli/src/invocation/modify.rs @@ -0,0 +1,33 @@ +use crate::argparse::{DescriptionMod, Modification}; +use failure::Fallible; +use taskchampion::TaskMut; + +/// Apply the given modification +pub(super) fn apply_modification(task: &mut TaskMut, modification: &Modification) -> Fallible<()> { + match modification.description { + DescriptionMod::Set(ref description) => task.set_description(description.clone())?, + DescriptionMod::Prepend(ref description) => { + task.set_description(format!("{} {}", description, task.get_description()))? + } + DescriptionMod::Append(ref description) => { + task.set_description(format!("{} {}", task.get_description(), description))? + } + DescriptionMod::None => {} + } + + if let Some(ref status) = modification.status { + task.set_status(status.clone())?; + } + + if let Some(true) = modification.active { + task.start()?; + } + + if let Some(false) = modification.active { + task.stop()?; + } + + println!("modified task {}", task.get_uuid()); + + Ok(()) +} diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 6c63d1674..190c8730f 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,60 +1,63 @@ -use clap::{App, AppSettings}; -use failure::Fallible; -use std::ffi::OsString; +/*! +This crate implements the command-line interface to TaskChampion. -mod cmd; -pub(crate) mod settings; +## Design + +The crate is split into two parts: argument parsing (`argparse`) and command invocation (`invocation`). +Both are fairly complex operations, and the split serves both to isolate that complexity and to facilitate testing. + +### Argparse + +The TaskChampion command line API is modeled on TaskWarrior's API, which is far from that of a typical UNIX command. +Tools like `clap` and `structopt` are not flexible enough to handle this syntax. + +Instead, the `argparse` module uses [nom](https://crates.io/crates/nom) to parse command lines as a sequence of words. +These parsers act on a list of strings, `&[&str]`, and at the top level return a `crate::argparse::Command`. +This is a wholly-owned repesentation of the command line's meaning, but with some interpretation. +For example, `task start`, `task stop`, and `task append` all map to a `crate::argparse::Subcommand::Modify` variant. + +### Invocation + +The `invocation` module executes a `Command`, given some settings and other ancillary data. +Most of its functionality is in common functions to handle filtering tasks, modifying tasks, and so on. + +## Rust API + +Note that this crate does not expose a Rust API for use from other crates. +For the public TaskChampion Rust API, see the `taskchampion` crate. + +*/ + +use failure::Fallible; +use std::os::unix::ffi::OsStringExt; +use std::string::FromUtf8Error; + +// NOTE: it's important that this 'mod' comes first so that the macros can be used in other modules +mod macros; + +mod argparse; +mod invocation; +mod settings; mod table; -use cmd::ArgMatchResult; -pub(crate) use cmd::CommandInvocation; +/// The main entry point for the command-line interface. This builds an Invocation +/// from the particulars of the operating-system interface, and then executes it. +pub fn main() -> Fallible<()> { + env_logger::init(); -/// Parse the given command line and return an as-yet un-executed CommandInvocation. -pub fn parse_command_line(iter: I) -> Fallible -where - I: IntoIterator, - T: Into + Clone, -{ - let subcommands = cmd::subcommands(); + // parse the command line into a vector of &str, failing if + // there are invalid utf-8 sequences. + let argv: Vec = std::env::args_os() + .map(|oss| String::from_utf8(oss.into_vec())) + .collect::>()?; + let argv: Vec<&str> = argv.iter().map(|s| s.as_ref()).collect(); - let mut app = App::new("TaskChampion") - .version(env!("CARGO_PKG_VERSION")) - .about("Personal task-tracking") - .setting(AppSettings::ColoredHelp); + // parse the command line + let command = argparse::Command::from_argv(&argv[..])?; - for subcommand in subcommands.iter() { - app = subcommand.decorate_app(app); - } + // load the application settings + let settings = settings::read_settings()?; - let matches = app.get_matches_from_safe(iter)?; - - for subcommand in subcommands.iter() { - match subcommand.arg_match(&matches) { - ArgMatchResult::Ok(invocation) => return Ok(CommandInvocation::new(invocation)), - ArgMatchResult::Err(err) => return Err(err), - ArgMatchResult::None => {} - } - } - - // one of the subcommands also matches the lack of subcommands, so this never - // occurrs. - unreachable!() -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_parse_command_line_success() -> Fallible<()> { - // This just verifies that one of the subcommands works; the subcommands themselves - // are tested in their own unit tests. - parse_command_line(vec!["task", "pending"].iter())?; - Ok(()) - } - - #[test] - fn test_parse_command_line_failure() { - assert!(parse_command_line(vec!["task", "--no-such-arg"].iter()).is_err()); - } + invocation::invoke(command, settings)?; + Ok(()) } diff --git a/cli/src/macros.rs b/cli/src/macros.rs new file mode 100644 index 000000000..284a2f426 --- /dev/null +++ b/cli/src/macros.rs @@ -0,0 +1,12 @@ +#![macro_use] + +/// create a &[&str] from vec notation +#[cfg(test)] +macro_rules! argv { + () => ( + &[][..] + ); + ($($x:expr),* $(,)?) => ( + &[$($x),*][..] + ); +} diff --git a/cli/tests/cli.rs b/cli/tests/cli.rs index 711adc1a5..e4eb0250b 100644 --- a/cli/tests/cli.rs +++ b/cli/tests/cli.rs @@ -2,8 +2,8 @@ use assert_cmd::prelude::*; use predicates::prelude::*; use std::process::Command; -// This tests that the task binary is running and parsing arguments. The details of subcommands -// are handled with unit tests. +// NOTE: This tests that the task binary is running and parsing arguments. The details of +// subcommands are handled with unit tests. #[test] fn help() -> Result<(), Box> { @@ -36,7 +36,7 @@ fn invalid_option() -> Result<(), Box> { cmd.arg("--no-such-option"); cmd.assert() .failure() - .stderr(predicate::str::contains("USAGE")); + .stderr(predicate::str::contains("command line not recognized")); Ok(()) } diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 49ba279db..ac2baacf5 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -42,7 +42,7 @@ impl Priority { } /// The status of a task. The default status in "Pending". -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Status { Pending, Completed, diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index b5f94c8ff..4ddd7df75 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -2,13 +2,11 @@ use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; -#[cfg(test)] mod inmemory; mod kv; mod operation; pub use self::kv::KVStorage; -#[cfg(test)] pub use inmemory::InMemoryStorage; pub use operation::Operation; From 6b550e7516f0ff7fae533e640537ec50f045ff49 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 20 Dec 2020 18:42:21 -0500 Subject: [PATCH 133/548] implement cli help --- Cargo.lock | 12 +- cli/Cargo.toml | 1 + cli/src/argparse/mod.rs | 6 + cli/src/argparse/subcommand.rs | 261 +++++++++++++++++++++++++++------ cli/src/invocation/cmd/help.rs | 12 +- cli/src/lib.rs | 1 + cli/src/usage.rs | 83 +++++++++++ 7 files changed, 323 insertions(+), 53 deletions(-) create mode 100644 cli/src/usage.rs diff --git a/Cargo.lock b/Cargo.lock index 9f40e8688..a0786badd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -569,7 +569,7 @@ dependencies = [ "atty", "bitflags", "strsim", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] @@ -2274,6 +2274,7 @@ dependencies = [ "prettytable-rs", "taskchampion", "tempdir", + "textwrap 0.12.1", ] [[package]] @@ -2346,6 +2347,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.22" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 58d154a94..949896a20 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,6 +11,7 @@ failure = "^0.1.8" log = "^0.4.11" nom = "*" prettytable-rs = "^0.8.0" +textwrap = "0.12.1" [dependencies.config] default-features = false diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index 27444466e..428f7bb0a 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -24,4 +24,10 @@ pub(crate) use modification::{DescriptionMod, Modification}; pub(crate) use report::Report; pub(crate) use subcommand::Subcommand; +use crate::usage::Usage; + type ArgList<'a> = &'a [&'a str]; + +pub(crate) fn get_usage(usage: &mut Usage) { + Subcommand::get_usage(usage); +} diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 8c71832c9..2ebf60644 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -1,7 +1,18 @@ use super::args::*; use super::{ArgList, DescriptionMod, Filter, Modification, Report}; +use crate::usage; use nom::{branch::alt, combinator::*, sequence::*, IResult}; use taskchampion::Status; +use textwrap::dedent; + +// IMPLEMENTATION NOTE: +// +// For each variant of Subcommand, there is a private, empty type of the same name with a `parse` +// method and a `get_usage` method. The parse methods may handle several subcommands, but always +// produce the variant of the same name as the type. +// +// This organization helps to gather the parsing and usage information into +// comprehensible chunks of code, to ensure that everything is documented. /// A subcommand is the specific operation that the CLI should execute. #[derive(Debug, PartialEq)] @@ -45,19 +56,33 @@ pub(crate) enum Subcommand { impl Subcommand { pub(super) fn parse(input: ArgList) -> IResult { alt(( - Self::version, - Self::help, - Self::add, - Self::modify_prepend_append, - Self::start_stop_done, - Self::list, - Self::info, - Self::gc, - Self::sync, + Version::parse, + Help::parse, + Add::parse, + Modify::parse, + List::parse, + Info::parse, + Gc::parse, + Sync::parse, ))(input) } - fn version(input: ArgList) -> IResult { + pub(super) fn get_usage(u: &mut usage::Usage) { + Version::get_usage(u); + Help::get_usage(u); + Add::get_usage(u); + Modify::get_usage(u); + List::get_usage(u); + Info::get_usage(u); + Gc::get_usage(u); + Sync::get_usage(u); + } +} + +struct Version; + +impl Version { + fn parse(input: ArgList) -> IResult { fn to_subcommand(_: &str) -> Result { Ok(Subcommand::Version) } @@ -70,7 +95,20 @@ impl Subcommand { )(input) } - fn help(input: ArgList) -> IResult { + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "version".to_owned(), + syntax: "version".to_owned(), + summary: "Show the TaskChampion version".to_owned(), + description: "Show the version of the TaskChampion binary".to_owned(), + }); + } +} + +struct Help; + +impl Help { + fn parse(input: ArgList) -> IResult { fn to_subcommand(input: &str) -> Result { Ok(Subcommand::Help { summary: input == "-h", @@ -86,7 +124,13 @@ impl Subcommand { )(input) } - fn add(input: ArgList) -> IResult { + fn get_usage(_u: &mut usage::Usage) {} +} + +struct Add; + +impl Add { + fn parse(input: ArgList) -> IResult { fn to_subcommand(input: (&str, Modification)) -> Result { Ok(Subcommand::Add { modification: input.1, @@ -98,7 +142,24 @@ impl Subcommand { )(input) } - fn modify_prepend_append(input: ArgList) -> IResult { + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "add".to_owned(), + syntax: "add [modification]".to_owned(), + summary: "Add a new task".to_owned(), + description: dedent( + " + Add a new, pending task to the list of tasks. The modification must include a + description.", + ), + }); + } +} + +struct Modify; + +impl Modify { + fn parse(input: ArgList) -> IResult { fn to_subcommand(input: (Filter, &str, Modification)) -> Result { let filter = input.0; let mut modification = input.2; @@ -114,6 +175,9 @@ impl Subcommand { modification.description = DescriptionMod::Append(s) } } + "start" => modification.active = Some(true), + "stop" => modification.active = Some(false), + "done" => modification.status = Some(Status::Completed), _ => {} } @@ -129,33 +193,6 @@ impl Subcommand { arg_matching(literal("modify")), arg_matching(literal("prepend")), arg_matching(literal("append")), - )), - Modification::parse, - )), - to_subcommand, - )(input) - } - - fn start_stop_done(input: ArgList) -> IResult { - // start, stop, and done are special cases of modify - fn to_subcommand(input: (Filter, &str, Modification)) -> Result { - let filter = input.0; - let mut modification = input.2; - match input.1 { - "start" => modification.active = Some(true), - "stop" => modification.active = Some(false), - "done" => modification.status = Some(Status::Completed), - _ => unreachable!(), - } - Ok(Subcommand::Modify { - filter, - modification, - }) - } - map_res( - tuple(( - Filter::parse, - alt(( arg_matching(literal("start")), arg_matching(literal("stop")), arg_matching(literal("done")), @@ -166,7 +203,70 @@ impl Subcommand { )(input) } - fn list(input: ArgList) -> IResult { + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "modify".to_owned(), + syntax: "[filter] modify [modification]".to_owned(), + summary: "Modify tasks".to_owned(), + description: dedent( + " + Modify all tasks matching the filter.", + ), + }); + u.subcommands.push(usage::Subcommand { + name: "prepend".to_owned(), + syntax: "[filter] prepend [modification]".to_owned(), + summary: "Prepend task description".to_owned(), + description: dedent( + " + Modify all tasks matching the filter by inserting the given description before each + task's description.", + ), + }); + u.subcommands.push(usage::Subcommand { + name: "append".to_owned(), + syntax: "[filter] append [modification]".to_owned(), + summary: "Append task description".to_owned(), + description: dedent( + " + Modify all tasks matching the filter by adding the given description to the end + of each task's description.", + ), + }); + u.subcommands.push(usage::Subcommand { + name: "start".to_owned(), + syntax: "[filter] start [modification]".to_owned(), + summary: "Start tasks".to_owned(), + description: dedent( + " + Start all tasks matching the filter, additionally applying any given modifications."), + }); + u.subcommands.push(usage::Subcommand { + name: "stop".to_owned(), + syntax: "[filter] start [modification]".to_owned(), + summary: "Stop tasks".to_owned(), + description: dedent( + " + Stop all tasks matching the filter, additionally applying any given modifications.", + ), + }); + u.subcommands.push(usage::Subcommand { + name: "done".to_owned(), + syntax: "[filter] start [modification]".to_owned(), + summary: "Mark tasks as completed".to_owned(), + description: dedent( + " + Mark all tasks matching the filter as completed, additionally applying any given + modifications.", + ), + }); + } +} + +struct List; + +impl List { + fn parse(input: ArgList) -> IResult { fn to_subcommand(input: (Report, &str)) -> Result { Ok(Subcommand::List { report: input.0 }) } @@ -176,7 +276,23 @@ impl Subcommand { )(input) } - fn info(input: ArgList) -> IResult { + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "list".to_owned(), + syntax: "[filter] list".to_owned(), + summary: "List tasks".to_owned(), + description: dedent( + " + Show a list of the tasks matching the filter", + ), + }); + } +} + +struct Info; + +impl Info { + fn parse(input: ArgList) -> IResult { fn to_subcommand(input: (Filter, &str)) -> Result { let debug = input.1 == "debug"; Ok(Subcommand::Info { @@ -196,19 +312,76 @@ impl Subcommand { )(input) } - fn gc(input: ArgList) -> IResult { + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "info".to_owned(), + syntax: "[filter] info".to_owned(), + summary: "Show tasks".to_owned(), + description: dedent( + " + Show information about all tasks matching the fiter.", + ), + }); + u.subcommands.push(usage::Subcommand { + name: "debug".to_owned(), + syntax: "[filter] debug".to_owned(), + summary: "Show task debug details".to_owned(), + description: dedent( + " + Show all key/value properties of the tasks matching the fiter.", + ), + }); + } +} + +struct Gc; + +impl Gc { + fn parse(input: ArgList) -> IResult { fn to_subcommand(_: &str) -> Result { Ok(Subcommand::Gc) } map_res(arg_matching(literal("gc")), to_subcommand)(input) } - fn sync(input: ArgList) -> IResult { + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "gc".to_owned(), + syntax: "gc".to_owned(), + summary: "Perform 'garbage collection'".to_owned(), + description: dedent( + " + Perform 'garbage collection'. This refreshes the list of pending tasks + and their short id's.", + ), + }); + } +} + +struct Sync; + +impl Sync { + fn parse(input: ArgList) -> IResult { fn to_subcommand(_: &str) -> Result { Ok(Subcommand::Sync) } map_res(arg_matching(literal("sync")), to_subcommand)(input) } + + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "sync".to_owned(), + syntax: "sync".to_owned(), + summary: "Synchronize this replica".to_owned(), + description: dedent( + " + Synchronize this replica locally or against a remote server, as configured. + + Synchronization is a critical part of maintaining the task database, and should + be done regularly, even if only locally. It is typically run in a crontask.", + ), + }) + } } #[cfg(test)] diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index eca51efc6..a53e54897 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -1,14 +1,10 @@ +use crate::usage::Usage; use failure::Fallible; +use std::io; pub(crate) fn execute(command_name: String, summary: bool) -> Fallible<()> { - println!( - "TaskChampion {}: Personal task-tracking", - env!("CARGO_PKG_VERSION") - ); - if !summary { - println!(); - println!("USAGE: {} [args]\n(help output TODO)", command_name); // TODO - } + let usage = Usage::new(); + usage.write_help(io::stdout(), command_name, summary)?; Ok(()) } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 190c8730f..01900fbc6 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -39,6 +39,7 @@ mod argparse; mod invocation; mod settings; mod table; +mod usage; /// The main entry point for the command-line interface. This builds an Invocation /// from the particulars of the operating-system interface, and then executes it. diff --git a/cli/src/usage.rs b/cli/src/usage.rs new file mode 100644 index 000000000..d1b36d59d --- /dev/null +++ b/cli/src/usage.rs @@ -0,0 +1,83 @@ +//! This module handles creation of CLI usage documents (--help, manpages, etc.) in +//! a way that puts the source of that documentation near its implementation. + +use crate::argparse; +use std::io::{Result, Write}; +use textwrap::indent; + +/// A top-level structure containing usage/help information for the entire CLI. +#[derive(Debug, Default)] +pub(crate) struct Usage { + pub(crate) subcommands: Vec, +} + +impl Usage { + /// Get a new, completely-filled-out usage object + pub(crate) fn new() -> Self { + let mut rv = Self { + ..Default::default() + }; + + argparse::get_usage(&mut rv); + + // TODO: sort subcommands + + rv + } + + /// Write this usage to the given output as a help string, writing a short version if `summary` + /// is true. + pub(crate) fn write_help( + &self, + mut w: W, + command_name: String, + summary: bool, + ) -> Result<()> { + write!( + w, + "TaskChampion {}: Personal task-tracking\n\n", + env!("CARGO_PKG_VERSION") + )?; + write!(w, "USAGE:\n {} [args]\n\n", command_name)?; + write!(w, "TaskChampion subcommands:\n")?; + for subcommand in self.subcommands.iter() { + subcommand.write_help(&mut w, summary)?; + } + if !summary { + write!(w, "\nSee `task help` for more detail\n")?; + } + Ok(()) + } +} + +#[derive(Debug, Default)] +pub(crate) struct Subcommand { + /// Name of the subcommand + pub(crate) name: String, + + /// Syntax summary, without command_name + pub(crate) syntax: String, + + /// One-line description of the subcommand. Use an initial capital and no trailing period. + pub(crate) summary: String, + + /// Multi-line description of the subcommand. It's OK for this to duplicate summary, as the + /// two are not displayed together. + pub(crate) description: String, +} + +impl Subcommand { + fn write_help(&self, mut w: W, summary: bool) -> Result<()> { + if summary { + write!(w, " task {} - {}\n", self.name, self.summary)?; + } else { + write!( + w, + " task {}\n{}\n", + self.syntax, + indent(self.description.trim(), " ") + )?; + } + Ok(()) + } +} From 7d17740ca89e47c7db09c0ad37dddeeaf5551d7d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 20 Dec 2020 19:45:24 -0500 Subject: [PATCH 134/548] use a generic Write instance for command output --- Cargo.lock | 1 + cli/Cargo.toml | 1 + cli/src/invocation/cmd/add.rs | 16 ++++++++++---- cli/src/invocation/cmd/gc.rs | 12 ++++++----- cli/src/invocation/cmd/help.rs | 17 ++++++++++----- cli/src/invocation/cmd/info.rs | 22 ++++++++++++++----- cli/src/invocation/cmd/list.rs | 21 +++++++++++++----- cli/src/invocation/cmd/modify.rs | 15 ++++++++++--- cli/src/invocation/cmd/sync.rs | 18 ++++++++++------ cli/src/invocation/cmd/test.rs | 36 +++++++++++++++++++++++++++++++ cli/src/invocation/cmd/version.rs | 13 ++++++++--- cli/src/invocation/mod.rs | 19 +++++++++------- cli/src/invocation/modify.rs | 9 ++++++-- 13 files changed, 154 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0786badd..b8549ae21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2274,6 +2274,7 @@ dependencies = [ "prettytable-rs", "taskchampion", "tempdir", + "termcolor", "textwrap 0.12.1", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 949896a20..a243a5d44 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -12,6 +12,7 @@ log = "^0.4.11" nom = "*" prettytable-rs = "^0.8.0" textwrap = "0.12.1" +termcolor = "1.1.2" [dependencies.config] default-features = false diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index 94ac03227..79cb5dd4b 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -1,34 +1,42 @@ use crate::argparse::{DescriptionMod, Modification}; use failure::Fallible; use taskchampion::{Replica, Status}; +use termcolor::WriteColor; -pub(crate) fn execute(replica: &mut Replica, modification: Modification) -> Fallible<()> { +pub(crate) fn execute( + w: &mut W, + replica: &mut Replica, + modification: Modification, +) -> Fallible<()> { let description = match modification.description { DescriptionMod::Set(ref s) => s.clone(), _ => "(no description)".to_owned(), }; let t = replica.new_task(Status::Pending, description).unwrap(); - println!("added task {}", t.get_uuid()); + write!(w, "added task {}\n", t.get_uuid())?; Ok(()) } #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::test_replica; + use crate::invocation::cmd::test::*; #[test] fn test_add() { + let mut w = test_writer(); let mut replica = test_replica(); let modification = Modification { description: DescriptionMod::Set("my description".to_owned()), ..Default::default() }; - execute(&mut replica, modification).unwrap(); + execute(&mut w, &mut replica, modification).unwrap(); // check that the task appeared.. let task = replica.get_working_set_task(1).unwrap().unwrap(); assert_eq!(task.get_description(), "my description"); assert_eq!(task.get_status(), Status::Pending); + + assert_eq!(w.into_string(), format!("added task {}\n", task.get_uuid())); } } diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs index 0898c5b1b..644974eb5 100644 --- a/cli/src/invocation/cmd/gc.rs +++ b/cli/src/invocation/cmd/gc.rs @@ -1,21 +1,23 @@ use failure::Fallible; use taskchampion::Replica; +use termcolor::WriteColor; -pub(crate) fn execute(replica: &mut Replica) -> Fallible<()> { +pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Fallible<()> { replica.gc()?; - println!("garbage collected."); + write!(w, "garbage collected.\n")?; Ok(()) } #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::test_replica; + use crate::invocation::cmd::test::*; #[test] fn test_gc() { + let mut w = test_writer(); let mut replica = test_replica(); - execute(&mut replica).unwrap(); - // this mostly just needs to not fail! + execute(&mut w, &mut replica).unwrap(); + assert_eq!(&w.into_string(), "garbage collected.\n") } } diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index a53e54897..a95e8415f 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -1,24 +1,31 @@ use crate::usage::Usage; use failure::Fallible; -use std::io; +use termcolor::WriteColor; -pub(crate) fn execute(command_name: String, summary: bool) -> Fallible<()> { +pub(crate) fn execute( + w: &mut W, + command_name: String, + summary: bool, +) -> Fallible<()> { let usage = Usage::new(); - usage.write_help(io::stdout(), command_name, summary)?; + usage.write_help(w, command_name, summary)?; Ok(()) } #[cfg(test)] mod test { use super::*; + use crate::invocation::cmd::test::*; #[test] fn test_summary() { - execute("task".to_owned(), true).unwrap(); + let mut w = test_writer(); + execute(&mut w, "task".to_owned(), true).unwrap(); } #[test] fn test_long() { - execute("task".to_owned(), false).unwrap(); + let mut w = test_writer(); + execute(&mut w, "task".to_owned(), false).unwrap(); } } diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index 20887587e..852c8faa9 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -4,8 +4,14 @@ use crate::table; use failure::Fallible; use prettytable::{cell, row, Table}; use taskchampion::Replica; +use termcolor::WriteColor; -pub(crate) fn execute(replica: &mut Replica, filter: Filter, debug: bool) -> Fallible<()> { +pub(crate) fn execute( + w: &mut W, + replica: &mut Replica, + filter: Filter, + debug: bool, +) -> Fallible<()> { for task in filtered_tasks(replica, &filter)? { let uuid = task.get_uuid(); @@ -25,7 +31,7 @@ pub(crate) fn execute(replica: &mut Replica, filter: Filter, debug: bool) -> Fal t.add_row(row![b->"Status", task.get_status()]); t.add_row(row![b->"Active", task.is_active()]); } - t.printstd(); + t.print(w)?; } Ok(()) @@ -34,16 +40,22 @@ pub(crate) fn execute(replica: &mut Replica, filter: Filter, debug: bool) -> Fal #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::test_replica; + use crate::invocation::cmd::test::*; + use taskchampion::Status; #[test] fn test_info() { + let mut w = test_writer(); let mut replica = test_replica(); + replica + .new_task(Status::Pending, "my task".to_owned()) + .unwrap(); + let filter = Filter { ..Default::default() }; let debug = false; - execute(&mut replica, filter, debug).unwrap(); - // output is to stdout, so this is as much as we can check + execute(&mut w, &mut replica, filter, debug).unwrap(); + assert!(w.into_string().contains("my task")); } } diff --git a/cli/src/invocation/cmd/list.rs b/cli/src/invocation/cmd/list.rs index 774200f21..4afcdab93 100644 --- a/cli/src/invocation/cmd/list.rs +++ b/cli/src/invocation/cmd/list.rs @@ -4,8 +4,13 @@ use crate::table; use failure::Fallible; use prettytable::{cell, row, Table}; use taskchampion::Replica; +use termcolor::WriteColor; -pub(crate) fn execute(replica: &mut Replica, report: Report) -> Fallible<()> { +pub(crate) fn execute( + w: &mut W, + replica: &mut Replica, + report: Report, +) -> Fallible<()> { let mut t = Table::new(); t.set_format(table::format()); t.set_titles(row![b->"id", b->"act", b->"description"]); @@ -21,7 +26,7 @@ pub(crate) fn execute(replica: &mut Replica, report: Report) -> Fallible<()> { }; t.add_row(row![id, active, task.get_description()]); } - t.printstd(); + t.print(w)?; Ok(()) } @@ -29,17 +34,23 @@ pub(crate) fn execute(replica: &mut Replica, report: Report) -> Fallible<()> { mod test { use super::*; use crate::argparse::Filter; - use crate::invocation::cmd::test::test_replica; + use crate::invocation::cmd::test::*; + use taskchampion::Status; #[test] fn test_list() { + let mut w = test_writer(); let mut replica = test_replica(); + replica + .new_task(Status::Pending, "my task".to_owned()) + .unwrap(); + let report = Report { filter: Filter { ..Default::default() }, }; - execute(&mut replica, report).unwrap(); - // output is to stdout, so this is as much as we can check + execute(&mut w, &mut replica, report).unwrap(); + assert!(w.into_string().contains("my task")); } } diff --git a/cli/src/invocation/cmd/modify.rs b/cli/src/invocation/cmd/modify.rs index 48e122437..dadcd9319 100644 --- a/cli/src/invocation/cmd/modify.rs +++ b/cli/src/invocation/cmd/modify.rs @@ -2,8 +2,10 @@ use crate::argparse::{Filter, Modification}; use crate::invocation::{apply_modification, filtered_tasks}; use failure::Fallible; use taskchampion::Replica; +use termcolor::WriteColor; -pub(crate) fn execute( +pub(crate) fn execute( + w: &mut W, replica: &mut Replica, filter: Filter, modification: Modification, @@ -11,7 +13,7 @@ pub(crate) fn execute( for task in filtered_tasks(replica, &filter)? { let mut task = task.into_mut(replica); - apply_modification(&mut task, &modification)?; + apply_modification(w, &mut task, &modification)?; } Ok(()) @@ -22,10 +24,12 @@ mod test { use super::*; use crate::argparse::DescriptionMod; use crate::invocation::cmd::test::test_replica; + use crate::invocation::cmd::test::*; use taskchampion::Status; #[test] fn test_modify() { + let mut w = test_writer(); let mut replica = test_replica(); let task = replica @@ -39,11 +43,16 @@ mod test { description: DescriptionMod::Set("new description".to_owned()), ..Default::default() }; - execute(&mut replica, filter, modification).unwrap(); + execute(&mut w, &mut replica, filter, modification).unwrap(); // check that the task appeared.. let task = replica.get_task(task.get_uuid()).unwrap().unwrap(); assert_eq!(task.get_description(), "new description"); assert_eq!(task.get_status(), Status::Pending); + + assert_eq!( + w.into_string(), + format!("modified task {}\n", task.get_uuid()) + ); } } diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index 6ce65b88b..737406ae9 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -1,26 +1,32 @@ use failure::Fallible; use taskchampion::{server::Server, Replica}; +use termcolor::WriteColor; -pub(crate) fn execute(replica: &mut Replica, server: &mut Box) -> Fallible<()> { +pub(crate) fn execute( + w: &mut W, + replica: &mut Replica, + server: &mut Box, +) -> Fallible<()> { replica.sync(server)?; - println!("sync complete."); + write!(w, "sync complete.\n")?; Ok(()) } #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::{test_replica, test_server}; + use crate::invocation::cmd::test::*; use tempdir::TempDir; #[test] fn test_add() { + let mut w = test_writer(); let mut replica = test_replica(); let server_dir = TempDir::new("test").unwrap(); let mut server = test_server(&server_dir); - // this just has to not fail -- the details of the actual sync are - // tested thoroughly in the taskchampion crate - execute(&mut replica, &mut server).unwrap(); + // Note that the details of the actual sync are tested thoroughly in the taskchampion crate + execute(&mut w, &mut replica, &mut server).unwrap(); + assert_eq!(&w.into_string(), "sync complete.\n") } } diff --git a/cli/src/invocation/cmd/test.rs b/cli/src/invocation/cmd/test.rs index 1ff17fd68..8f32723b0 100644 --- a/cli/src/invocation/cmd/test.rs +++ b/cli/src/invocation/cmd/test.rs @@ -1,3 +1,4 @@ +use std::io; use taskchampion::{server, taskstorage, Replica, ServerConfig}; use tempdir::TempDir; @@ -12,3 +13,38 @@ pub(super) fn test_server(dir: &TempDir) -> Box { }) .unwrap() } + +pub(super) struct TestWriter { + data: Vec, +} + +impl TestWriter { + pub(super) fn into_string(self) -> String { + String::from_utf8(self.data).unwrap() + } +} + +impl io::Write for TestWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.data.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.data.flush() + } +} + +impl termcolor::WriteColor for TestWriter { + fn supports_color(&self) -> bool { + false + } + fn set_color(&mut self, _spec: &termcolor::ColorSpec) -> io::Result<()> { + Ok(()) + } + fn reset(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub(super) fn test_writer() -> TestWriter { + TestWriter { data: vec![] } +} diff --git a/cli/src/invocation/cmd/version.rs b/cli/src/invocation/cmd/version.rs index ace90b2ea..8a74a7681 100644 --- a/cli/src/invocation/cmd/version.rs +++ b/cli/src/invocation/cmd/version.rs @@ -1,16 +1,23 @@ use failure::Fallible; +use termcolor::{ColorSpec, WriteColor}; -pub(crate) fn execute() -> Fallible<()> { - println!("TaskChampion {}", env!("CARGO_PKG_VERSION")); +pub(crate) fn execute(w: &mut W) -> Fallible<()> { + write!(w, "TaskChampion ")?; + w.set_color(ColorSpec::new().set_bold(true))?; + write!(w, "{}\n", env!("CARGO_PKG_VERSION"))?; + w.reset()?; Ok(()) } #[cfg(test)] mod test { use super::*; + use crate::invocation::cmd::test::*; #[test] fn test_version() { - execute().unwrap(); + let mut w = test_writer(); + execute(&mut w).unwrap(); + assert!(w.into_string().starts_with("TaskChampion ")); } } diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 42818c142..ec04b3ac1 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -4,6 +4,7 @@ use crate::argparse::{Command, Subcommand}; use config::Config; use failure::Fallible; use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Uuid}; +use termcolor::{ColorChoice, StandardStream}; mod cmd; mod filter; @@ -17,6 +18,8 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { log::debug!("command: {:?}", command); log::debug!("settings: {:?}", settings); + let mut w = StandardStream::stdout(ColorChoice::Auto); + // This function examines the command and breaks out the necessary bits to call one of the // `execute` functions in a submodule of `cmd`. @@ -26,11 +29,11 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { Command { subcommand: Subcommand::Help { summary }, command_name, - } => return cmd::help::execute(command_name, summary), + } => return cmd::help::execute(&mut w, command_name, summary), Command { subcommand: Subcommand::Version, .. - } => return cmd::version::execute(), + } => return cmd::version::execute(&mut w), _ => {} }; @@ -39,7 +42,7 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { Command { subcommand: Subcommand::Add { modification }, .. - } => return cmd::add::execute(&mut replica, modification), + } => return cmd::add::execute(&mut w, &mut replica, modification), Command { subcommand: @@ -48,29 +51,29 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { modification, }, .. - } => return cmd::modify::execute(&mut replica, filter, modification), + } => return cmd::modify::execute(&mut w, &mut replica, filter, modification), Command { subcommand: Subcommand::List { report }, .. - } => return cmd::list::execute(&mut replica, report), + } => return cmd::list::execute(&mut w, &mut replica, report), Command { subcommand: Subcommand::Info { filter, debug }, .. - } => return cmd::info::execute(&mut replica, filter, debug), + } => return cmd::info::execute(&mut w, &mut replica, filter, debug), Command { subcommand: Subcommand::Gc, .. - } => return cmd::gc::execute(&mut replica), + } => return cmd::gc::execute(&mut w, &mut replica), Command { subcommand: Subcommand::Sync, .. } => { let mut server = get_server(&settings)?; - return cmd::sync::execute(&mut replica, &mut server); + return cmd::sync::execute(&mut w, &mut replica, &mut server); } // handled in the first match, but here to ensure this match is exhaustive diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index a1fa7c70b..29802dbab 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,9 +1,14 @@ use crate::argparse::{DescriptionMod, Modification}; use failure::Fallible; use taskchampion::TaskMut; +use termcolor::WriteColor; /// Apply the given modification -pub(super) fn apply_modification(task: &mut TaskMut, modification: &Modification) -> Fallible<()> { +pub(super) fn apply_modification( + w: &mut W, + task: &mut TaskMut, + modification: &Modification, +) -> Fallible<()> { match modification.description { DescriptionMod::Set(ref description) => task.set_description(description.clone())?, DescriptionMod::Prepend(ref description) => { @@ -27,7 +32,7 @@ pub(super) fn apply_modification(task: &mut TaskMut, modification: &Modification task.stop()?; } - println!("modified task {}", task.get_uuid()); + write!(w, "modified task {}\n", task.get_uuid())?; Ok(()) } From 8ba6277cce65747da5f4e4fdbaf313405066245f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 20 Dec 2020 19:54:38 -0500 Subject: [PATCH 135/548] use 'atty' to detect when colors can be used --- Cargo.lock | 1 + cli/Cargo.toml | 1 + cli/src/invocation/mod.rs | 11 ++++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b8549ae21..7b3aeae18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2264,6 +2264,7 @@ name = "taskchampion-cli" version = "0.2.0" dependencies = [ "assert_cmd", + "atty", "config", "dirs 3.0.1", "env_logger", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a243a5d44..9daeb1653 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -13,6 +13,7 @@ nom = "*" prettytable-rs = "^0.8.0" textwrap = "0.12.1" termcolor = "1.1.2" +atty = "0.2.14" [dependencies.config] default-features = false diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index ec04b3ac1..18d8000b6 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -18,7 +18,7 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { log::debug!("command: {:?}", command); log::debug!("settings: {:?}", settings); - let mut w = StandardStream::stdout(ColorChoice::Auto); + let mut w = get_writer()?; // This function examines the command and breaks out the necessary bits to call one of the // `execute` functions in a submodule of `cmd`. @@ -110,3 +110,12 @@ fn get_server(settings: &Config) -> Fallible> { client_id, })?) } + +/// Get a WriteColor implementation based on whether the output is a tty. +fn get_writer() -> Fallible { + Ok(StandardStream::stdout(if atty::is(atty::Stream::Stdout) { + ColorChoice::Auto + } else { + ColorChoice::Never + })) +} From 28c5fb2268f8bea698419d6762ba892ae2170384 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 21 Dec 2020 19:31:30 +0000 Subject: [PATCH 136/548] Add support for task tags Based on properties named `tag.` as already documented --- docs/src/tasks.md | 2 +- taskchampion/src/lib.rs | 4 +- taskchampion/src/task.rs | 194 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 190 insertions(+), 10 deletions(-) diff --git a/docs/src/tasks.md b/docs/src/tasks.md index d53715bbd..8bdf8fa72 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -31,9 +31,9 @@ The following keys, and key formats, are defined: * `description` - the one-line summary of the task * `modified` - the time of the last modification of this task * `start.` - either an empty string (representing work on the task to the task that has not been stopped) or a timestamp (representing the time that work stopped) +* `tag.` - indicates this task has tag `` (value is an empty string) The following are not yet implemented: * `dep.` - indicates this task depends on `` (value is an empty string) -* `tag.` - indicates this task has tag `` (value is an empty string) * `annotation.` - value is an annotation created at the given time diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 64c6cb238..715dcaefa 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -34,9 +34,7 @@ mod utils; pub use config::{ReplicaConfig, ServerConfig}; pub use replica::Replica; -pub use task::Priority; -pub use task::Status; -pub use task::{Task, TaskMut}; +pub use task::{Priority, Status, Tag, Task, TaskMut}; /// Re-exported type from the `uuid` crate, for ease of compatibility for consumers of this crate. pub use uuid::Uuid; diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index ac2baacf5..2710a241c 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -1,8 +1,10 @@ use crate::replica::Replica; use crate::taskstorage::TaskMap; use chrono::prelude::*; -use failure::Fallible; +use failure::{format_err, Fallible}; use log::trace; +use std::convert::{TryFrom, TryInto}; +use std::fmt; use uuid::Uuid; pub type Timestamp = DateTime; @@ -81,6 +83,56 @@ impl Status { } } +/// A Tag is a newtype around a String that limits its values to valid tags. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Tag(String); + +impl Tag { + fn from_str(value: &str) -> Result { + if let Some(c) = value.chars().next() { + if !c.is_ascii_alphabetic() { + return Err(format_err!("first character of a tag must be alphabetic")); + } + } else { + return Err(format_err!("tags must have at least one character")); + } + if !value.chars().skip(1).all(|c| c.is_ascii_alphanumeric()) { + return Err(format_err!( + "characters of a tag after the first must be alphanumeric" + )); + } + Ok(Self(String::from(value))) + } +} + +impl TryFrom<&str> for Tag { + type Error = failure::Error; + + fn try_from(value: &str) -> Result { + Self::from_str(value) + } +} + +impl TryFrom<&String> for Tag { + type Error = failure::Error; + + fn try_from(value: &String) -> Result { + Self::from_str(&value[..]) + } +} + +impl fmt::Display for Tag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl AsRef for Tag { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + #[derive(Debug, PartialEq)] pub struct Annotation { pub entry: Timestamp, @@ -155,13 +207,31 @@ impl Task { .any(|(k, v)| k.starts_with("start.") && v.is_empty()) } + /// Check if this task has the given tag + pub fn has_tag(&self, tag: &Tag) -> bool { + self.taskmap.contains_key(&format!("tag.{}", tag)) + } + + /// Iterate over the task's tags + pub fn get_tags(&self) -> impl Iterator + '_ { + self.taskmap.iter().filter_map(|(k, _)| { + if k.starts_with("tag.") { + if let Ok(tag) = (&k[4..]).try_into() { + return Some(tag); + } + // note that invalid "tag.*" are ignored + } + None + }) + } + pub fn get_modified(&self) -> Option> { self.get_timestamp("modified") } // -- utility functions - pub fn get_timestamp(&self, property: &str) -> Option> { + fn get_timestamp(&self, property: &str) -> Option> { if let Some(ts) = self.taskmap.get(property) { if let Ok(ts) = ts.parse() { return Some(Utc.timestamp(ts, 0)); @@ -204,7 +274,7 @@ impl<'r> TaskMut<'r> { return Ok(()); } let k = format!("start.{}", Utc::now().timestamp()); - self.set_string(k.as_ref(), Some(String::from(""))) + self.set_string(k, Some(String::from(""))) } /// Stop the task by adding the current timestamp to all un-resolved "start." keys. @@ -224,6 +294,16 @@ impl<'r> TaskMut<'r> { Ok(()) } + /// Add a tag to this task. Does nothing if the tag is already present. + pub fn add_tag(&mut self, tag: &Tag) -> Fallible<()> { + self.set_string(format!("tag.{}", tag), Some("".to_owned())) + } + + /// Remove a tag from this task. Does nothing if the tag is not present. + pub fn remove_tag(&mut self, tag: &Tag) -> Fallible<()> { + self.set_string(format!("tag.{}", tag), None) + } + // -- utility functions fn lastmod(&mut self) -> Fallible<()> { @@ -238,17 +318,18 @@ impl<'r> TaskMut<'r> { Ok(()) } - fn set_string(&mut self, property: &str, value: Option) -> Fallible<()> { + fn set_string>(&mut self, property: S, value: Option) -> Fallible<()> { + let property = property.into(); self.lastmod()?; self.replica - .update_task(self.task.uuid, property, value.as_ref())?; + .update_task(self.task.uuid, &property, value.as_ref())?; if let Some(v) = value { trace!("task {}: set property {}={:?}", self.task.uuid, property, v); self.task.taskmap.insert(property.to_string(), v); } else { trace!("task {}: remove property {}", self.task.uuid, property); - self.task.taskmap.remove(property); + self.task.taskmap.remove(&property); } Ok(()) } @@ -297,6 +378,30 @@ mod test { f(task) } + #[test] + fn test_tag_from_str() { + let tag: Tag = "abc".try_into().unwrap(); + assert_eq!(tag, Tag("abc".to_owned())); + + let tag: Result = "".try_into(); + assert_eq!( + tag.unwrap_err().to_string(), + "tags must have at least one character" + ); + + let tag: Result = "999".try_into(); + assert_eq!( + tag.unwrap_err().to_string(), + "first character of a tag must be alphabetic" + ); + + let tag: Result = "abc!!".try_into(); + assert_eq!( + tag.unwrap_err().to_string(), + "characters of a tag after the first must be alphanumeric" + ); + } + #[test] fn test_is_active_never_started() { let task = Task::new(Uuid::new_v4(), TaskMap::new()); @@ -327,6 +432,55 @@ mod test { assert!(!task.is_active()); } + #[test] + fn test_has_tag() { + let task = Task::new( + Uuid::new_v4(), + vec![(String::from("tag.abc"), String::from(""))] + .drain(..) + .collect(), + ); + + assert!(task.has_tag(&"abc".try_into().unwrap())); + assert!(!task.has_tag(&"def".try_into().unwrap())); + } + + #[test] + fn test_get_tags() { + let task = Task::new( + Uuid::new_v4(), + vec![ + (String::from("tag.abc"), String::from("")), + (String::from("tag.def"), String::from("")), + ] + .drain(..) + .collect(), + ); + + let mut tags: Vec<_> = task.get_tags().collect(); + tags.sort(); + assert_eq!(tags, vec![Tag("abc".to_owned()), Tag("def".to_owned())]); + } + + #[test] + fn test_get_tags_invalid_tags() { + let task = Task::new( + Uuid::new_v4(), + vec![ + (String::from("tag.ok"), String::from("")), + (String::from("tag."), String::from("")), + (String::from("tag.123"), String::from("")), + (String::from("tag.a!!"), String::from("")), + ] + .drain(..) + .collect(), + ); + + // only "ok" is OK + let tags: Vec<_> = task.get_tags().collect(); + assert_eq!(tags, vec![Tag("ok".to_owned())]); + } + fn count_taskmap(task: &TaskMut, f: fn(&(&String, &String)) -> bool) -> usize { task.taskmap.iter().filter(f).count() } @@ -416,6 +570,34 @@ mod test { }); } + #[test] + fn test_add_tags() { + with_mut_task(|mut task| { + task.add_tag(&Tag("abc".to_owned())).unwrap(); + assert!(task.taskmap.contains_key("tag.abc")); + task.reload().unwrap(); + assert!(task.taskmap.contains_key("tag.abc")); + // redundant add has no effect.. + task.add_tag(&Tag("abc".to_owned())).unwrap(); + assert!(task.taskmap.contains_key("tag.abc")); + }); + } + + #[test] + fn test_remove_tags() { + with_mut_task(|mut task| { + task.add_tag(&Tag("abc".to_owned())).unwrap(); + task.reload().unwrap(); + assert!(task.taskmap.contains_key("tag.abc")); + + task.remove_tag(&Tag("abc".to_owned())).unwrap(); + assert!(!task.taskmap.contains_key("tag.abc")); + // redundant remove has no effect.. + task.remove_tag(&Tag("abc".to_owned())).unwrap(); + assert!(!task.taskmap.contains_key("tag.abc")); + }); + } + #[test] fn test_priority() { assert_eq!(Priority::L.to_taskmap(), "L"); From 54e8484bc2a2ae2da1ec445cfad4e03e63a01bef Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 21 Dec 2020 19:40:07 +0000 Subject: [PATCH 137/548] Support CLI syntax for changing tags --- cli/src/argparse/args.rs | 34 ++------------- cli/src/argparse/modification.rs | 71 ++++++++++++++++++++++++++++++-- cli/src/invocation/modify.rs | 13 ++++++ cli/src/macros.rs | 14 +++++++ 4 files changed, 97 insertions(+), 35 deletions(-) diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs index c59a4d730..d0fb9cc2d 100644 --- a/cli/src/argparse/args.rs +++ b/cli/src/argparse/args.rs @@ -58,7 +58,6 @@ pub(super) fn id_list(input: &str) -> IResult<&str, Vec<&str>> { } /// Recognizes a tag prefixed with `+` and returns the tag value -#[allow(dead_code)] // tags not implemented yet pub(super) fn plus_tag(input: &str) -> IResult<&str, &str> { fn to_tag(input: (char, &str)) -> Result<&str, ()> { Ok(input.1) @@ -70,7 +69,6 @@ pub(super) fn plus_tag(input: &str) -> IResult<&str, &str> { } /// Recognizes a tag prefixed with `-` and returns the tag value -#[allow(dead_code)] // tags not implemented yet pub(super) fn minus_tag(input: &str) -> IResult<&str, &str> { fn to_tag(input: (char, &str)) -> Result<&str, ()> { Ok(input.1) @@ -81,18 +79,6 @@ pub(super) fn minus_tag(input: &str) -> IResult<&str, &str> { )(input) } -/// Recognizes a tag prefixed with either `-` or `+`, returning true for + and false for - -#[allow(dead_code)] // tags not implemented yet -pub(super) fn tag(input: &str) -> IResult<&str, (bool, &str)> { - fn to_plus(input: &str) -> Result<(bool, &str), ()> { - Ok((true, input)) - } - fn to_minus(input: &str) -> Result<(bool, &str), ()> { - Ok((false, input)) - } - alt((map_res(plus_tag, to_plus), map_res(minus_tag, to_minus)))(input) -} - /// Consume a single argument from an argument list that matches the given string parser (one /// of the other functions in this module). The given parser must consume the entire input. pub(super) fn arg_matching<'a, O, F>(f: F) -> impl Fn(ArgList<'a>) -> IResult @@ -131,14 +117,10 @@ mod test { #[test] fn test_arg_matching() { assert_eq!( - arg_matching(tag)(argv!["+foo", "bar"]).unwrap(), - (argv!["bar"], (true, "foo")) + arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), + (argv!["bar"], "foo") ); - assert_eq!( - arg_matching(tag)(argv!["-foo", "bar"]).unwrap(), - (argv!["bar"], (false, "foo")) - ); - assert!(arg_matching(tag)(argv!["foo", "bar"]).is_err()); + assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); } #[test] @@ -161,16 +143,6 @@ mod test { assert!(minus_tag("-1abc").is_err()); } - #[test] - fn test_tag() { - assert_eq!(tag("-abc").unwrap().1, (false, "abc")); - assert_eq!(tag("+abc123").unwrap().1, (true, "abc123")); - assert!(tag("+abc123 --").is_err()); - assert!(tag("-abc123 ").is_err()); - assert!(tag(" -abc123").is_err()); - assert!(tag("-1abc").is_err()); - } - #[test] fn test_literal() { assert_eq!(literal("list")("list").unwrap().1, "list"); diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 6778ba30b..892e2ff5b 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -1,6 +1,7 @@ -use super::args::{any, arg_matching}; +use super::args::{any, arg_matching, minus_tag, plus_tag}; use super::ArgList; -use nom::{combinator::*, multi::fold_many0, IResult}; +use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; +use std::collections::HashSet; use taskchampion::Status; #[derive(Debug, PartialEq, Clone)] @@ -34,13 +35,21 @@ pub struct Modification { /// Set the status pub status: Option, - /// Set the "active" status, that is, start (true) or stop (false) the task. + /// Set the "active" state, that is, start (true) or stop (false) the task. pub active: Option, + + /// Add tags + pub add_tags: HashSet, + + /// Remove tags + pub remove_tags: HashSet, } /// A single argument that is part of a modification, used internally to this module enum ModArg<'a> { Description(&'a str), + PlusTag(&'a str), + MinusTag(&'a str), } impl Modification { @@ -55,11 +64,22 @@ impl Modification { acc.description = DescriptionMod::Set(description.to_string()); } } + ModArg::PlusTag(tag) => { + acc.add_tags.insert(tag.to_owned()); + } + ModArg::MinusTag(tag) => { + acc.remove_tags.insert(tag.to_owned()); + } } acc } fold_many0( - Self::description, + alt(( + Self::plus_tag, + Self::minus_tag, + // this must come last + Self::description, + )), Modification { ..Default::default() }, @@ -73,6 +93,20 @@ impl Modification { } map_res(arg_matching(any), to_modarg)(input) } + + fn plus_tag(input: ArgList) -> IResult { + fn to_modarg(input: &str) -> Result { + Ok(ModArg::PlusTag(input)) + } + map_res(arg_matching(plus_tag), to_modarg)(input) + } + + fn minus_tag(input: ArgList) -> IResult { + fn to_modarg(input: &str) -> Result { + Ok(ModArg::MinusTag(input)) + } + map_res(arg_matching(minus_tag), to_modarg)(input) + } } #[cfg(test)] @@ -104,6 +138,19 @@ mod test { ); } + #[test] + fn test_add_tags() { + let (input, modification) = Modification::parse(argv!["+abc", "+def"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + modification, + Modification { + add_tags: set!["abc".to_owned(), "def".to_owned()], + ..Default::default() + } + ); + } + #[test] fn test_multi_arg_description() { let (input, modification) = Modification::parse(argv!["new", "desc", "fun"]).unwrap(); @@ -116,4 +163,20 @@ mod test { } ); } + + #[test] + fn test_multi_arg_description_and_tags() { + let (input, modification) = + Modification::parse(argv!["new", "+next", "desc", "-daytime", "fun"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + modification, + Modification { + description: DescriptionMod::Set("new desc fun".to_owned()), + add_tags: set!["next".to_owned()], + remove_tags: set!["daytime".to_owned()], + ..Default::default() + } + ); + } } diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index 29802dbab..26e190e2c 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,5 +1,6 @@ use crate::argparse::{DescriptionMod, Modification}; use failure::Fallible; +use std::convert::TryInto; use taskchampion::TaskMut; use termcolor::WriteColor; @@ -32,6 +33,18 @@ pub(super) fn apply_modification( task.stop()?; } + for tag in modification.add_tags.iter() { + // note that the parser should have already ensured that this tag was valid + let tag = tag.try_into()?; + task.add_tag(&tag)?; + } + + for tag in modification.remove_tags.iter() { + // note that the parser should have already ensured that this tag was valid + let tag = tag.try_into()?; + task.remove_tag(&tag)?; + } + write!(w, "modified task {}\n", task.get_uuid())?; Ok(()) diff --git a/cli/src/macros.rs b/cli/src/macros.rs index 284a2f426..83a347dd0 100644 --- a/cli/src/macros.rs +++ b/cli/src/macros.rs @@ -10,3 +10,17 @@ macro_rules! argv { &[$($x),*][..] ); } + +/// Create a hashset, similar to vec! +#[cfg(test)] +macro_rules! set( + { $($key:expr),+ } => { + { + let mut s = ::std::collections::HashSet::new(); + $( + s.insert($key); + )+ + s + } + }; +); From 5ea72f878a13bf4bac53429f4bc436702ff61e25 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 21 Dec 2020 20:38:12 +0000 Subject: [PATCH 138/548] display tags in 'task info' --- cli/src/invocation/cmd/info.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index 852c8faa9..99f470eb4 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -30,6 +30,11 @@ pub(crate) fn execute( t.add_row(row![b->"Description", task.get_description()]); t.add_row(row![b->"Status", task.get_status()]); t.add_row(row![b->"Active", task.is_active()]); + let mut tags: Vec<_> = task.get_tags().map(|t| format!("+{}", t)).collect(); + if !tags.is_empty() { + tags.sort(); + t.add_row(row![b->"Tags", tags.join(" ")]); + } } t.print(w)?; } From a0568f017c9affb018f343e4ed7d6464b925d630 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 23 Dec 2020 03:39:38 +0000 Subject: [PATCH 139/548] Refactor filtering to start with a universe --- cli/src/argparse/args.rs | 125 ++++++++++----- cli/src/argparse/filter.rs | 107 ++++++++++--- cli/src/argparse/mod.rs | 3 +- cli/src/argparse/subcommand.rs | 37 +++-- cli/src/invocation/cmd/add.rs | 2 +- cli/src/invocation/cmd/gc.rs | 2 +- cli/src/invocation/cmd/help.rs | 2 +- cli/src/invocation/cmd/info.rs | 2 +- cli/src/invocation/cmd/list.rs | 2 +- cli/src/invocation/cmd/mod.rs | 3 - cli/src/invocation/cmd/modify.rs | 4 +- cli/src/invocation/cmd/sync.rs | 2 +- cli/src/invocation/cmd/version.rs | 2 +- cli/src/invocation/filter.rs | 217 ++++++++++++++++++++++++--- cli/src/invocation/mod.rs | 3 + cli/src/invocation/{cmd => }/test.rs | 0 16 files changed, 401 insertions(+), 112 deletions(-) rename cli/src/invocation/{cmd => }/test.rs (100%) diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs index d0fb9cc2d..edf6248a9 100644 --- a/cli/src/argparse/args.rs +++ b/cli/src/argparse/args.rs @@ -10,6 +10,20 @@ use nom::{ sequence::*, Err, IResult, }; +use taskchampion::Uuid; + +/// A task identifier, as given in a filter command-line expression +#[derive(Debug, PartialEq, Clone)] +pub(crate) enum TaskId { + /// A small integer identifying a working-set task + WorkingSetId(usize), + + /// A full Uuid specifically identifying a task + Uuid(Uuid), + + /// A prefix of a Uuid + PartialUuid(String), +} /// Recognizes any argument pub(super) fn any(input: &str) -> IResult<&str, &str> { @@ -21,38 +35,60 @@ pub(super) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, & move |input: &str| all_consuming(nomtag(literal))(input) } -/// Recognizes a comma-separated list of ID's (integers or UUID prefixes) -pub(super) fn id_list(input: &str) -> IResult<&str, Vec<&str>> { +/// Recognizes a comma-separated list of TaskIds +pub(super) fn id_list(input: &str) -> IResult<&str, Vec> { fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> { move |input: &str| recognize(many_m_n(n, n, one_of(&b"0123456789abcdefABCDEF"[..])))(input) } + fn uuid(input: &str) -> Result { + Ok(TaskId::Uuid(Uuid::parse_str(input).map_err(|_| ())?)) + } + fn partial_uuid(input: &str) -> Result { + Ok(TaskId::PartialUuid(input.to_owned())) + } + fn working_set_id(input: &str) -> Result { + Ok(TaskId::WorkingSetId(input.parse().map_err(|_| ())?)) + } all_consuming(separated_list1( char(','), alt(( - recognize(tuple(( - hex_n(8), - char('-'), - hex_n(4), - char('-'), - hex_n(4), - char('-'), - hex_n(4), - char('-'), - hex_n(12), - ))), - recognize(tuple(( - hex_n(8), - char('-'), - hex_n(4), - char('-'), - hex_n(4), - char('-'), - hex_n(4), - ))), - recognize(tuple((hex_n(8), char('-'), hex_n(4), char('-'), hex_n(4)))), - recognize(tuple((hex_n(8), char('-'), hex_n(4)))), - hex_n(8), - digit1, + map_res( + recognize(tuple(( + hex_n(8), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + char('-'), + hex_n(12), + ))), + uuid, + ), + map_res( + recognize(tuple(( + hex_n(8), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + ))), + partial_uuid, + ), + map_res( + recognize(tuple((hex_n(8), char('-'), hex_n(4), char('-'), hex_n(4)))), + partial_uuid, + ), + map_res( + recognize(tuple((hex_n(8), char('-'), hex_n(4)))), + partial_uuid, + ), + map_res(hex_n(8), partial_uuid), + // note that an 8-decimal-digit value will be treated as a UUID + map_res(digit1, working_set_id), )), ))(input) } @@ -154,29 +190,40 @@ mod test { #[test] fn test_id_list_single() { - assert_eq!(id_list("123").unwrap().1, vec!["123".to_owned()]); + assert_eq!(id_list("123").unwrap().1, vec![TaskId::WorkingSetId(123)]); } #[test] fn test_id_list_uuids() { - assert_eq!(id_list("12341234").unwrap().1, vec!["12341234".to_owned()]); - assert_eq!(id_list("1234abcd").unwrap().1, vec!["1234abcd".to_owned()]); - assert_eq!(id_list("abcd1234").unwrap().1, vec!["abcd1234".to_owned()]); + assert_eq!( + id_list("12341234").unwrap().1, + vec![TaskId::PartialUuid("12341234".to_owned())] + ); + assert_eq!( + id_list("1234abcd").unwrap().1, + vec![TaskId::PartialUuid("1234abcd".to_owned())] + ); + assert_eq!( + id_list("abcd1234").unwrap().1, + vec![TaskId::PartialUuid("abcd1234".to_owned())] + ); assert_eq!( id_list("abcd1234-1234").unwrap().1, - vec!["abcd1234-1234".to_owned()] + vec![TaskId::PartialUuid("abcd1234-1234".to_owned())] ); assert_eq!( id_list("abcd1234-1234-2345").unwrap().1, - vec!["abcd1234-1234-2345".to_owned()] + vec![TaskId::PartialUuid("abcd1234-1234-2345".to_owned())] ); assert_eq!( id_list("abcd1234-1234-2345-3456").unwrap().1, - vec!["abcd1234-1234-2345-3456".to_owned()] + vec![TaskId::PartialUuid("abcd1234-1234-2345-3456".to_owned())] ); assert_eq!( id_list("abcd1234-1234-2345-3456-0123456789ab").unwrap().1, - vec!["abcd1234-1234-2345-3456-0123456789ab".to_owned()] + vec![TaskId::Uuid( + Uuid::parse_str("abcd1234-1234-2345-3456-0123456789ab").unwrap() + )] ); } @@ -194,11 +241,11 @@ mod test { #[test] fn test_id_list_uuids_mixed() { assert_eq!(id_list("abcd1234,abcd1234-1234,abcd1234-1234-2345,abcd1234-1234-2345-3456,abcd1234-1234-2345-3456-0123456789ab").unwrap().1, - vec!["abcd1234".to_owned(), - "abcd1234-1234".to_owned(), - "abcd1234-1234-2345".to_owned(), - "abcd1234-1234-2345-3456".to_owned(), - "abcd1234-1234-2345-3456-0123456789ab".to_owned(), + vec![TaskId::PartialUuid("abcd1234".to_owned()), + TaskId::PartialUuid("abcd1234-1234".to_owned()), + TaskId::PartialUuid("abcd1234-1234-2345".to_owned()), + TaskId::PartialUuid("abcd1234-1234-2345-3456".to_owned()), + TaskId::Uuid(Uuid::parse_str("abcd1234-1234-2345-3456-0123456789ab").unwrap()), ]); } } diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 24e0519eb..07b2b3fd3 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -1,48 +1,80 @@ -use super::args::{arg_matching, id_list}; +use super::args::{arg_matching, id_list, TaskId}; use super::ArgList; use nom::{combinator::*, multi::fold_many0, IResult}; /// A filter represents a selection of a particular set of tasks. +/// +/// A filter has a "universe" of tasks that might match, and a list of conditions +/// all of which tasks must match. The universe can be a set of task IDs, or just +/// pending tasks, or all tasks. #[derive(Debug, PartialEq, Default, Clone)] pub(crate) struct Filter { /// A list of numeric IDs or prefixes of UUIDs - pub(crate) id_list: Option>, + pub(crate) universe: Universe, } +/// The universe of tasks over which a filter should be applied. +#[derive(Debug, PartialEq, Clone)] +pub(crate) enum Universe { + /// Only the identified tasks. Note that this may contain duplicates. + IdList(Vec), + /// All tasks in the task database + AllTasks, + /// Only pending tasks (or as an approximation, the working set) + #[allow(dead_code)] // currently only used in tests + PendingTasks, +} + +impl Universe { + /// Testing shorthand to construct a simple universe + #[cfg(test)] + pub(super) fn for_ids(mut ids: Vec) -> Self { + Universe::IdList(ids.drain(..).map(|id| TaskId::WorkingSetId(id)).collect()) + } +} + +impl Default for Universe { + fn default() -> Self { + Self::AllTasks + } +} + +/// Internal struct representing a parsed filter argument enum FilterArg { - IdList(Vec), + IdList(Vec), } impl Filter { pub(super) fn parse(input: ArgList) -> IResult { - fn fold(mut acc: Filter, mod_arg: FilterArg) -> Filter { - match mod_arg { - FilterArg::IdList(mut id_list) => { - if let Some(ref mut existing) = acc.id_list { - // given multiple ID lists, concatenate them to represent - // an "OR" between them. - existing.append(&mut id_list); - } else { - acc.id_list = Some(id_list); - } - } - } - acc - } fold_many0( Self::id_list, Filter { ..Default::default() }, - fold, + Self::fold_args, )(input) } + /// fold multiple filter args into a single Filter instance + fn fold_args(mut acc: Filter, mod_arg: FilterArg) -> Filter { + match mod_arg { + FilterArg::IdList(mut id_list) => { + // If any IDs are specified, then the filter's universe + // is those IDs. If there are already IDs, append to the + // list. + if let Universe::IdList(ref mut existing) = acc.universe { + existing.append(&mut id_list); + } else { + acc.universe = Universe::IdList(id_list); + } + } + } + acc + } + fn id_list(input: ArgList) -> IResult { - fn to_filterarg(mut input: Vec<&str>) -> Result { - Ok(FilterArg::IdList( - input.drain(..).map(str::to_owned).collect(), - )) + fn to_filterarg(input: Vec) -> Result { + Ok(FilterArg::IdList(input)) } map_res(arg_matching(id_list), to_filterarg)(input) } @@ -71,7 +103,7 @@ mod test { assert_eq!( filter, Filter { - id_list: Some(vec!["1".to_owned()]), + universe: Universe::IdList(vec![TaskId::WorkingSetId(1)]), ..Default::default() } ); @@ -84,7 +116,29 @@ mod test { assert_eq!( filter, Filter { - id_list: Some(vec!["1".to_owned(), "2".to_owned(), "3".to_owned()]), + universe: Universe::IdList(vec![ + TaskId::WorkingSetId(1), + TaskId::WorkingSetId(2), + TaskId::WorkingSetId(3), + ]), + ..Default::default() + } + ); + } + + #[test] + fn test_id_list_multi_arg() { + let (input, filter) = Filter::parse(argv!["1,2", "3,4"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + filter, + Filter { + universe: Universe::IdList(vec![ + TaskId::WorkingSetId(1), + TaskId::WorkingSetId(2), + TaskId::WorkingSetId(3), + TaskId::WorkingSetId(4), + ]), ..Default::default() } ); @@ -97,7 +151,10 @@ mod test { assert_eq!( filter, Filter { - id_list: Some(vec!["1".to_owned(), "abcd1234".to_owned()]), + universe: Universe::IdList(vec![ + TaskId::WorkingSetId(1), + TaskId::PartialUuid("abcd1234".to_owned()), + ]), ..Default::default() } ); diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index 428f7bb0a..ad757b892 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -18,8 +18,9 @@ mod modification; mod report; mod subcommand; +pub(crate) use args::TaskId; pub(crate) use command::Command; -pub(crate) use filter::Filter; +pub(crate) use filter::{Filter, Universe}; pub(crate) use modification::{DescriptionMod, Modification}; pub(crate) use report::Report; pub(crate) use subcommand::Subcommand; diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 2ebf60644..af4f6fe36 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -387,6 +387,7 @@ impl Sync { #[cfg(test)] mod test { use super::*; + use crate::argparse::Universe; const EMPTY: Vec<&str> = vec![]; @@ -462,7 +463,8 @@ mod test { fn test_modify_description_multi() { let subcommand = Subcommand::Modify { filter: Filter { - id_list: Some(vec!["123".to_owned()]), + universe: Universe::for_ids(vec![123]), + ..Default::default() }, modification: Modification { description: DescriptionMod::Set("foo bar".to_owned()), @@ -479,7 +481,8 @@ mod test { fn test_append() { let subcommand = Subcommand::Modify { filter: Filter { - id_list: Some(vec!["123".to_owned()]), + universe: Universe::for_ids(vec![123]), + ..Default::default() }, modification: Modification { description: DescriptionMod::Append("foo bar".to_owned()), @@ -496,7 +499,8 @@ mod test { fn test_prepend() { let subcommand = Subcommand::Modify { filter: Filter { - id_list: Some(vec!["123".to_owned()]), + universe: Universe::for_ids(vec![123]), + ..Default::default() }, modification: Modification { description: DescriptionMod::Prepend("foo bar".to_owned()), @@ -513,7 +517,8 @@ mod test { fn test_done() { let subcommand = Subcommand::Modify { filter: Filter { - id_list: Some(vec!["123".to_owned()]), + universe: Universe::for_ids(vec![123]), + ..Default::default() }, modification: Modification { status: Some(Status::Completed), @@ -530,7 +535,8 @@ mod test { fn test_done_modify() { let subcommand = Subcommand::Modify { filter: Filter { - id_list: Some(vec!["123".to_owned()]), + universe: Universe::for_ids(vec![123]), + ..Default::default() }, modification: Modification { description: DescriptionMod::Set("now-finished".to_owned()), @@ -548,7 +554,8 @@ mod test { fn test_start() { let subcommand = Subcommand::Modify { filter: Filter { - id_list: Some(vec!["123".to_owned()]), + universe: Universe::for_ids(vec![123]), + ..Default::default() }, modification: Modification { active: Some(true), @@ -565,7 +572,8 @@ mod test { fn test_start_modify() { let subcommand = Subcommand::Modify { filter: Filter { - id_list: Some(vec!["123".to_owned()]), + universe: Universe::for_ids(vec![123]), + ..Default::default() }, modification: Modification { active: Some(true), @@ -583,7 +591,8 @@ mod test { fn test_stop() { let subcommand = Subcommand::Modify { filter: Filter { - id_list: Some(vec!["123".to_owned()]), + universe: Universe::for_ids(vec![123]), + ..Default::default() }, modification: Modification { active: Some(false), @@ -600,7 +609,8 @@ mod test { fn test_stop_modify() { let subcommand = Subcommand::Modify { filter: Filter { - id_list: Some(vec!["123".to_owned()]), + universe: Universe::for_ids(vec![123]), + ..Default::default() }, modification: Modification { description: DescriptionMod::Set("mod".to_owned()), @@ -632,7 +642,8 @@ mod test { let subcommand = Subcommand::List { report: Report { filter: Filter { - id_list: Some(vec!["12".to_owned(), "13".to_owned()]), + universe: Universe::for_ids(vec![12, 13]), + ..Default::default() }, }, }; @@ -647,7 +658,8 @@ mod test { let subcommand = Subcommand::Info { debug: false, filter: Filter { - id_list: Some(vec!["12".to_owned(), "13".to_owned()]), + universe: Universe::for_ids(vec![12, 13]), + ..Default::default() }, }; assert_eq!( @@ -661,7 +673,8 @@ mod test { let subcommand = Subcommand::Info { debug: true, filter: Filter { - id_list: Some(vec!["12".to_owned()]), + universe: Universe::for_ids(vec![12]), + ..Default::default() }, }; assert_eq!( diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index 79cb5dd4b..e3054992d 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -20,7 +20,7 @@ pub(crate) fn execute( #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::*; + use crate::invocation::test::*; #[test] fn test_add() { diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs index 644974eb5..1aa9e2161 100644 --- a/cli/src/invocation/cmd/gc.rs +++ b/cli/src/invocation/cmd/gc.rs @@ -11,7 +11,7 @@ pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Fallib #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::*; + use crate::invocation::test::*; #[test] fn test_gc() { diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index a95e8415f..1aaba3ed8 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -15,7 +15,7 @@ pub(crate) fn execute( #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::*; + use crate::invocation::test::*; #[test] fn test_summary() { diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index 99f470eb4..f30e4a8fd 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -45,7 +45,7 @@ pub(crate) fn execute( #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::*; + use crate::invocation::test::*; use taskchampion::Status; #[test] diff --git a/cli/src/invocation/cmd/list.rs b/cli/src/invocation/cmd/list.rs index 4afcdab93..2905a9c19 100644 --- a/cli/src/invocation/cmd/list.rs +++ b/cli/src/invocation/cmd/list.rs @@ -34,7 +34,7 @@ pub(crate) fn execute( mod test { use super::*; use crate::argparse::Filter; - use crate::invocation::cmd::test::*; + use crate::invocation::test::*; use taskchampion::Status; #[test] diff --git a/cli/src/invocation/cmd/mod.rs b/cli/src/invocation/cmd/mod.rs index 1637968ef..43050215b 100644 --- a/cli/src/invocation/cmd/mod.rs +++ b/cli/src/invocation/cmd/mod.rs @@ -8,6 +8,3 @@ pub(crate) mod list; pub(crate) mod modify; pub(crate) mod sync; pub(crate) mod version; - -#[cfg(test)] -mod test; diff --git a/cli/src/invocation/cmd/modify.rs b/cli/src/invocation/cmd/modify.rs index dadcd9319..b935f93af 100644 --- a/cli/src/invocation/cmd/modify.rs +++ b/cli/src/invocation/cmd/modify.rs @@ -23,8 +23,8 @@ pub(crate) fn execute( mod test { use super::*; use crate::argparse::DescriptionMod; - use crate::invocation::cmd::test::test_replica; - use crate::invocation::cmd::test::*; + use crate::invocation::test::test_replica; + use crate::invocation::test::*; use taskchampion::Status; #[test] diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index 737406ae9..6115b4822 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -15,7 +15,7 @@ pub(crate) fn execute( #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::*; + use crate::invocation::test::*; use tempdir::TempDir; #[test] diff --git a/cli/src/invocation/cmd/version.rs b/cli/src/invocation/cmd/version.rs index 8a74a7681..5c8c7577e 100644 --- a/cli/src/invocation/cmd/version.rs +++ b/cli/src/invocation/cmd/version.rs @@ -12,7 +12,7 @@ pub(crate) fn execute(w: &mut W) -> Fallible<()> { #[cfg(test)] mod test { use super::*; - use crate::invocation::cmd::test::*; + use crate::invocation::test::*; #[test] fn test_version() { diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index b5814dbc4..ae9422627 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -1,38 +1,209 @@ -use crate::argparse::Filter; +use crate::argparse::{Filter, TaskId, Universe}; use failure::Fallible; +use std::collections::HashSet; use taskchampion::{Replica, Task}; -/// Return the tasks matching the given filter. +fn match_task(_filter: &Filter, _task: &Task) -> bool { + // TODO: at the moment, only filtering by Universe is supported + true +} + +/// Return the tasks matching the given filter. This will return each matching +/// task once, even if the user specified the same task multiple times on the +/// command line. pub(super) fn filtered_tasks( replica: &mut Replica, filter: &Filter, ) -> Fallible> { - // For the moment, this gets the entire set of tasks and then iterates - // over the result. A few optimizations are possible: - // - // - id_list could be better parsed (id, uuid-fragment, uuid) in argparse - // - depending on the nature of the filter, we could just scan the working set - // - we could produce the tasks on-demand (but at the cost of holding a ref - // to the replica, preventing modifying tasks..) let mut res = vec![]; - 'task: for (uuid, task) in replica.all_tasks()?.drain() { - if let Some(ref ids) = filter.id_list { - for id in ids { - if let Ok(index) = id.parse::() { - if replica.get_working_set_index(&uuid)? == Some(index) { - res.push(task); - continue 'task; + + fn is_partial_uuid(taskid: &TaskId) -> bool { + match taskid { + TaskId::PartialUuid(_) => true, + _ => false, + } + } + + // We will enumerate the universe of tasks for this filter, checking + // each resulting task with match_task + match filter.universe { + // A list of IDs, but some are partial so we need to iterate over + // all tasks and pattern-match their Uuids + Universe::IdList(ref ids) if ids.iter().any(is_partial_uuid) => { + 'task: for (uuid, task) in replica.all_tasks()?.drain() { + for id in ids { + if match id { + TaskId::WorkingSetId(id) => { + // NOTE: (#108) this results in many reads of the working set; it + // may be better to cache this information here or in the Replica. + replica.get_working_set_index(&uuid)? == Some(*id) + } + TaskId::PartialUuid(prefix) => uuid.to_string().starts_with(prefix), + TaskId::Uuid(id) => id == &uuid, + } { + if match_task(filter, &task) { + res.push(task); + continue 'task; + } + } + } + } + } + + // A list of full IDs, which we can fetch directly + Universe::IdList(ref ids) => { + // this is the only case where we might accidentally return the same task + // several times, so we must track the seen tasks. + let mut seen = HashSet::new(); + for id in ids { + let task = match id { + TaskId::WorkingSetId(id) => replica.get_working_set_task(*id)?, + TaskId::PartialUuid(_) => unreachable!(), // handled above + TaskId::Uuid(id) => replica.get_task(id)?, + }; + + if let Some(task) = task { + // if we have already seen this task, skip ahead.. + let uuid = *task.get_uuid(); + if seen.contains(&uuid) { + continue; + } + seen.insert(uuid); + + if match_task(filter, &task) { + res.push(task); + } + } + } + } + + // All tasks -- iterate over the full set + Universe::AllTasks => { + for (_, task) in replica.all_tasks()?.drain() { + if match_task(filter, &task) { + res.push(task); + } + } + } + + // Pending tasks -- just scan the working set + Universe::PendingTasks => { + for task in replica.working_set()?.drain(..) { + if let Some(task) = task { + if match_task(filter, &task) { + res.push(task); } - } else if uuid.to_string().starts_with(id) { - res.push(task); - continue 'task; } } - } else { - // default to returning all tasks - res.push(task); - continue 'task; } } Ok(res.into_iter()) } + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::test::*; + use taskchampion::Status; + + #[test] + fn exact_ids() { + let mut replica = test_replica(); + + let t1 = replica.new_task(Status::Pending, "A".to_owned()).unwrap(); + let t2 = replica.new_task(Status::Completed, "B".to_owned()).unwrap(); + let _t = replica.new_task(Status::Pending, "C".to_owned()).unwrap(); + replica.gc().unwrap(); + + let t1uuid = *t1.get_uuid(); + + let filter = Filter { + universe: Universe::IdList(vec![ + TaskId::Uuid(t1uuid), // A + TaskId::WorkingSetId(1), // A (again, dups filtered) + TaskId::Uuid(*t2.get_uuid()), // B + ]), + ..Default::default() + }; + let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) + .unwrap() + .map(|t| t.get_description().to_owned()) + .collect(); + filtered.sort(); + assert_eq!(vec!["A".to_owned(), "B".to_owned()], filtered); + } + + #[test] + fn partial_ids() { + let mut replica = test_replica(); + + let t1 = replica.new_task(Status::Pending, "A".to_owned()).unwrap(); + let t2 = replica.new_task(Status::Completed, "B".to_owned()).unwrap(); + let _t = replica.new_task(Status::Pending, "C".to_owned()).unwrap(); + replica.gc().unwrap(); + + let t1uuid = *t1.get_uuid(); + let t2uuid = t2.get_uuid().to_string(); + let t2partial = t2uuid[..13].to_owned(); + + let filter = Filter { + universe: Universe::IdList(vec![ + TaskId::Uuid(t1uuid), // A + TaskId::WorkingSetId(1), // A (again, dups filtered) + TaskId::PartialUuid(t2partial), // B + ]), + ..Default::default() + }; + let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) + .unwrap() + .map(|t| t.get_description().to_owned()) + .collect(); + filtered.sort(); + assert_eq!(vec!["A".to_owned(), "B".to_owned()], filtered); + } + + #[test] + fn all_tasks() { + let mut replica = test_replica(); + + replica.new_task(Status::Pending, "A".to_owned()).unwrap(); + replica.new_task(Status::Completed, "B".to_owned()).unwrap(); + replica.new_task(Status::Deleted, "C".to_owned()).unwrap(); + replica.gc().unwrap(); + + let filter = Filter { + universe: Universe::AllTasks, + ..Default::default() + }; + let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) + .unwrap() + .map(|t| t.get_description().to_owned()) + .collect(); + filtered.sort(); + assert_eq!( + vec!["A".to_owned(), "B".to_owned(), "C".to_owned()], + filtered + ); + } + + #[test] + fn pending_tasks() { + let mut replica = test_replica(); + + replica.new_task(Status::Pending, "A".to_owned()).unwrap(); + replica.new_task(Status::Completed, "B".to_owned()).unwrap(); + replica.new_task(Status::Deleted, "C".to_owned()).unwrap(); + replica.gc().unwrap(); + + let filter = Filter { + universe: Universe::PendingTasks, + ..Default::default() + }; + let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) + .unwrap() + .map(|t| t.get_description().to_owned()) + .collect(); + filtered.sort(); + assert_eq!(vec!["A".to_owned()], filtered); + } +} diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 18d8000b6..760be3964 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -10,6 +10,9 @@ mod cmd; mod filter; mod modify; +#[cfg(test)] +mod test; + use filter::filtered_tasks; use modify::apply_modification; diff --git a/cli/src/invocation/cmd/test.rs b/cli/src/invocation/test.rs similarity index 100% rename from cli/src/invocation/cmd/test.rs rename to cli/src/invocation/test.rs From 9c94a7b7530477181163a3e7d27c12daae4b45d3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 24 Dec 2020 16:38:08 +0000 Subject: [PATCH 140/548] support filtering by tags --- cli/src/argparse/filter.rs | 57 +++++++++++++++++++++-- cli/src/argparse/mod.rs | 2 +- cli/src/invocation/filter.rs | 88 ++++++++++++++++++++++++++++++++++-- cli/src/invocation/modify.rs | 6 +-- 4 files changed, 140 insertions(+), 13 deletions(-) diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 07b2b3fd3..1ff7a7d66 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -1,6 +1,6 @@ -use super::args::{arg_matching, id_list, TaskId}; +use super::args::{arg_matching, id_list, minus_tag, plus_tag, TaskId}; use super::ArgList; -use nom::{combinator::*, multi::fold_many0, IResult}; +use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; /// A filter represents a selection of a particular set of tasks. /// @@ -9,8 +9,12 @@ use nom::{combinator::*, multi::fold_many0, IResult}; /// pending tasks, or all tasks. #[derive(Debug, PartialEq, Default, Clone)] pub(crate) struct Filter { - /// A list of numeric IDs or prefixes of UUIDs + /// The universe of tasks from which this filter can select pub(crate) universe: Universe, + + /// A set of filter conditions, all of which must match a task in order for that task to be + /// selected. + pub(crate) conditions: Vec, } /// The universe of tasks over which a filter should be applied. @@ -39,15 +43,26 @@ impl Default for Universe { } } +/// A condition which tasks must match to be accepted by the filter. +#[derive(Debug, PartialEq, Clone)] +pub(crate) enum Condition { + /// Task has the given tag + HasTag(String), + + /// Task does not have the given tag + NoTag(String), +} + /// Internal struct representing a parsed filter argument enum FilterArg { IdList(Vec), + Condition(Condition), } impl Filter { pub(super) fn parse(input: ArgList) -> IResult { fold_many0( - Self::id_list, + alt((Self::id_list, Self::plus_tag, Self::minus_tag)), Filter { ..Default::default() }, @@ -68,6 +83,9 @@ impl Filter { acc.universe = Universe::IdList(id_list); } } + FilterArg::Condition(cond) => { + acc.conditions.push(cond); + } } acc } @@ -78,6 +96,20 @@ impl Filter { } map_res(arg_matching(id_list), to_filterarg)(input) } + + fn plus_tag(input: ArgList) -> IResult { + fn to_filterarg(input: &str) -> Result { + Ok(FilterArg::Condition(Condition::HasTag(input.to_owned()))) + } + map_res(arg_matching(plus_tag), to_filterarg)(input) + } + + fn minus_tag(input: ArgList) -> IResult { + fn to_filterarg(input: &str) -> Result { + Ok(FilterArg::Condition(Condition::NoTag(input.to_owned()))) + } + map_res(arg_matching(minus_tag), to_filterarg)(input) + } } #[cfg(test)] @@ -159,4 +191,21 @@ mod test { } ); } + + #[test] + fn test_tags() { + let (input, filter) = Filter::parse(argv!["1", "+yes", "-no"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + filter, + Filter { + universe: Universe::IdList(vec![TaskId::WorkingSetId(1),]), + conditions: vec![ + Condition::HasTag("yes".into()), + Condition::NoTag("no".into()), + ], + ..Default::default() + } + ); + } } diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index ad757b892..b398a6c93 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -20,7 +20,7 @@ mod subcommand; pub(crate) use args::TaskId; pub(crate) use command::Command; -pub(crate) use filter::{Filter, Universe}; +pub(crate) use filter::{Condition, Filter, Universe}; pub(crate) use modification::{DescriptionMod, Modification}; pub(crate) use report::Report; pub(crate) use subcommand::Subcommand; diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index ae9422627..e6eacc9fc 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -1,10 +1,28 @@ -use crate::argparse::{Filter, TaskId, Universe}; +use crate::argparse::{Condition, Filter, TaskId, Universe}; use failure::Fallible; use std::collections::HashSet; -use taskchampion::{Replica, Task}; +use std::convert::TryInto; +use taskchampion::{Replica, Tag, Task}; -fn match_task(_filter: &Filter, _task: &Task) -> bool { - // TODO: at the moment, only filtering by Universe is supported +fn match_task(filter: &Filter, task: &Task) -> bool { + for cond in &filter.conditions { + match cond { + Condition::HasTag(ref tag) => { + // see #111 for the unwrap + let tag: Tag = tag.try_into().unwrap(); + if !task.has_tag(&tag) { + return false; + } + } + Condition::NoTag(ref tag) => { + // see #111 for the unwrap + let tag: Tag = tag.try_into().unwrap(); + if task.has_tag(&tag) { + return false; + } + } + } + } true } @@ -186,6 +204,68 @@ mod test { ); } + #[test] + fn tag_filtering() -> Fallible<()> { + let mut replica = test_replica(); + let yes: Tag = "yes".try_into()?; + let no: Tag = "no".try_into()?; + + let mut t1 = replica + .new_task(Status::Pending, "A".to_owned())? + .into_mut(&mut replica); + t1.add_tag(&yes)?; + let mut t2 = replica + .new_task(Status::Pending, "B".to_owned())? + .into_mut(&mut replica); + t2.add_tag(&yes)?; + t2.add_tag(&no)?; + let mut t3 = replica + .new_task(Status::Pending, "C".to_owned())? + .into_mut(&mut replica); + t3.add_tag(&no)?; + let _t4 = replica.new_task(Status::Pending, "D".to_owned())?; + + // look for just "yes" (A and B) + let filter = Filter { + universe: Universe::AllTasks, + conditions: vec![Condition::HasTag("yes".to_owned())], + ..Default::default() + }; + let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? + .map(|t| t.get_description().to_owned()) + .collect(); + filtered.sort(); + assert_eq!(vec!["A".to_owned(), "B".to_owned()], filtered); + + // look for tags without "no" (A, D) + let filter = Filter { + universe: Universe::AllTasks, + conditions: vec![Condition::NoTag("no".to_owned())], + ..Default::default() + }; + let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? + .map(|t| t.get_description().to_owned()) + .collect(); + filtered.sort(); + assert_eq!(vec!["A".to_owned(), "D".to_owned()], filtered); + + // look for tags with "yes" and "no" (B) + let filter = Filter { + universe: Universe::AllTasks, + conditions: vec![ + Condition::HasTag("yes".to_owned()), + Condition::HasTag("no".to_owned()), + ], + ..Default::default() + }; + let filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? + .map(|t| t.get_description().to_owned()) + .collect(); + assert_eq!(vec!["B".to_owned()], filtered); + + Ok(()) + } + #[test] fn pending_tasks() { let mut replica = test_replica(); diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index 26e190e2c..c3923f0ee 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -34,14 +34,12 @@ pub(super) fn apply_modification( } for tag in modification.add_tags.iter() { - // note that the parser should have already ensured that this tag was valid - let tag = tag.try_into()?; + let tag = tag.try_into()?; // see #111 task.add_tag(&tag)?; } for tag in modification.remove_tags.iter() { - // note that the parser should have already ensured that this tag was valid - let tag = tag.try_into()?; + let tag = tag.try_into()?; // see #111 task.remove_tag(&tag)?; } From 8c9e240e97ad125d82a21f2c0b8648cca5f071c3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 24 Dec 2020 17:04:51 +0000 Subject: [PATCH 141/548] Document filter and modification syntax --- cli/src/argparse/filter.rs | 32 ++++++++++++++ cli/src/argparse/mod.rs | 2 + cli/src/argparse/modification.rs | 30 +++++++++++++ cli/src/argparse/subcommand.rs | 4 +- cli/src/usage.rs | 75 ++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 2 deletions(-) diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 1ff7a7d66..66845e54b 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -1,6 +1,8 @@ use super::args::{arg_matching, id_list, minus_tag, plus_tag, TaskId}; use super::ArgList; +use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; +use textwrap::dedent; /// A filter represents a selection of a particular set of tasks. /// @@ -110,6 +112,36 @@ impl Filter { } map_res(arg_matching(minus_tag), to_filterarg)(input) } + + pub(super) fn get_usage(u: &mut usage::Usage) { + u.filters.push(usage::Filter { + syntax: "TASKID[,TASKID,..]".to_owned(), + summary: "Specific tasks".to_owned(), + description: dedent( + " + Select only specific tasks. Multiple tasks can be specified either separated by + commas or as separate arguments. Each task may be specfied by its working-set + index (a small number) or by its UUID. Prefixes of UUIDs broken at hyphens are + also supported, such as `b5664ef8-423d` or `b5664ef8`.", + ), + }); + u.filters.push(usage::Filter { + syntax: "+TAG".to_owned(), + summary: "Tagged tasks".to_owned(), + description: dedent( + " + Select tasks with the given tag.", + ), + }); + u.filters.push(usage::Filter { + syntax: "-TAG".to_owned(), + summary: "Un-tagged tasks".to_owned(), + description: dedent( + " + Select tasks that do not have the given tag.", + ), + }); + } } #[cfg(test)] diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index b398a6c93..a6d8c72bd 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -31,4 +31,6 @@ type ArgList<'a> = &'a [&'a str]; pub(crate) fn get_usage(usage: &mut Usage) { Subcommand::get_usage(usage); + Filter::get_usage(usage); + Modification::get_usage(usage); } diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 892e2ff5b..1c46856a2 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -1,8 +1,10 @@ use super::args::{any, arg_matching, minus_tag, plus_tag}; use super::ArgList; +use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use std::collections::HashSet; use taskchampion::Status; +use textwrap::dedent; #[derive(Debug, PartialEq, Clone)] pub enum DescriptionMod { @@ -107,6 +109,34 @@ impl Modification { } map_res(arg_matching(minus_tag), to_modarg)(input) } + + pub(super) fn get_usage(u: &mut usage::Usage) { + u.modifications.push(usage::Modification { + syntax: "DESCRIPTION".to_owned(), + summary: "Set description".to_owned(), + description: dedent( + " + Set the task description. Multiple arguments are combined into a single + space-separated description.", + ), + }); + u.modifications.push(usage::Modification { + syntax: "+TAG".to_owned(), + summary: "Tag task".to_owned(), + description: dedent( + " + Add the given tag to the task.", + ), + }); + u.modifications.push(usage::Modification { + syntax: "-TAG".to_owned(), + summary: "Un-tag task".to_owned(), + description: dedent( + " + Remove the given tag from the task.", + ), + }); + } } #[cfg(test)] diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index af4f6fe36..6db98105e 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -243,7 +243,7 @@ impl Modify { }); u.subcommands.push(usage::Subcommand { name: "stop".to_owned(), - syntax: "[filter] start [modification]".to_owned(), + syntax: "[filter] stop [modification]".to_owned(), summary: "Stop tasks".to_owned(), description: dedent( " @@ -252,7 +252,7 @@ impl Modify { }); u.subcommands.push(usage::Subcommand { name: "done".to_owned(), - syntax: "[filter] start [modification]".to_owned(), + syntax: "[filter] done [modification]".to_owned(), summary: "Mark tasks as completed".to_owned(), description: dedent( " diff --git a/cli/src/usage.rs b/cli/src/usage.rs index d1b36d59d..d91b449cc 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -9,6 +9,8 @@ use textwrap::indent; #[derive(Debug, Default)] pub(crate) struct Usage { pub(crate) subcommands: Vec, + pub(crate) filters: Vec, + pub(crate) modifications: Vec, } impl Usage { @@ -43,6 +45,18 @@ impl Usage { for subcommand in self.subcommands.iter() { subcommand.write_help(&mut w, summary)?; } + write!(w, "Filter Expressions:\n\n")?; + write!(w, "Where [filter] appears above, zero or more of the following arguments can be used to limit\n")?; + write!(w, "the tasks concerned.\n\n")?; + for filter in self.filters.iter() { + filter.write_help(&mut w, summary)?; + } + write!(w, "Modifications:\n\n")?; + write!(w, "Where [modification] appears above, zero or more of the following arguments can be used\n")?; + write!(w, "to modify the selected tasks.\n\n")?; + for modification in self.modifications.iter() { + modification.write_help(&mut w, summary)?; + } if !summary { write!(w, "\nSee `task help` for more detail\n")?; } @@ -50,6 +64,7 @@ impl Usage { } } +/// Usage documentation for a subcommand #[derive(Debug, Default)] pub(crate) struct Subcommand { /// Name of the subcommand @@ -81,3 +96,63 @@ impl Subcommand { Ok(()) } } + +/// Usage documentation for a filter argument +#[derive(Debug, Default)] +pub(crate) struct Filter { + /// Syntax summary + pub(crate) syntax: String, + + /// One-line description of the filter. Use all-caps words for placeholders. + pub(crate) summary: String, + + /// Multi-line description of the filter. It's OK for this to duplicate summary, as the + /// two are not displayed together. + pub(crate) description: String, +} + +impl Filter { + fn write_help(&self, mut w: W, summary: bool) -> Result<()> { + if summary { + write!(w, " {} - {}\n", self.syntax, self.summary)?; + } else { + write!( + w, + " {}\n{}\n", + self.syntax, + indent(self.description.trim(), " ") + )?; + } + Ok(()) + } +} + +/// Usage documentation for a modification argument +#[derive(Debug, Default)] +pub(crate) struct Modification { + /// Syntax summary + pub(crate) syntax: String, + + /// One-line description of the modification. Use all-caps words for placeholders. + pub(crate) summary: String, + + /// Multi-line description of the modification. It's OK for this to duplicate summary, as the + /// two are not displayed together. + pub(crate) description: String, +} + +impl Modification { + fn write_help(&self, mut w: W, summary: bool) -> Result<()> { + if summary { + write!(w, " {} - {}\n", self.syntax, self.summary)?; + } else { + write!( + w, + " {}\n{}\n", + self.syntax, + indent(self.description.trim(), " ") + )?; + } + Ok(()) + } +} From 8989b0d2e3b054c1041afff05d9b0ee964873ed8 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 24 Dec 2020 17:39:49 +0000 Subject: [PATCH 142/548] Fix clippy warnings and make them all errors --- cli/src/invocation/cmd/add.rs | 2 +- cli/src/invocation/cmd/gc.rs | 2 +- cli/src/invocation/cmd/sync.rs | 2 +- cli/src/invocation/cmd/version.rs | 2 +- cli/src/invocation/filter.rs | 16 ++++++---------- cli/src/invocation/mod.rs | 3 ++- cli/src/invocation/modify.rs | 2 +- cli/src/lib.rs | 1 + cli/src/usage.rs | 8 ++++---- sync-server/src/main.rs | 2 ++ taskchampion/src/lib.rs | 1 + taskchampion/src/task.rs | 6 +++--- 12 files changed, 24 insertions(+), 23 deletions(-) diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index e3054992d..0b3618f4e 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -13,7 +13,7 @@ pub(crate) fn execute( _ => "(no description)".to_owned(), }; let t = replica.new_task(Status::Pending, description).unwrap(); - write!(w, "added task {}\n", t.get_uuid())?; + writeln!(w, "added task {}", t.get_uuid())?; Ok(()) } diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs index 1aa9e2161..5fe20b74e 100644 --- a/cli/src/invocation/cmd/gc.rs +++ b/cli/src/invocation/cmd/gc.rs @@ -4,7 +4,7 @@ use termcolor::WriteColor; pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Fallible<()> { replica.gc()?; - write!(w, "garbage collected.\n")?; + writeln!(w, "garbage collected.")?; Ok(()) } diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index 6115b4822..9abc8b657 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -8,7 +8,7 @@ pub(crate) fn execute( server: &mut Box, ) -> Fallible<()> { replica.sync(server)?; - write!(w, "sync complete.\n")?; + writeln!(w, "sync complete.")?; Ok(()) } diff --git a/cli/src/invocation/cmd/version.rs b/cli/src/invocation/cmd/version.rs index 5c8c7577e..db3f2a80b 100644 --- a/cli/src/invocation/cmd/version.rs +++ b/cli/src/invocation/cmd/version.rs @@ -4,7 +4,7 @@ use termcolor::{ColorSpec, WriteColor}; pub(crate) fn execute(w: &mut W) -> Fallible<()> { write!(w, "TaskChampion ")?; w.set_color(ColorSpec::new().set_bold(true))?; - write!(w, "{}\n", env!("CARGO_PKG_VERSION"))?; + writeln!(w, "{}", env!("CARGO_PKG_VERSION"))?; w.reset()?; Ok(()) } diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index ae9422627..97c4dd342 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -18,10 +18,7 @@ pub(super) fn filtered_tasks( let mut res = vec![]; fn is_partial_uuid(taskid: &TaskId) -> bool { - match taskid { - TaskId::PartialUuid(_) => true, - _ => false, - } + matches!(taskid, TaskId::PartialUuid(_)) } // We will enumerate the universe of tasks for this filter, checking @@ -32,7 +29,7 @@ pub(super) fn filtered_tasks( Universe::IdList(ref ids) if ids.iter().any(is_partial_uuid) => { 'task: for (uuid, task) in replica.all_tasks()?.drain() { for id in ids { - if match id { + let in_universe = match id { TaskId::WorkingSetId(id) => { // NOTE: (#108) this results in many reads of the working set; it // may be better to cache this information here or in the Replica. @@ -40,11 +37,10 @@ pub(super) fn filtered_tasks( } TaskId::PartialUuid(prefix) => uuid.to_string().starts_with(prefix), TaskId::Uuid(id) => id == &uuid, - } { - if match_task(filter, &task) { - res.push(task); - continue 'task; - } + }; + if in_universe && match_task(filter, &task) { + res.push(task); + continue 'task; } } } diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 760be3964..1920bb2dc 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -17,6 +17,7 @@ use filter::filtered_tasks; use modify::apply_modification; /// Invoke the given Command in the context of the given settings +#[allow(clippy::needless_return)] pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { log::debug!("command: {:?}", command); log::debug!("settings: {:?}", settings); @@ -88,7 +89,7 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { subcommand: Subcommand::Version, .. } => unreachable!(), - } + }; } // utilities for invoke diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index 26e190e2c..558e4e2a4 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -45,7 +45,7 @@ pub(super) fn apply_modification( task.remove_tag(&tag)?; } - write!(w, "modified task {}\n", task.get_uuid())?; + writeln!(w, "modified task {}", task.get_uuid())?; Ok(()) } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 01900fbc6..379b3ea81 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,3 +1,4 @@ +#![deny(clippy::all)] /*! This crate implements the command-line interface to TaskChampion. diff --git a/cli/src/usage.rs b/cli/src/usage.rs index d1b36d59d..bdc983fe0 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -38,13 +38,13 @@ impl Usage { "TaskChampion {}: Personal task-tracking\n\n", env!("CARGO_PKG_VERSION") )?; - write!(w, "USAGE:\n {} [args]\n\n", command_name)?; - write!(w, "TaskChampion subcommands:\n")?; + writeln!(w, "USAGE:\n {} [args]\n", command_name)?; + writeln!(w, "TaskChampion subcommands:")?; for subcommand in self.subcommands.iter() { subcommand.write_help(&mut w, summary)?; } if !summary { - write!(w, "\nSee `task help` for more detail\n")?; + writeln!(w, "\nSee `task help` for more detail")?; } Ok(()) } @@ -69,7 +69,7 @@ pub(crate) struct Subcommand { impl Subcommand { fn write_help(&self, mut w: W, summary: bool) -> Result<()> { if summary { - write!(w, " task {} - {}\n", self.name, self.summary)?; + writeln!(w, " task {} - {}", self.name, self.summary)?; } else { write!( w, diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 6c5fde701..c69ac6e77 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,3 +1,5 @@ +#![deny(clippy::all)] + use crate::storage::{KVStorage, Storage}; use actix_web::{get, web, App, HttpServer, Responder, Scope}; use api::{api_scope, ServerState}; diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 715dcaefa..fb91f36e7 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -1,3 +1,4 @@ +#![deny(clippy::all)] /*! This crate implements the core of TaskChampion, the [replica](crate::Replica). diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 2710a241c..8997919de 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -215,8 +215,8 @@ impl Task { /// Iterate over the task's tags pub fn get_tags(&self) -> impl Iterator + '_ { self.taskmap.iter().filter_map(|(k, _)| { - if k.starts_with("tag.") { - if let Ok(tag) = (&k[4..]).try_into() { + if let Some(tag) = k.strip_prefix("tag.") { + if let Ok(tag) = tag.try_into() { return Some(tag); } // note that invalid "tag.*" are ignored @@ -326,7 +326,7 @@ impl<'r> TaskMut<'r> { if let Some(v) = value { trace!("task {}: set property {}={:?}", self.task.uuid, property, v); - self.task.taskmap.insert(property.to_string(), v); + self.task.taskmap.insert(property, v); } else { trace!("task {}: remove property {}", self.task.uuid, property); self.task.taskmap.remove(&property); From 75aaf8d4ab07ae1bfe569ef6af9ee3d4301d2886 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 24 Dec 2020 21:07:27 +0000 Subject: [PATCH 143/548] use static strings for usage --- Cargo.lock | 11 +++ cli/Cargo.toml | 2 +- cli/src/argparse/filter.rs | 27 +++---- cli/src/argparse/modification.rs | 25 +++--- cli/src/argparse/subcommand.rs | 132 +++++++++++++------------------ cli/src/invocation/cmd/help.rs | 2 +- cli/src/usage.rs | 80 ++++++++++++------- 7 files changed, 138 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b3aeae18..e04f55e23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2340,6 +2340,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -2355,6 +2365,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" dependencies = [ + "terminal_size", "unicode-width", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9daeb1653..5b00532b5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,7 +11,7 @@ failure = "^0.1.8" log = "^0.4.11" nom = "*" prettytable-rs = "^0.8.0" -textwrap = "0.12.1" +textwrap = { version="0.12.1", features=["terminal_size"] } termcolor = "1.1.2" atty = "0.2.14" diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 66845e54b..07129a75b 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -2,7 +2,6 @@ use super::args::{arg_matching, id_list, minus_tag, plus_tag, TaskId}; use super::ArgList; use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; -use textwrap::dedent; /// A filter represents a selection of a particular set of tasks. /// @@ -115,31 +114,25 @@ impl Filter { pub(super) fn get_usage(u: &mut usage::Usage) { u.filters.push(usage::Filter { - syntax: "TASKID[,TASKID,..]".to_owned(), - summary: "Specific tasks".to_owned(), - description: dedent( - " + syntax: "TASKID[,TASKID,..]", + summary: "Specific tasks", + description: " Select only specific tasks. Multiple tasks can be specified either separated by commas or as separate arguments. Each task may be specfied by its working-set - index (a small number) or by its UUID. Prefixes of UUIDs broken at hyphens are + index (a small number) or by its UUID. Partial UUIDs, broken on a hyphen, are also supported, such as `b5664ef8-423d` or `b5664ef8`.", - ), }); u.filters.push(usage::Filter { - syntax: "+TAG".to_owned(), - summary: "Tagged tasks".to_owned(), - description: dedent( - " + syntax: "+TAG", + summary: "Tagged tasks", + description: " Select tasks with the given tag.", - ), }); u.filters.push(usage::Filter { - syntax: "-TAG".to_owned(), - summary: "Un-tagged tasks".to_owned(), - description: dedent( - " + syntax: "-TAG", + summary: "Un-tagged tasks", + description: " Select tasks that do not have the given tag.", - ), }); } } diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 1c46856a2..fbcc97370 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -4,7 +4,6 @@ use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use std::collections::HashSet; use taskchampion::Status; -use textwrap::dedent; #[derive(Debug, PartialEq, Clone)] pub enum DescriptionMod { @@ -112,29 +111,23 @@ impl Modification { pub(super) fn get_usage(u: &mut usage::Usage) { u.modifications.push(usage::Modification { - syntax: "DESCRIPTION".to_owned(), - summary: "Set description".to_owned(), - description: dedent( - " + syntax: "DESCRIPTION", + summary: "Set description", + description: " Set the task description. Multiple arguments are combined into a single space-separated description.", - ), }); u.modifications.push(usage::Modification { - syntax: "+TAG".to_owned(), - summary: "Tag task".to_owned(), - description: dedent( - " + syntax: "+TAG", + summary: "Tag task", + description: " Add the given tag to the task.", - ), }); u.modifications.push(usage::Modification { - syntax: "-TAG".to_owned(), - summary: "Un-tag task".to_owned(), - description: dedent( - " + syntax: "-TAG", + summary: "Un-tag task", + description: " Remove the given tag from the task.", - ), }); } } diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 6db98105e..9b2a0712e 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -3,7 +3,6 @@ use super::{ArgList, DescriptionMod, Filter, Modification, Report}; use crate::usage; use nom::{branch::alt, combinator::*, sequence::*, IResult}; use taskchampion::Status; -use textwrap::dedent; // IMPLEMENTATION NOTE: // @@ -97,10 +96,10 @@ impl Version { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "version".to_owned(), - syntax: "version".to_owned(), - summary: "Show the TaskChampion version".to_owned(), - description: "Show the version of the TaskChampion binary".to_owned(), + name: "version", + syntax: "version", + summary: "Show the TaskChampion version", + description: "Show the version of the TaskChampion binary", }); } } @@ -144,14 +143,12 @@ impl Add { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "add".to_owned(), - syntax: "add [modification]".to_owned(), - summary: "Add a new task".to_owned(), - description: dedent( - " + name: "add", + syntax: "add [modification]", + summary: "Add a new task", + description: " Add a new, pending task to the list of tasks. The modification must include a description.", - ), }); } } @@ -205,60 +202,49 @@ impl Modify { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "modify".to_owned(), - syntax: "[filter] modify [modification]".to_owned(), - summary: "Modify tasks".to_owned(), - description: dedent( - " + name: "modify", + syntax: "[filter] modify [modification]", + summary: "Modify tasks", + description: " Modify all tasks matching the filter.", - ), }); u.subcommands.push(usage::Subcommand { - name: "prepend".to_owned(), - syntax: "[filter] prepend [modification]".to_owned(), - summary: "Prepend task description".to_owned(), - description: dedent( - " + name: "prepend", + syntax: "[filter] prepend [modification]", + summary: "Prepend task description", + description: " Modify all tasks matching the filter by inserting the given description before each task's description.", - ), }); u.subcommands.push(usage::Subcommand { - name: "append".to_owned(), - syntax: "[filter] append [modification]".to_owned(), - summary: "Append task description".to_owned(), - description: dedent( - " + name: "append", + syntax: "[filter] append [modification]", + summary: "Append task description", + description: " Modify all tasks matching the filter by adding the given description to the end of each task's description.", - ), }); u.subcommands.push(usage::Subcommand { - name: "start".to_owned(), - syntax: "[filter] start [modification]".to_owned(), - summary: "Start tasks".to_owned(), - description: dedent( - " - Start all tasks matching the filter, additionally applying any given modifications."), + name: "start", + syntax: "[filter] start [modification]", + summary: "Start tasks", + description: " + Start all tasks matching the filter, additionally applying any given modifications." }); u.subcommands.push(usage::Subcommand { - name: "stop".to_owned(), - syntax: "[filter] stop [modification]".to_owned(), - summary: "Stop tasks".to_owned(), - description: dedent( - " + name: "stop", + syntax: "[filter] stop [modification]", + summary: "Stop tasks", + description: " Stop all tasks matching the filter, additionally applying any given modifications.", - ), }); u.subcommands.push(usage::Subcommand { - name: "done".to_owned(), - syntax: "[filter] done [modification]".to_owned(), - summary: "Mark tasks as completed".to_owned(), - description: dedent( - " + name: "done", + syntax: "[filter] done [modification]", + summary: "Mark tasks as completed", + description: " Mark all tasks matching the filter as completed, additionally applying any given modifications.", - ), }); } } @@ -278,13 +264,11 @@ impl List { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "list".to_owned(), - syntax: "[filter] list".to_owned(), - summary: "List tasks".to_owned(), - description: dedent( - " + name: "list", + syntax: "[filter] list", + summary: "List tasks", + description: " Show a list of the tasks matching the filter", - ), }); } } @@ -314,22 +298,16 @@ impl Info { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "info".to_owned(), - syntax: "[filter] info".to_owned(), - summary: "Show tasks".to_owned(), - description: dedent( - " - Show information about all tasks matching the fiter.", - ), + name: "info", + syntax: "[filter] info", + summary: "Show tasks", + description: " Show information about all tasks matching the fiter.", }); u.subcommands.push(usage::Subcommand { - name: "debug".to_owned(), - syntax: "[filter] debug".to_owned(), - summary: "Show task debug details".to_owned(), - description: dedent( - " - Show all key/value properties of the tasks matching the fiter.", - ), + name: "debug", + syntax: "[filter] debug", + summary: "Show task debug details", + description: " Show all key/value properties of the tasks matching the fiter.", }); } } @@ -346,14 +324,12 @@ impl Gc { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "gc".to_owned(), - syntax: "gc".to_owned(), - summary: "Perform 'garbage collection'".to_owned(), - description: dedent( - " + name: "gc", + syntax: "gc", + summary: "Perform 'garbage collection'", + description: " Perform 'garbage collection'. This refreshes the list of pending tasks and their short id's.", - ), }); } } @@ -370,16 +346,14 @@ impl Sync { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "sync".to_owned(), - syntax: "sync".to_owned(), - summary: "Synchronize this replica".to_owned(), - description: dedent( - " + name: "sync", + syntax: "sync", + summary: "Synchronize this replica", + description: " Synchronize this replica locally or against a remote server, as configured. Synchronization is a critical part of maintaining the task database, and should be done regularly, even if only locally. It is typically run in a crontask.", - ), }) } } diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index 1aaba3ed8..0847ad9ab 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -8,7 +8,7 @@ pub(crate) fn execute( summary: bool, ) -> Fallible<()> { let usage = Usage::new(); - usage.write_help(w, command_name, summary)?; + usage.write_help(w, command_name.as_ref(), summary)?; Ok(()) } diff --git a/cli/src/usage.rs b/cli/src/usage.rs index b8251ffd1..159a7d368 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -3,7 +3,6 @@ use crate::argparse; use std::io::{Result, Write}; -use textwrap::indent; /// A top-level structure containing usage/help information for the entire CLI. #[derive(Debug, Default)] @@ -32,7 +31,7 @@ impl Usage { pub(crate) fn write_help( &self, mut w: W, - command_name: String, + command_name: &str, summary: bool, ) -> Result<()> { write!( @@ -45,15 +44,31 @@ impl Usage { for subcommand in self.subcommands.iter() { subcommand.write_help(&mut w, summary)?; } - write!(w, "Filter Expressions:\n\n")?; - write!(w, "Where [filter] appears above, zero or more of the following arguments can be used to limit\n")?; - write!(w, "the tasks concerned.\n\n")?; + writeln!(w, "Filter Expressions:\n")?; + writeln!( + w, + "{}", + indented( + " + Where [filter] appears above, zero or more of the following arguments can be used + to limit the tasks addressed by the subcommand.", + "" + ) + )?; for filter in self.filters.iter() { filter.write_help(&mut w, summary)?; } - write!(w, "Modifications:\n\n")?; - write!(w, "Where [modification] appears above, zero or more of the following arguments can be used\n")?; - write!(w, "to modify the selected tasks.\n\n")?; + writeln!(w, "Modifications:\n")?; + writeln!( + w, + "{}", + indented( + " + Where [modification] appears above, zero or more of the following arguments can be + used to modify the selected tasks.", + "" + ) + )?; for modification in self.modifications.iter() { modification.write_help(&mut w, summary)?; } @@ -64,21 +79,32 @@ impl Usage { } } +/// wrap an indented string +fn indented(string: &str, indent: &str) -> String { + let termwidth = textwrap::termwidth(); + let words: Vec<&str> = string.split_whitespace().collect(); + let string = words.join(" "); + textwrap::indent( + textwrap::fill(string.trim(), termwidth - indent.len()).as_ref(), + indent, + ) +} + /// Usage documentation for a subcommand #[derive(Debug, Default)] pub(crate) struct Subcommand { /// Name of the subcommand - pub(crate) name: String, + pub(crate) name: &'static str, /// Syntax summary, without command_name - pub(crate) syntax: String, + pub(crate) syntax: &'static str, /// One-line description of the subcommand. Use an initial capital and no trailing period. - pub(crate) summary: String, + pub(crate) summary: &'static str, /// Multi-line description of the subcommand. It's OK for this to duplicate summary, as the /// two are not displayed together. - pub(crate) description: String, + pub(crate) description: &'static str, } impl Subcommand { @@ -86,11 +112,11 @@ impl Subcommand { if summary { writeln!(w, " task {} - {}", self.name, self.summary)?; } else { - write!( + writeln!( w, - " task {}\n{}\n", + " task {}\n{}", self.syntax, - indent(self.description.trim(), " ") + indented(self.description, " ") )?; } Ok(()) @@ -101,26 +127,26 @@ impl Subcommand { #[derive(Debug, Default)] pub(crate) struct Filter { /// Syntax summary - pub(crate) syntax: String, + pub(crate) syntax: &'static str, /// One-line description of the filter. Use all-caps words for placeholders. - pub(crate) summary: String, + pub(crate) summary: &'static str, /// Multi-line description of the filter. It's OK for this to duplicate summary, as the /// two are not displayed together. - pub(crate) description: String, + pub(crate) description: &'static str, } impl Filter { fn write_help(&self, mut w: W, summary: bool) -> Result<()> { if summary { - write!(w, " {} - {}\n", self.syntax, self.summary)?; + writeln!(w, " {} - {}", self.syntax, self.summary)?; } else { write!( w, " {}\n{}\n", self.syntax, - indent(self.description.trim(), " ") + indented(self.description, " ") )?; } Ok(()) @@ -131,26 +157,26 @@ impl Filter { #[derive(Debug, Default)] pub(crate) struct Modification { /// Syntax summary - pub(crate) syntax: String, + pub(crate) syntax: &'static str, /// One-line description of the modification. Use all-caps words for placeholders. - pub(crate) summary: String, + pub(crate) summary: &'static str, /// Multi-line description of the modification. It's OK for this to duplicate summary, as the /// two are not displayed together. - pub(crate) description: String, + pub(crate) description: &'static str, } impl Modification { fn write_help(&self, mut w: W, summary: bool) -> Result<()> { if summary { - write!(w, " {} - {}\n", self.syntax, self.summary)?; + writeln!(w, " {} - {}", self.syntax, self.summary)?; } else { - write!( + writeln!( w, - " {}\n{}\n", + " {}\n{}", self.syntax, - indent(self.description.trim(), " ") + indented(self.description, " ") )?; } Ok(()) From 922e71cd4dfa053fa438a95ff5e824f6584a5181 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 24 Dec 2020 21:14:20 +0000 Subject: [PATCH 144/548] use a s!(..) shorthand in CLI tests --- cli/src/argparse/args.rs | 20 +++++------ cli/src/argparse/command.rs | 2 +- cli/src/argparse/filter.rs | 2 +- cli/src/argparse/modification.rs | 12 +++---- cli/src/argparse/subcommand.rs | 16 ++++----- cli/src/invocation/cmd/add.rs | 2 +- cli/src/invocation/cmd/help.rs | 4 +-- cli/src/invocation/cmd/info.rs | 4 +-- cli/src/invocation/cmd/list.rs | 4 +-- cli/src/invocation/cmd/modify.rs | 4 +-- cli/src/invocation/filter.rs | 58 ++++++++++++++------------------ cli/src/macros.rs | 6 ++++ 12 files changed, 65 insertions(+), 69 deletions(-) diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs index edf6248a9..c58b5ac51 100644 --- a/cli/src/argparse/args.rs +++ b/cli/src/argparse/args.rs @@ -197,27 +197,27 @@ mod test { fn test_id_list_uuids() { assert_eq!( id_list("12341234").unwrap().1, - vec![TaskId::PartialUuid("12341234".to_owned())] + vec![TaskId::PartialUuid(s!("12341234"))] ); assert_eq!( id_list("1234abcd").unwrap().1, - vec![TaskId::PartialUuid("1234abcd".to_owned())] + vec![TaskId::PartialUuid(s!("1234abcd"))] ); assert_eq!( id_list("abcd1234").unwrap().1, - vec![TaskId::PartialUuid("abcd1234".to_owned())] + vec![TaskId::PartialUuid(s!("abcd1234"))] ); assert_eq!( id_list("abcd1234-1234").unwrap().1, - vec![TaskId::PartialUuid("abcd1234-1234".to_owned())] + vec![TaskId::PartialUuid(s!("abcd1234-1234"))] ); assert_eq!( id_list("abcd1234-1234-2345").unwrap().1, - vec![TaskId::PartialUuid("abcd1234-1234-2345".to_owned())] + vec![TaskId::PartialUuid(s!("abcd1234-1234-2345"))] ); assert_eq!( id_list("abcd1234-1234-2345-3456").unwrap().1, - vec![TaskId::PartialUuid("abcd1234-1234-2345-3456".to_owned())] + vec![TaskId::PartialUuid(s!("abcd1234-1234-2345-3456"))] ); assert_eq!( id_list("abcd1234-1234-2345-3456-0123456789ab").unwrap().1, @@ -241,10 +241,10 @@ mod test { #[test] fn test_id_list_uuids_mixed() { assert_eq!(id_list("abcd1234,abcd1234-1234,abcd1234-1234-2345,abcd1234-1234-2345-3456,abcd1234-1234-2345-3456-0123456789ab").unwrap().1, - vec![TaskId::PartialUuid("abcd1234".to_owned()), - TaskId::PartialUuid("abcd1234-1234".to_owned()), - TaskId::PartialUuid("abcd1234-1234-2345".to_owned()), - TaskId::PartialUuid("abcd1234-1234-2345-3456".to_owned()), + vec![TaskId::PartialUuid(s!("abcd1234")), + TaskId::PartialUuid(s!("abcd1234-1234")), + TaskId::PartialUuid(s!("abcd1234-1234-2345")), + TaskId::PartialUuid(s!("abcd1234-1234-2345-3456")), TaskId::Uuid(Uuid::parse_str("abcd1234-1234-2345-3456-0123456789ab").unwrap()), ]); } diff --git a/cli/src/argparse/command.rs b/cli/src/argparse/command.rs index 7d55616e7..fec430d2c 100644 --- a/cli/src/argparse/command.rs +++ b/cli/src/argparse/command.rs @@ -55,7 +55,7 @@ mod test { Command::from_argv(argv!["task", "version"]).unwrap(), Command { subcommand: Subcommand::Version, - command_name: "task".to_owned(), + command_name: s!("task"), } ); } diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 07129a75b..f381e5862 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -210,7 +210,7 @@ mod test { Filter { universe: Universe::IdList(vec![ TaskId::WorkingSetId(1), - TaskId::PartialUuid("abcd1234".to_owned()), + TaskId::PartialUuid(s!("abcd1234")), ]), ..Default::default() } diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index fbcc97370..d449f1ef6 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -155,7 +155,7 @@ mod test { assert_eq!( modification, Modification { - description: DescriptionMod::Set("newdesc".to_owned()), + description: DescriptionMod::Set(s!("newdesc")), ..Default::default() } ); @@ -168,7 +168,7 @@ mod test { assert_eq!( modification, Modification { - add_tags: set!["abc".to_owned(), "def".to_owned()], + add_tags: set![s!("abc"), s!("def")], ..Default::default() } ); @@ -181,7 +181,7 @@ mod test { assert_eq!( modification, Modification { - description: DescriptionMod::Set("new desc fun".to_owned()), + description: DescriptionMod::Set(s!("new desc fun")), ..Default::default() } ); @@ -195,9 +195,9 @@ mod test { assert_eq!( modification, Modification { - description: DescriptionMod::Set("new desc fun".to_owned()), - add_tags: set!["next".to_owned()], - remove_tags: set!["daytime".to_owned()], + description: DescriptionMod::Set(s!("new desc fun")), + add_tags: set![s!("next")], + remove_tags: set![s!("daytime")], ..Default::default() } ); diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 9b2a0712e..5efa4c6d1 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -409,7 +409,7 @@ mod test { fn test_add_description() { let subcommand = Subcommand::Add { modification: Modification { - description: DescriptionMod::Set("foo".to_owned()), + description: DescriptionMod::Set(s!("foo")), ..Default::default() }, }; @@ -423,7 +423,7 @@ mod test { fn test_add_description_multi() { let subcommand = Subcommand::Add { modification: Modification { - description: DescriptionMod::Set("foo bar".to_owned()), + description: DescriptionMod::Set(s!("foo bar")), ..Default::default() }, }; @@ -441,7 +441,7 @@ mod test { ..Default::default() }, modification: Modification { - description: DescriptionMod::Set("foo bar".to_owned()), + description: DescriptionMod::Set(s!("foo bar")), ..Default::default() }, }; @@ -459,7 +459,7 @@ mod test { ..Default::default() }, modification: Modification { - description: DescriptionMod::Append("foo bar".to_owned()), + description: DescriptionMod::Append(s!("foo bar")), ..Default::default() }, }; @@ -477,7 +477,7 @@ mod test { ..Default::default() }, modification: Modification { - description: DescriptionMod::Prepend("foo bar".to_owned()), + description: DescriptionMod::Prepend(s!("foo bar")), ..Default::default() }, }; @@ -513,7 +513,7 @@ mod test { ..Default::default() }, modification: Modification { - description: DescriptionMod::Set("now-finished".to_owned()), + description: DescriptionMod::Set(s!("now-finished")), status: Some(Status::Completed), ..Default::default() }, @@ -551,7 +551,7 @@ mod test { }, modification: Modification { active: Some(true), - description: DescriptionMod::Set("mod".to_owned()), + description: DescriptionMod::Set(s!("mod")), ..Default::default() }, }; @@ -587,7 +587,7 @@ mod test { ..Default::default() }, modification: Modification { - description: DescriptionMod::Set("mod".to_owned()), + description: DescriptionMod::Set(s!("mod")), active: Some(false), ..Default::default() }, diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index 0b3618f4e..104a6357c 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -27,7 +27,7 @@ mod test { let mut w = test_writer(); let mut replica = test_replica(); let modification = Modification { - description: DescriptionMod::Set("my description".to_owned()), + description: DescriptionMod::Set(s!("my description")), ..Default::default() }; execute(&mut w, &mut replica, modification).unwrap(); diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index 0847ad9ab..df7bb5919 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -20,12 +20,12 @@ mod test { #[test] fn test_summary() { let mut w = test_writer(); - execute(&mut w, "task".to_owned(), true).unwrap(); + execute(&mut w, s!("task"), true).unwrap(); } #[test] fn test_long() { let mut w = test_writer(); - execute(&mut w, "task".to_owned(), false).unwrap(); + execute(&mut w, s!("task"), false).unwrap(); } } diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index f30e4a8fd..5d70163c7 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -52,9 +52,7 @@ mod test { fn test_info() { let mut w = test_writer(); let mut replica = test_replica(); - replica - .new_task(Status::Pending, "my task".to_owned()) - .unwrap(); + replica.new_task(Status::Pending, s!("my task")).unwrap(); let filter = Filter { ..Default::default() diff --git a/cli/src/invocation/cmd/list.rs b/cli/src/invocation/cmd/list.rs index 2905a9c19..b3d922527 100644 --- a/cli/src/invocation/cmd/list.rs +++ b/cli/src/invocation/cmd/list.rs @@ -41,9 +41,7 @@ mod test { fn test_list() { let mut w = test_writer(); let mut replica = test_replica(); - replica - .new_task(Status::Pending, "my task".to_owned()) - .unwrap(); + replica.new_task(Status::Pending, s!("my task")).unwrap(); let report = Report { filter: Filter { diff --git a/cli/src/invocation/cmd/modify.rs b/cli/src/invocation/cmd/modify.rs index b935f93af..d914da418 100644 --- a/cli/src/invocation/cmd/modify.rs +++ b/cli/src/invocation/cmd/modify.rs @@ -33,14 +33,14 @@ mod test { let mut replica = test_replica(); let task = replica - .new_task(Status::Pending, "old description".to_owned()) + .new_task(Status::Pending, s!("old description")) .unwrap(); let filter = Filter { ..Default::default() }; let modification = Modification { - description: DescriptionMod::Set("new description".to_owned()), + description: DescriptionMod::Set(s!("new description")), ..Default::default() }; execute(&mut w, &mut replica, filter, modification).unwrap(); diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index 4bca65825..881836009 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -124,9 +124,9 @@ mod test { fn exact_ids() { let mut replica = test_replica(); - let t1 = replica.new_task(Status::Pending, "A".to_owned()).unwrap(); - let t2 = replica.new_task(Status::Completed, "B".to_owned()).unwrap(); - let _t = replica.new_task(Status::Pending, "C".to_owned()).unwrap(); + let t1 = replica.new_task(Status::Pending, s!("A")).unwrap(); + let t2 = replica.new_task(Status::Completed, s!("B")).unwrap(); + let _t = replica.new_task(Status::Pending, s!("C")).unwrap(); replica.gc().unwrap(); let t1uuid = *t1.get_uuid(); @@ -144,16 +144,16 @@ mod test { .map(|t| t.get_description().to_owned()) .collect(); filtered.sort(); - assert_eq!(vec!["A".to_owned(), "B".to_owned()], filtered); + assert_eq!(vec![s!("A"), s!("B")], filtered); } #[test] fn partial_ids() { let mut replica = test_replica(); - let t1 = replica.new_task(Status::Pending, "A".to_owned()).unwrap(); - let t2 = replica.new_task(Status::Completed, "B".to_owned()).unwrap(); - let _t = replica.new_task(Status::Pending, "C".to_owned()).unwrap(); + let t1 = replica.new_task(Status::Pending, s!("A")).unwrap(); + let t2 = replica.new_task(Status::Completed, s!("B")).unwrap(); + let _t = replica.new_task(Status::Pending, s!("C")).unwrap(); replica.gc().unwrap(); let t1uuid = *t1.get_uuid(); @@ -173,16 +173,16 @@ mod test { .map(|t| t.get_description().to_owned()) .collect(); filtered.sort(); - assert_eq!(vec!["A".to_owned(), "B".to_owned()], filtered); + assert_eq!(vec![s!("A"), s!("B")], filtered); } #[test] fn all_tasks() { let mut replica = test_replica(); - replica.new_task(Status::Pending, "A".to_owned()).unwrap(); - replica.new_task(Status::Completed, "B".to_owned()).unwrap(); - replica.new_task(Status::Deleted, "C".to_owned()).unwrap(); + replica.new_task(Status::Pending, s!("A")).unwrap(); + replica.new_task(Status::Completed, s!("B")).unwrap(); + replica.new_task(Status::Deleted, s!("C")).unwrap(); replica.gc().unwrap(); let filter = Filter { @@ -194,10 +194,7 @@ mod test { .map(|t| t.get_description().to_owned()) .collect(); filtered.sort(); - assert_eq!( - vec!["A".to_owned(), "B".to_owned(), "C".to_owned()], - filtered - ); + assert_eq!(vec![s!("A"), s!("B"), s!("C")], filtered); } #[test] @@ -207,57 +204,54 @@ mod test { let no: Tag = "no".try_into()?; let mut t1 = replica - .new_task(Status::Pending, "A".to_owned())? + .new_task(Status::Pending, s!("A"))? .into_mut(&mut replica); t1.add_tag(&yes)?; let mut t2 = replica - .new_task(Status::Pending, "B".to_owned())? + .new_task(Status::Pending, s!("B"))? .into_mut(&mut replica); t2.add_tag(&yes)?; t2.add_tag(&no)?; let mut t3 = replica - .new_task(Status::Pending, "C".to_owned())? + .new_task(Status::Pending, s!("C"))? .into_mut(&mut replica); t3.add_tag(&no)?; - let _t4 = replica.new_task(Status::Pending, "D".to_owned())?; + let _t4 = replica.new_task(Status::Pending, s!("D"))?; // look for just "yes" (A and B) let filter = Filter { universe: Universe::AllTasks, - conditions: vec![Condition::HasTag("yes".to_owned())], + conditions: vec![Condition::HasTag(s!("yes"))], ..Default::default() }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) .collect(); filtered.sort(); - assert_eq!(vec!["A".to_owned(), "B".to_owned()], filtered); + assert_eq!(vec![s!("A"), s!("B")], filtered); // look for tags without "no" (A, D) let filter = Filter { universe: Universe::AllTasks, - conditions: vec![Condition::NoTag("no".to_owned())], + conditions: vec![Condition::NoTag(s!("no"))], ..Default::default() }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) .collect(); filtered.sort(); - assert_eq!(vec!["A".to_owned(), "D".to_owned()], filtered); + assert_eq!(vec![s!("A"), s!("D")], filtered); // look for tags with "yes" and "no" (B) let filter = Filter { universe: Universe::AllTasks, - conditions: vec![ - Condition::HasTag("yes".to_owned()), - Condition::HasTag("no".to_owned()), - ], + conditions: vec![Condition::HasTag(s!("yes")), Condition::HasTag(s!("no"))], ..Default::default() }; let filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) .collect(); - assert_eq!(vec!["B".to_owned()], filtered); + assert_eq!(vec![s!("B")], filtered); Ok(()) } @@ -266,9 +260,9 @@ mod test { fn pending_tasks() { let mut replica = test_replica(); - replica.new_task(Status::Pending, "A".to_owned()).unwrap(); - replica.new_task(Status::Completed, "B".to_owned()).unwrap(); - replica.new_task(Status::Deleted, "C".to_owned()).unwrap(); + replica.new_task(Status::Pending, s!("A")).unwrap(); + replica.new_task(Status::Completed, s!("B")).unwrap(); + replica.new_task(Status::Deleted, s!("C")).unwrap(); replica.gc().unwrap(); let filter = Filter { @@ -280,6 +274,6 @@ mod test { .map(|t| t.get_description().to_owned()) .collect(); filtered.sort(); - assert_eq!(vec!["A".to_owned()], filtered); + assert_eq!(vec![s!("A")], filtered); } } diff --git a/cli/src/macros.rs b/cli/src/macros.rs index 83a347dd0..02e11e5cf 100644 --- a/cli/src/macros.rs +++ b/cli/src/macros.rs @@ -24,3 +24,9 @@ macro_rules! set( } }; ); + +/// Create a String from an &str; just a testing shorthand +#[cfg(test)] +macro_rules! s( + { $s:expr } => { $s.to_owned() }; +); From 8a10fa83353b73b4c2d474c8137ffb1926969729 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 25 Dec 2020 02:43:51 +0000 Subject: [PATCH 145/548] fix some more pedantic lints --- taskchampion/src/replica.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 1de446e26..215578647 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -94,7 +94,7 @@ impl Replica { let mut res = Vec::with_capacity(working_set.len()); for item in working_set.iter() { res.push(match item { - Some(u) => match self.taskdb.get_task(&u)? { + Some(u) => match self.taskdb.get_task(u)? { Some(tm) => Some(Task::new(*u, tm)), None => None, }, @@ -157,7 +157,7 @@ impl Replica { fn delete_task(&mut self, uuid: &Uuid) -> Fallible<()> { // check that it already exists; this is a convenience check, as the task may already exist // when this Create operation is finally sync'd with operations from other replicas - if self.taskdb.get_task(&uuid)?.is_none() { + if self.taskdb.get_task(uuid)?.is_none() { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } self.taskdb.apply(Operation::Delete { uuid: *uuid })?; From e7b7e88bc9ca09382983912c40984e90f5f02ba1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 25 Dec 2020 02:52:49 +0000 Subject: [PATCH 146/548] convert unrwap to expect and add testing --- taskchampion/src/utils.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/taskchampion/src/utils.rs b/taskchampion/src/utils.rs index aafe6f010..6747b6cbd 100644 --- a/taskchampion/src/utils.rs +++ b/taskchampion/src/utils.rs @@ -8,7 +8,7 @@ pub(crate) struct Key(uuid::Bytes); impl From<&[u8]> for Key { fn from(bytes: &[u8]) -> Key { - Key(bytes.try_into().unwrap()) + Key(bytes.try_into().expect("expected 16 bytes")) } } @@ -37,3 +37,24 @@ impl AsRef<[u8]> for Key { &self.0[..] } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_from_bytes() { + let k: Key = (&[1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16][..]).into(); + let u: Uuid = k.into(); + assert_eq!( + u, + Uuid::parse_str("01020304-0506-0708-090a-0b0c0d0e0f10").unwrap() + ); + } + + #[test] + #[should_panic] + fn test_from_bytes_bad_len() { + let _: Key = (&[1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11][..]).into(); + } +} From f264e74288682fc33e91f63d624833eda52c5cfc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 25 Dec 2020 04:18:24 +0000 Subject: [PATCH 147/548] specify nom version --- cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5b00532b5..025ee0715 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,7 +9,7 @@ dirs = "^3.0.1" env_logger = "^0.8.2" failure = "^0.1.8" log = "^0.4.11" -nom = "*" +nom = "^6.0.1" prettytable-rs = "^0.8.0" textwrap = { version="0.12.1", features=["terminal_size"] } termcolor = "1.1.2" From 00f548c71383d26a0a0b4684378504b796d0c06e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 26 Dec 2020 04:07:36 +0000 Subject: [PATCH 148/548] implement generic report generation --- cli/src/argparse/mod.rs | 2 +- cli/src/argparse/report.rs | 79 +++++-- cli/src/argparse/subcommand.rs | 46 +++- cli/src/invocation/cmd/list.rs | 29 +-- cli/src/invocation/mod.rs | 2 + cli/src/invocation/report.rs | 374 +++++++++++++++++++++++++++++++++ taskchampion/src/task.rs | 2 +- 7 files changed, 484 insertions(+), 50 deletions(-) create mode 100644 cli/src/invocation/report.rs diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index a6d8c72bd..01df66bed 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -22,7 +22,7 @@ pub(crate) use args::TaskId; pub(crate) use command::Command; pub(crate) use filter::{Condition, Filter, Universe}; pub(crate) use modification::{DescriptionMod, Modification}; -pub(crate) use report::Report; +pub(crate) use report::{Column, Property, Report, Sort, SortBy}; pub(crate) use subcommand::Subcommand; use crate::usage::Usage; diff --git a/cli/src/argparse/report.rs b/cli/src/argparse/report.rs index b800cafb6..3b569652c 100644 --- a/cli/src/argparse/report.rs +++ b/cli/src/argparse/report.rs @@ -1,33 +1,68 @@ -use super::{ArgList, Filter}; -use nom::IResult; +use super::Filter; /// A report specifies a filter as well as a sort order and information about which /// task attributes to display -#[derive(Debug, PartialEq, Default)] +#[derive(Clone, Debug, PartialEq, Default)] pub(crate) struct Report { + /// Columns to display in this report + pub columns: Vec, + /// Sort order for this report + pub sort: Vec, + /// Filter selecting tasks for this report pub filter: Filter, } -impl Report { - pub(super) fn parse(input: ArgList) -> IResult { - let (input, filter) = Filter::parse(input)?; - Ok((input, Report { filter })) - } +/// A column to display in a report +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Column { + /// The label for this column + pub label: String, + + /// The property to display + pub property: Property, } -#[cfg(test)] -mod test { - use super::*; +/// Task property to display in a report +#[derive(Clone, Debug, PartialEq)] +#[allow(dead_code)] +pub(crate) enum Property { + /// The task's ID, either working-set index or Uuid if no in the working set + Id, - #[test] - fn test_empty() { - let (input, report) = Report::parse(argv![]).unwrap(); - assert_eq!(input.len(), 0); - assert_eq!( - report, - Report { - ..Default::default() - } - ); - } + /// The task's full UUID + Uuid, + + /// Whether the task is active or not + Active, + + /// The task's description + Description, + + /// The task's tags + Tags, +} + +/// A sorting criterion for a sort operation. +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Sort { + /// True if the sort should be "ascending" (a -> z, 0 -> 9, etc.) + pub ascending: bool, + + /// The property to sort on + pub sort_by: SortBy, +} + +/// Task property to sort by +#[derive(Clone, Debug, PartialEq)] +#[allow(dead_code)] +pub(crate) enum SortBy { + /// The task's ID, either working-set index or a UUID prefix; working + /// set tasks sort before others. + Id, + + /// The task's full UUID + Uuid, + + /// The task's description + Description, } diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 5efa4c6d1..5884cc4a9 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -1,5 +1,7 @@ use super::args::*; -use super::{ArgList, DescriptionMod, Filter, Modification, Report}; +use super::{ + ArgList, Column, DescriptionMod, Filter, Modification, Property, Report, Sort, SortBy, +}; use crate::usage; use nom::{branch::alt, combinator::*, sequence::*, IResult}; use taskchampion::Status; @@ -252,12 +254,45 @@ impl Modify { struct List; impl List { + // temporary + fn default_report() -> Report { + Report { + columns: vec![ + Column { + label: "Id".to_owned(), + property: Property::Id, + }, + Column { + label: "Description".to_owned(), + property: Property::Description, + }, + Column { + label: "Active".to_owned(), + property: Property::Active, + }, + Column { + label: "Tags".to_owned(), + property: Property::Tags, + }, + ], + sort: vec![Sort { + ascending: false, + sort_by: SortBy::Uuid, + }], + ..Default::default() + } + } + fn parse(input: ArgList) -> IResult { - fn to_subcommand(input: (Report, &str)) -> Result { - Ok(Subcommand::List { report: input.0 }) + fn to_subcommand(input: (Filter, &str)) -> Result { + let report = Report { + filter: input.0, + ..List::default_report() + }; + Ok(Subcommand::List { report }) } map_res( - pair(Report::parse, arg_matching(literal("list"))), + pair(Filter::parse, arg_matching(literal("list"))), to_subcommand, )(input) } @@ -602,7 +637,7 @@ mod test { fn test_list() { let subcommand = Subcommand::List { report: Report { - ..Default::default() + ..List::default_report() }, }; assert_eq!( @@ -619,6 +654,7 @@ mod test { universe: Universe::for_ids(vec![12, 13]), ..Default::default() }, + ..List::default_report() }, }; assert_eq!( diff --git a/cli/src/invocation/cmd/list.rs b/cli/src/invocation/cmd/list.rs index b3d922527..48f8543b6 100644 --- a/cli/src/invocation/cmd/list.rs +++ b/cli/src/invocation/cmd/list.rs @@ -1,8 +1,6 @@ use crate::argparse::Report; -use crate::invocation::filtered_tasks; -use crate::table; +use crate::invocation::display_report; use failure::Fallible; -use prettytable::{cell, row, Table}; use taskchampion::Replica; use termcolor::WriteColor; @@ -11,29 +9,13 @@ pub(crate) fn execute( replica: &mut Replica, report: Report, ) -> Fallible<()> { - let mut t = Table::new(); - t.set_format(table::format()); - t.set_titles(row![b->"id", b->"act", b->"description"]); - for task in filtered_tasks(replica, &report.filter)? { - let uuid = task.get_uuid(); - let mut id = uuid.to_string(); - if let Some(i) = replica.get_working_set_index(&uuid)? { - id = i.to_string(); - } - let active = match task.is_active() { - true => "*", - false => "", - }; - t.add_row(row![id, active, task.get_description()]); - } - t.print(w)?; - Ok(()) + display_report(w, replica, &report) } #[cfg(test)] mod test { use super::*; - use crate::argparse::Filter; + use crate::argparse::{Column, Filter, Property}; use crate::invocation::test::*; use taskchampion::Status; @@ -47,6 +29,11 @@ mod test { filter: Filter { ..Default::default() }, + columns: vec![Column { + label: "Description".to_owned(), + property: Property::Description, + }], + ..Default::default() }; execute(&mut w, &mut replica, report).unwrap(); assert!(w.into_string().contains("my task")); diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 1920bb2dc..d7bcaef0a 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -9,12 +9,14 @@ use termcolor::{ColorChoice, StandardStream}; mod cmd; mod filter; mod modify; +mod report; #[cfg(test)] mod test; use filter::filtered_tasks; use modify::apply_modification; +use report::display_report; /// Invoke the given Command in the context of the given settings #[allow(clippy::needless_return)] diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs new file mode 100644 index 000000000..db5d577df --- /dev/null +++ b/cli/src/invocation/report.rs @@ -0,0 +1,374 @@ +use crate::argparse::{Column, Property, Report, SortBy}; +use crate::invocation::filtered_tasks; +use crate::table; +use failure::Fallible; +use prettytable::{Row, Table}; +use std::cmp::Ordering; +use taskchampion::{Replica, Task, Uuid}; +use termcolor::WriteColor; + +// pending #123, this is a non-fallible way of looking up a task's working set index +struct WorkingSet(Vec>); + +impl WorkingSet { + fn new(replica: &mut Replica) -> Fallible { + let working_set = replica.working_set()?; + Ok(Self( + working_set + .iter() + .map(|opt| opt.as_ref().map(|t| *t.get_uuid())) + .collect(), + )) + } + + fn index(&self, target: &Uuid) -> Option { + for (i, uuid) in self.0.iter().enumerate() { + if let Some(uuid) = uuid { + if uuid == target { + return Some(i); + } + } + } + None + } +} + +/// Sort tasks for the given report. +fn sort_tasks(tasks: &mut Vec, report: &Report, working_set: &WorkingSet) { + tasks.sort_by(|a, b| { + for s in &report.sort { + let ord = match s.sort_by { + SortBy::Id => { + let a_uuid = a.get_uuid(); + let b_uuid = b.get_uuid(); + let a_id = working_set.index(a_uuid); + let b_id = working_set.index(b_uuid); + println!("a_uuid {} -> a_id {:?}", a_uuid, a_id); + println!("b_uuid {} -> b_id {:?}", b_uuid, b_id); + match (a_id, b_id) { + (Some(a_id), Some(b_id)) => a_id.cmp(&b_id), + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (None, None) => a_uuid.cmp(b_uuid), + } + } + SortBy::Uuid => a.get_uuid().cmp(b.get_uuid()), + SortBy::Description => a.get_description().cmp(b.get_description()), + }; + // If this sort property is equal, go on to the next.. + if ord == Ordering::Equal { + continue; + } + // Reverse order if not ascending + if s.ascending { + return ord; + } else { + return ord.reverse(); + } + } + Ordering::Equal + }); +} + +/// Generate the string representation for the given task and column. +fn task_column(task: &Task, column: &Column, working_set: &WorkingSet) -> String { + match column.property { + Property::Id => { + let uuid = task.get_uuid(); + let mut id = uuid.to_string(); + if let Some(i) = working_set.index(uuid) { + id = i.to_string(); + } + id + } + Property::Uuid => { + let uuid = task.get_uuid(); + uuid.to_string() + } + Property::Active => match task.is_active() { + true => "*".to_owned(), + false => "".to_owned(), + }, + Property::Description => task.get_description().to_owned(), + Property::Tags => { + let mut tags = task + .get_tags() + .map(|t| format!("+{}", t)) + .collect::>(); + tags.sort(); + tags.join(" ") + } + } +} + +pub(super) fn display_report( + w: &mut W, + replica: &mut Replica, + report: &Report, +) -> Fallible<()> { + let mut t = Table::new(); + + let working_set = WorkingSet::new(replica)?; + + // Get the tasks from the filter + let mut tasks: Vec<_> = filtered_tasks(replica, &report.filter)?.collect(); + + // ..sort them as desired + sort_tasks(&mut tasks, report, &working_set); + + // ..set up the column titles + t.set_format(table::format()); + t.set_titles(report.columns.iter().map(|col| col.label.clone()).into()); + + // ..insert the data + for task in &tasks { + let row: Row = report + .columns + .iter() + .map(|col| task_column(task, col, &working_set)) + .collect::(); + t.add_row(row); + } + + // ..and display it + t.print(w)?; + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::argparse::{Column, Property, Report, Sort, SortBy}; + use crate::invocation::test::*; + use std::convert::TryInto; + use taskchampion::Status; + + fn create_tasks(replica: &mut Replica) -> [Uuid; 3] { + let t1 = replica.new_task(Status::Pending, s!("A")).unwrap(); + let t2 = replica.new_task(Status::Pending, s!("B")).unwrap(); + let t3 = replica.new_task(Status::Pending, s!("C")).unwrap(); + + // t2 is comleted and not in the working set + let mut t2 = t2.into_mut(replica); + t2.set_status(Status::Completed).unwrap(); + let t2 = t2.into_immut(); + + replica.gc().unwrap(); + + [*t1.get_uuid(), *t2.get_uuid(), *t3.get_uuid()] + } + + #[test] + fn sorting_by_descr() { + let mut replica = test_replica(); + create_tasks(&mut replica); + let working_set = WorkingSet::new(&mut replica).unwrap(); + let mut report = Report { + sort: vec![Sort { + ascending: true, + sort_by: SortBy::Description, + }], + ..Default::default() + }; + + // ascending + let mut tasks: Vec<_> = replica.all_tasks().unwrap().values().cloned().collect(); + sort_tasks(&mut tasks, &report, &working_set); + let descriptions: Vec<_> = tasks.iter().map(|t| t.get_description()).collect(); + assert_eq!(descriptions, vec!["A", "B", "C"]); + + // ascending + report.sort[0].ascending = false; + let mut tasks: Vec<_> = replica.all_tasks().unwrap().values().cloned().collect(); + sort_tasks(&mut tasks, &report, &working_set); + let descriptions: Vec<_> = tasks.iter().map(|t| t.get_description()).collect(); + assert_eq!(descriptions, vec!["C", "B", "A"]); + } + + #[test] + fn sorting_by_id() { + let mut replica = test_replica(); + create_tasks(&mut replica); + let working_set = WorkingSet::new(&mut replica).unwrap(); + let mut report = Report { + sort: vec![Sort { + ascending: true, + sort_by: SortBy::Id, + }], + ..Default::default() + }; + + // ascending + let mut tasks: Vec<_> = replica.all_tasks().unwrap().values().cloned().collect(); + sort_tasks(&mut tasks, &report, &working_set); + let descriptions: Vec<_> = tasks.iter().map(|t| t.get_description()).collect(); + assert_eq!(descriptions, vec!["A", "C", "B"]); + + // ascending + report.sort[0].ascending = false; + let mut tasks: Vec<_> = replica.all_tasks().unwrap().values().cloned().collect(); + sort_tasks(&mut tasks, &report, &working_set); + let descriptions: Vec<_> = tasks.iter().map(|t| t.get_description()).collect(); + assert_eq!(descriptions, vec!["B", "C", "A"]); + } + + #[test] + fn sorting_by_uuid() { + let mut replica = test_replica(); + let uuids = create_tasks(&mut replica); + let working_set = WorkingSet::new(&mut replica).unwrap(); + let report = Report { + sort: vec![Sort { + ascending: true, + sort_by: SortBy::Uuid, + }], + ..Default::default() + }; + + let mut tasks: Vec<_> = replica.all_tasks().unwrap().values().cloned().collect(); + sort_tasks(&mut tasks, &report, &working_set); + let got_uuids: Vec<_> = tasks.iter().map(|t| *t.get_uuid()).collect(); + let mut exp_uuids = uuids.to_vec(); + exp_uuids.sort(); + assert_eq!(got_uuids, exp_uuids); + } + + #[test] + fn sorting_by_multiple() { + let mut replica = test_replica(); + create_tasks(&mut replica); + + // make a second task named A with a larger ID than the first + let t = replica.new_task(Status::Pending, s!("A")).unwrap(); + t.into_mut(&mut replica) + .add_tag(&("second".try_into().unwrap())) + .unwrap(); + + let working_set = WorkingSet::new(&mut replica).unwrap(); + let report = Report { + sort: vec![ + Sort { + ascending: false, + sort_by: SortBy::Description, + }, + Sort { + ascending: true, + sort_by: SortBy::Id, + }, + ], + ..Default::default() + }; + + let mut tasks: Vec<_> = replica.all_tasks().unwrap().values().cloned().collect(); + sort_tasks(&mut tasks, &report, &working_set); + let descriptions: Vec<_> = tasks.iter().map(|t| t.get_description()).collect(); + assert_eq!(descriptions, vec!["C", "B", "A", "A"]); + assert!(tasks[3].has_tag(&("second".try_into().unwrap()))); + } + + #[test] + fn task_column_id() { + let mut replica = test_replica(); + let uuids = create_tasks(&mut replica); + let working_set = WorkingSet::new(&mut replica).unwrap(); + + let task = replica.get_working_set_task(1).unwrap().unwrap(); + let column = Column { + label: s!(""), + property: Property::Id, + }; + assert_eq!(task_column(&task, &column, &working_set), s!("1")); + + // get the task that's not in the working set, which should show + // a uuid for its id column + let task = replica.get_task(&uuids[1]).unwrap().unwrap(); + assert_eq!( + task_column(&task, &column, &working_set), + uuids[1].to_string() + ); + } + + #[test] + fn task_column_uuid() { + let mut replica = test_replica(); + create_tasks(&mut replica); + let working_set = WorkingSet::new(&mut replica).unwrap(); + + let task = replica.get_working_set_task(1).unwrap().unwrap(); + let column = Column { + label: s!(""), + property: Property::Uuid, + }; + assert_eq!( + task_column(&task, &column, &working_set), + task.get_uuid().to_string() + ); + } + + #[test] + fn task_column_active() { + let mut replica = test_replica(); + let uuids = create_tasks(&mut replica); + let working_set = WorkingSet::new(&mut replica).unwrap(); + + // make task A active + replica + .get_task(&uuids[0]) + .unwrap() + .unwrap() + .into_mut(&mut replica) + .start() + .unwrap(); + + let column = Column { + label: s!(""), + property: Property::Active, + }; + + let task = replica.get_working_set_task(1).unwrap().unwrap(); + assert_eq!(task_column(&task, &column, &working_set), s!("*")); + let task = replica.get_working_set_task(2).unwrap().unwrap(); + assert_eq!(task_column(&task, &column, &working_set), s!("")); + } + + #[test] + fn task_column_description() { + let mut replica = test_replica(); + create_tasks(&mut replica); + let working_set = WorkingSet::new(&mut replica).unwrap(); + + let task = replica.get_working_set_task(2).unwrap().unwrap(); + let column = Column { + label: s!(""), + property: Property::Description, + }; + assert_eq!(task_column(&task, &column, &working_set), s!("C")); + } + + #[test] + fn task_column_tags() { + let mut replica = test_replica(); + let uuids = create_tasks(&mut replica); + let working_set = WorkingSet::new(&mut replica).unwrap(); + + // add some tags to task A + let mut t1 = replica + .get_task(&uuids[0]) + .unwrap() + .unwrap() + .into_mut(&mut replica); + t1.add_tag(&("foo".try_into().unwrap())).unwrap(); + t1.add_tag(&("bar".try_into().unwrap())).unwrap(); + + let column = Column { + label: s!(""), + property: Property::Tags, + }; + + let task = replica.get_working_set_task(1).unwrap().unwrap(); + assert_eq!(task_column(&task, &column, &working_set), s!("+bar +foo")); + let task = replica.get_working_set_task(2).unwrap().unwrap(); + assert_eq!(task_column(&task, &column, &working_set), s!("")); + } +} +// TODO: test task_column diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 8997919de..8736a97da 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -148,7 +148,7 @@ pub struct Annotation { /// /// This struct contains only getters for various values on the task. The `into_mut` method returns /// a TaskMut which can be used to modify the task. -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Task { uuid: Uuid, taskmap: TaskMap, From 57834848f200ef96f4f4243c933d5df7e5ff69ee Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 26 Dec 2020 15:21:23 +0000 Subject: [PATCH 149/548] Default to a local server, so `task sync` works out of the box --- cli/src/invocation/mod.rs | 28 +++++++++++++++++++--------- cli/src/settings.rs | 15 ++++++++++++--- docs/src/usage.md | 24 +++++++++++++++++++++--- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index d7bcaef0a..9b9d7029c 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -106,15 +106,25 @@ fn get_replica(settings: &Config) -> Fallible { /// Get the server for this invocation fn get_server(settings: &Config) -> Fallible> { - let client_id = settings.get_str("server_client_id")?; - let client_id = Uuid::parse_str(&client_id)?; - let origin = settings.get_str("server_origin")?; - log::debug!("Using sync-server with origin {}", origin); - log::debug!("Sync client ID: {}", client_id); - Ok(server::from_config(ServerConfig::Remote { - origin, - client_id, - })?) + // if server_client_id and server_origin are both set, use + // the remote server + if let (Ok(client_id), Ok(origin)) = ( + settings.get_str("server_client_id"), + settings.get_str("server_origin"), + ) { + let client_id = Uuid::parse_str(&client_id)?; + + log::debug!("Using sync-server with origin {}", origin); + log::debug!("Sync client ID: {}", client_id); + Ok(server::from_config(ServerConfig::Remote { + origin, + client_id, + })?) + } else { + let server_dir = settings.get_str("server_dir")?.into(); + log::debug!("Using local sync-server at `{:?}`", server_dir); + Ok(server::from_config(ServerConfig::Local { server_dir })?) + } } /// Get a WriteColor implementation based on whether the output is a tty. diff --git a/cli/src/settings.rs b/cli/src/settings.rs index 02e554c76..4d59b00be 100644 --- a/cli/src/settings.rs +++ b/cli/src/settings.rs @@ -7,12 +7,21 @@ pub(crate) fn read_settings() -> Fallible { let mut settings = Config::default(); // set up defaults - if let Some(mut dir) = dirs::data_local_dir() { - dir.push("taskchampion"); + if let Some(dir) = dirs::data_local_dir() { + let mut tc_dir = dir.clone(); + tc_dir.push("taskchampion"); settings.set_default( "data_dir", // the config crate does not support non-string paths - dir.to_str().expect("data_local_dir is not utf-8"), + tc_dir.to_str().expect("data_local_dir is not utf-8"), + )?; + + let mut server_dir = dir; + server_dir.push("taskchampion-sync-server"); + settings.set_default( + "server_dir", + // the config crate does not support non-string paths + server_dir.to_str().expect("data_local_dir is not utf-8"), )?; } diff --git a/docs/src/usage.md b/docs/src/usage.md index 84ee5626c..5ddc60c91 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -17,15 +17,33 @@ On OS X, it's `~/Library/Preferences`. On Windows, it's `AppData/Roaming` in your home directory. The path can be overridden by setting `$TASKCHAMPION_CONFIG`. -Individual configuration parameters can be overridden by environemnt variables, converted to upper-case and prefixed with `TASKCHAMPION_`, e.g., `TASKCHAMPION_DATA_DIR`. +Individual configuration parameters can be overridden by environment variables, converted to upper-case and prefixed with `TASKCHAMPION_`, e.g., `TASKCHAMPION_DATA_DIR`. Nested configuration parameters cannot be overridden by environment variables. The following configuration parameters are available: * `data_dir` - path to a directory containing the replica's task data (which will be created if necessary). - Default: `taskchampion` in the local data directory -* `server_origin` - Origin of the taskchampion sync server, e.g., `https://taskchampion.example.com` + Default: `taskchampion` in the local data directory. +* `server_dir` - path to a directory containing the local server's data. + This is only used if `server_origin` or `server_client_id` are not set. + Default: `taskchampion-sync-server` in the local data directory. +* `server_origin` - Origin of the TaskChampion sync server, e.g., `https://taskchampion.example.com`. + If not set, then sync is done to a local server. * `server_client_id` - Client ID to identify this replica to the sync server (a UUID) + If not set, then sync is done to a local server. + +### Synchronization + +A TaskChampion replica "synchronizes" its local task database with other replicas via a sync server. +This operation is triggered by running `task sync`. +Typically this runs frequently in a cron task. +The operation is quick, especially if no changes have occurred. + +The replica expects to be synchronized frequently, even if no server is involved. +Without periodic syncs, the storage space used for the task database will grow quickly, and performance will suffer. + +By default, TaskChampion syncs to a "local server", as specified by the `server_dir` configuration parameter. +It is possible to switch to a remote server later by setting `server_origin` and `server_client_id` appropriately. ## `taskchampion-sync-server` From 64b38ee8149c2785c7f825f4707cd3de3fbf3196 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 26 Dec 2020 15:34:00 +0000 Subject: [PATCH 150/548] add some debug logging --- cli/src/invocation/filter.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index 881836009..f87263270 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -45,6 +45,7 @@ pub(super) fn filtered_tasks( // A list of IDs, but some are partial so we need to iterate over // all tasks and pattern-match their Uuids Universe::IdList(ref ids) if ids.iter().any(is_partial_uuid) => { + log::debug!("Scanning entire task database due to partial UUIDs in the filter"); 'task: for (uuid, task) in replica.all_tasks()?.drain() { for id in ids { let in_universe = match id { @@ -66,6 +67,7 @@ pub(super) fn filtered_tasks( // A list of full IDs, which we can fetch directly Universe::IdList(ref ids) => { + log::debug!("Scanning only the tasks specified in the filter"); // this is the only case where we might accidentally return the same task // several times, so we must track the seen tasks. let mut seen = HashSet::new(); @@ -93,6 +95,7 @@ pub(super) fn filtered_tasks( // All tasks -- iterate over the full set Universe::AllTasks => { + log::debug!("Scanning all tasks in the task database"); for (_, task) in replica.all_tasks()?.drain() { if match_task(filter, &task) { res.push(task); @@ -102,6 +105,7 @@ pub(super) fn filtered_tasks( // Pending tasks -- just scan the working set Universe::PendingTasks => { + log::debug!("Scanning only the working set (pending tasks)"); for task in replica.working_set()?.drain(..) { if let Some(task) = task { if match_task(filter, &task) { From 6b70b47aa0161a7339595aa818e4fa5b93b4b3fb Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 26 Dec 2020 16:03:57 +0000 Subject: [PATCH 151/548] specify version ranges --- cli/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 025ee0715..49a7e061e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,9 +11,9 @@ failure = "^0.1.8" log = "^0.4.11" nom = "^6.0.1" prettytable-rs = "^0.8.0" -textwrap = { version="0.12.1", features=["terminal_size"] } -termcolor = "1.1.2" -atty = "0.2.14" +textwrap = { version="^0.12.1", features=["terminal_size"] } +termcolor = "^1.1.2" +atty = "^0.2.14" [dependencies.config] default-features = false From a8d45c67c65c6c36b0e2976495f08f86806a601c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 26 Dec 2020 16:37:31 +0000 Subject: [PATCH 152/548] Encrypt content sent to the server This implements client-side encryption, so that users' task information is not availble to the server (or to anyone who does not have the `encryption_secret`). --- Cargo.lock | 19 +++ cli/src/invocation/mod.rs | 6 +- docs/src/usage.md | 23 ++- taskchampion/Cargo.toml | 1 + taskchampion/src/config.rs | 4 + taskchampion/src/server/mod.rs | 8 +- taskchampion/src/server/remote/crypto.rs | 131 ++++++++++++++++++ .../src/server/{remote.rs => remote/mod.rs} | 40 +++--- 8 files changed, 206 insertions(+), 26 deletions(-) create mode 100644 taskchampion/src/server/remote/crypto.rs rename taskchampion/src/server/{remote.rs => remote/mod.rs} (75%) diff --git a/Cargo.lock b/Cargo.lock index e04f55e23..7087bbabf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1642,6 +1642,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "protobuf" +version = "2.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da78e04bc0e40f36df43ecc6575e4f4b180e8156c4efd73f13d5619479b05696" + [[package]] name = "publicsuffix" version = "1.5.4" @@ -2255,6 +2261,7 @@ dependencies = [ "serde", "serde_json", "tempdir", + "tindercrypt", "ureq", "uuid", ] @@ -2456,6 +2463,18 @@ dependencies = [ "syn", ] +[[package]] +name = "tindercrypt" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34fd5cc8db265f27abf29e8a3cec5cc643ae9f3f9ae39f08a77dc4add375b6d" +dependencies = [ + "protobuf", + "rand 0.7.3", + "ring", + "thiserror", +] + [[package]] name = "tinyvec" version = "1.1.0" diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 9b9d7029c..194fc9cfc 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -2,7 +2,7 @@ use crate::argparse::{Command, Subcommand}; use config::Config; -use failure::Fallible; +use failure::{format_err, Fallible}; use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Uuid}; use termcolor::{ColorChoice, StandardStream}; @@ -113,12 +113,16 @@ fn get_server(settings: &Config) -> Fallible> { settings.get_str("server_origin"), ) { let client_id = Uuid::parse_str(&client_id)?; + let encryption_secret = settings + .get_str("encryption_secret") + .map_err(|_| format_err!("Could not read `encryption_secret` configuration"))?; log::debug!("Using sync-server with origin {}", origin); log::debug!("Sync client ID: {}", client_id); Ok(server::from_config(ServerConfig::Remote { origin, client_id, + encryption_secret: encryption_secret.as_bytes().to_vec(), })?) } else { let server_dir = settings.get_str("server_dir")?.into(); diff --git a/docs/src/usage.md b/docs/src/usage.md index 5ddc60c91..39cf18bf7 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -27,6 +27,11 @@ The following configuration parameters are available: * `server_dir` - path to a directory containing the local server's data. This is only used if `server_origin` or `server_client_id` are not set. Default: `taskchampion-sync-server` in the local data directory. +* `encryption_secret` - Secret value used to encrypt all data stored on the server. + This should be a long random string. + If you have `openssl` installed, a command like `openssl rand -hex 35` will generate a suitable value. + This value is only used when synchronizing with a remote server -- local servers are unencrypted. + Treat this value as a password. * `server_origin` - Origin of the TaskChampion sync server, e.g., `https://taskchampion.example.com`. If not set, then sync is done to a local server. * `server_client_id` - Client ID to identify this replica to the sync server (a UUID) @@ -34,16 +39,26 @@ The following configuration parameters are available: ### Synchronization -A TaskChampion replica "synchronizes" its local task database with other replicas via a sync server. +A single TaskChampion task database is known as a "replica". +A replica "synchronizes" its local information with other replicas via a sync server. +Many replicas can thus share the same task history. + This operation is triggered by running `task sync`. Typically this runs frequently in a cron task. -The operation is quick, especially if no changes have occurred. +Synchronization is quick, especially if no changes have occurred. -The replica expects to be synchronized frequently, even if no server is involved. +Each replica expects to be synchronized frequently, even if no server is involved. Without periodic syncs, the storage space used for the task database will grow quickly, and performance will suffer. By default, TaskChampion syncs to a "local server", as specified by the `server_dir` configuration parameter. -It is possible to switch to a remote server later by setting `server_origin` and `server_client_id` appropriately. +Every replica sharing a task history should have precisely the same configuration for `server_origin`, `server_client_id`, and `encryption_secret`. + +Synchronizing a new replica to an existing task history is easy: begin with an empty replica, configured for the remote server, and run `task sync`. +The replica will download the entire task history. + +It is possible to switch a single replica to a remote server by simply configuring for the remote server and running `task sync`. +The replica will upload the entire task history to the server. +Once this is complete, additional replicas can be configured with the same settings in order to share the task history. ## `taskchampion-sync-server` diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index fe0899d00..1781183d4 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -20,6 +20,7 @@ kv = {version = "^0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "^0.12.3"} ureq = "^1.5.2" log = "^0.4.11" +tindercrypt = { version = "^0.2.2", default-features = false } [dev-dependencies] proptest = "^0.9.4" diff --git a/taskchampion/src/config.rs b/taskchampion/src/config.rs index 8a8ee5bcd..7347bcd5a 100644 --- a/taskchampion/src/config.rs +++ b/taskchampion/src/config.rs @@ -22,5 +22,9 @@ pub enum ServerConfig { /// Client ID to identify this replica to the server client_id: Uuid, + + /// Private encryption secret used to encrypt all data sent to the server. This can + /// be any suitably un-guessable string of bytes. + encryption_secret: Vec, }, } diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 936e441d8..4d214ee7e 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -16,8 +16,10 @@ pub use types::*; pub fn from_config(config: ServerConfig) -> Fallible> { Ok(match config { ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), - ServerConfig::Remote { origin, client_id } => { - Box::new(RemoteServer::new(origin, client_id)) - } + ServerConfig::Remote { + origin, + client_id, + encryption_secret, + } => Box::new(RemoteServer::new(origin, client_id, encryption_secret)), }) } diff --git a/taskchampion/src/server/remote/crypto.rs b/taskchampion/src/server/remote/crypto.rs new file mode 100644 index 000000000..89dc91398 --- /dev/null +++ b/taskchampion/src/server/remote/crypto.rs @@ -0,0 +1,131 @@ +use crate::server::HistorySegment; +use failure::{format_err, Fallible}; +use std::convert::TryFrom; +use std::io::Read; +use tindercrypt::cryptors::RingCryptor; +use uuid::Uuid; + +pub(super) struct Secret(pub(super) Vec); + +impl From> for Secret { + fn from(bytes: Vec) -> Self { + Self(bytes) + } +} + +impl AsRef<[u8]> for Secret { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// A cleartext payload containing a history segment. +pub(super) struct HistoryCleartext { + pub(super) parent_version_id: Uuid, + pub(super) history_segment: HistorySegment, +} + +impl HistoryCleartext { + /// Seal the payload into its ciphertext + pub(super) fn seal(self, secret: &Secret) -> Fallible { + let cryptor = RingCryptor::new().with_aad(self.parent_version_id.as_bytes()); + let ciphertext = cryptor.seal_with_passphrase(secret.as_ref(), &self.history_segment)?; + Ok(HistoryCiphertext(ciphertext)) + } +} + +/// An ecrypted payload containing a history segment +pub(super) struct HistoryCiphertext(pub(super) Vec); + +impl HistoryCiphertext { + pub(super) fn open( + self, + secret: &Secret, + parent_version_id: Uuid, + ) -> Fallible { + let cryptor = RingCryptor::new().with_aad(parent_version_id.as_bytes()); + let plaintext = cryptor.open(secret.as_ref(), &self.0)?; + + Ok(HistoryCleartext { + parent_version_id, + history_segment: plaintext, + }) + } +} + +impl TryFrom for HistoryCiphertext { + type Error = failure::Error; + + fn try_from(resp: ureq::Response) -> Result { + if let Some("application/vnd.taskchampion.history-segment") = resp.header("Content-Type") { + let mut reader = resp.into_reader(); + let mut bytes = vec![]; + reader.read_to_end(&mut bytes)?; + Ok(Self(bytes)) + } else { + Err(format_err!("Response did not have expected content-type")) + } + } +} + +impl AsRef<[u8]> for HistoryCiphertext { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn round_trip() { + let parent_version_id = Uuid::new_v4(); + let history_segment = b"HISTORY REPEATS ITSELF".to_vec(); + let secret = Secret(b"SEKRIT".to_vec()); + + let history_cleartext = HistoryCleartext { + parent_version_id, + history_segment: history_segment.clone(), + }; + let history_ciphertext = history_cleartext.seal(&secret).unwrap(); + let history_cleartext = history_ciphertext.open(&secret, parent_version_id).unwrap(); + + assert_eq!(history_cleartext.history_segment, history_segment); + assert_eq!(history_cleartext.parent_version_id, parent_version_id); + } + + #[test] + fn round_trip_bad_key() { + let parent_version_id = Uuid::new_v4(); + let history_segment = b"HISTORY REPEATS ITSELF".to_vec(); + let secret = Secret(b"SEKRIT".to_vec()); + + let history_cleartext = HistoryCleartext { + parent_version_id, + history_segment: history_segment.clone(), + }; + let history_ciphertext = history_cleartext.seal(&secret).unwrap(); + + let secret = Secret(b"BADSEKRIT".to_vec()); + assert!(history_ciphertext.open(&secret, parent_version_id).is_err()); + } + + #[test] + fn round_trip_bad_pvid() { + let parent_version_id = Uuid::new_v4(); + let history_segment = b"HISTORY REPEATS ITSELF".to_vec(); + let secret = Secret(b"SEKRIT".to_vec()); + + let history_cleartext = HistoryCleartext { + parent_version_id, + history_segment: history_segment.clone(), + }; + let history_ciphertext = history_cleartext.seal(&secret).unwrap(); + + let bad_parent_version_id = Uuid::new_v4(); + assert!(history_ciphertext + .open(&secret, bad_parent_version_id) + .is_err()); + } +} diff --git a/taskchampion/src/server/remote.rs b/taskchampion/src/server/remote/mod.rs similarity index 75% rename from taskchampion/src/server/remote.rs rename to taskchampion/src/server/remote/mod.rs index 9392e055f..51da87072 100644 --- a/taskchampion/src/server/remote.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -1,11 +1,15 @@ use crate::server::{AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId}; use failure::{format_err, Fallible}; -use std::io::Read; +use std::convert::TryInto; use uuid::Uuid; +mod crypto; +use crypto::{HistoryCiphertext, HistoryCleartext, Secret}; + pub struct RemoteServer { origin: String, client_id: Uuid, + encryption_secret: Secret, agent: ureq::Agent, } @@ -16,10 +20,11 @@ impl RemoteServer { /// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_id to /// identify this client to the server. Multiple replicas synchronizing the same task history /// should use the same client_id. - pub fn new(origin: String, client_id: Uuid) -> RemoteServer { + pub fn new(origin: String, client_id: Uuid, encryption_secret: Vec) -> RemoteServer { RemoteServer { origin, client_id, + encryption_secret: encryption_secret.into(), agent: ureq::agent(), } } @@ -45,18 +50,6 @@ fn get_uuid_header(resp: &ureq::Response, name: &str) -> Fallible { Ok(value) } -/// Get the body of a request as a HistorySegment -fn into_body(resp: ureq::Response) -> Fallible { - if let Some("application/vnd.taskchampion.history-segment") = resp.header("Content-Type") { - let mut reader = resp.into_reader(); - let mut bytes = vec![]; - reader.read_to_end(&mut bytes)?; - Ok(bytes) - } else { - Err(format_err!("Response did not have expected content-type")) - } -} - impl Server for RemoteServer { fn add_version( &mut self, @@ -67,6 +60,11 @@ impl Server for RemoteServer { "{}/client/{}/add-version/{}", self.origin, self.client_id, parent_version_id ); + let history_cleartext = HistoryCleartext { + parent_version_id, + history_segment, + }; + let history_ciphertext = history_cleartext.seal(&self.encryption_secret)?; let resp = self .agent .post(&url) @@ -76,7 +74,7 @@ impl Server for RemoteServer { "Content-Type", "application/vnd.taskchampion.history-segment", ) - .send_bytes(&history_segment); + .send_bytes(history_ciphertext.as_ref()); if resp.ok() { let version_id = get_uuid_header(&resp, "X-Version-Id")?; Ok(AddVersionResult::Ok(version_id)) @@ -101,10 +99,16 @@ impl Server for RemoteServer { .call(); if resp.ok() { + let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; + let version_id = get_uuid_header(&resp, "X-Version-Id")?; + let history_ciphertext: HistoryCiphertext = resp.try_into()?; + let history_segment = history_ciphertext + .open(&self.encryption_secret, parent_version_id)? + .history_segment; Ok(GetVersionResult::Version { - version_id: get_uuid_header(&resp, "X-Version-Id")?, - parent_version_id: get_uuid_header(&resp, "X-Parent-Version-Id")?, - history_segment: into_body(resp)?, + version_id, + parent_version_id, + history_segment, }) } else if resp.status() == 404 { Ok(GetVersionResult::NoSuchVersion) From e555af8895c564cfbfc394ad61ce0129d486dac1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 27 Dec 2020 22:52:01 +0000 Subject: [PATCH 153/548] add request logging --- sync-server/src/main.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index c69ac6e77..28481d3e7 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,7 +1,7 @@ #![deny(clippy::all)] use crate::storage::{KVStorage, Storage}; -use actix_web::{get, web, App, HttpServer, Responder, Scope}; +use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder, Scope}; use api::{api_scope, ServerState}; use clap::Arg; use failure::Fallible; @@ -61,10 +61,14 @@ async fn main() -> Fallible<()> { let server_state = ServerState::new(server_box); log::warn!("Serving on port {}", port); - HttpServer::new(move || App::new().service(app_scope(server_state.clone()))) - .bind(format!("0.0.0.0:{}", port))? - .run() - .await?; + HttpServer::new(move || { + App::new() + .wrap(Logger::default()) + .service(app_scope(server_state.clone())) + }) + .bind(format!("0.0.0.0:{}", port))? + .run() + .await?; Ok(()) } From 92d629522ba8955c38b865ea608a27f180cf7fa6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 28 Dec 2020 21:31:02 +0000 Subject: [PATCH 154/548] rename client id -> client key --- cli/src/invocation/mod.rs | 8 +-- docs/src/sync-protocol.md | 4 +- sync-server/src/api/add_version.rs | 34 ++++++------- sync-server/src/api/get_child_version.rs | 28 +++++------ sync-server/src/server.rs | 46 ++++++++--------- sync-server/src/storage/inmemory.rs | 30 +++++------ sync-server/src/storage/kv.rs | 64 ++++++++++++------------ sync-server/src/storage/mod.rs | 10 ++-- taskchampion/src/config.rs | 4 +- taskchampion/src/server/mod.rs | 4 +- taskchampion/src/server/remote/mod.rs | 14 +++--- 11 files changed, 124 insertions(+), 122 deletions(-) diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 194fc9cfc..685c96cf2 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -108,20 +108,20 @@ fn get_replica(settings: &Config) -> Fallible { fn get_server(settings: &Config) -> Fallible> { // if server_client_id and server_origin are both set, use // the remote server - if let (Ok(client_id), Ok(origin)) = ( + if let (Ok(client_key), Ok(origin)) = ( settings.get_str("server_client_id"), settings.get_str("server_origin"), ) { - let client_id = Uuid::parse_str(&client_id)?; + let client_key = Uuid::parse_str(&client_key)?; let encryption_secret = settings .get_str("encryption_secret") .map_err(|_| format_err!("Could not read `encryption_secret` configuration"))?; log::debug!("Using sync-server with origin {}", origin); - log::debug!("Sync client ID: {}", client_id); + log::debug!("Sync client ID: {}", client_key); Ok(server::from_config(ServerConfig::Remote { origin, - client_id, + client_key, encryption_secret: encryption_secret.as_bytes().to_vec(), })?) } else { diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md index 9a5caa247..1a4799630 100644 --- a/docs/src/sync-protocol.md +++ b/docs/src/sync-protocol.md @@ -9,6 +9,8 @@ The protocol builds on the model presented in the previous chapter, and in parti From the server's perspective, replicas are indistinguishable, so this protocol uses the term "client" to refer generically to all replicas replicating a single task history. +Each client is identified and authenticated with a "client key", known only to the server and to the replicas replicating the task history. + ## Server For each client, the server is responsible for storing the task history, in the form of a branch-free sequence of versions. @@ -66,7 +68,7 @@ If not found, the server returns a negative response. The transactions above are realized for an HTTP server at `` using the HTTP requests and responses described here. The `origin` *should* be an HTTPS endpoint on general principle, but nothing in the functonality or security of the protocol depends on connection encryption. -The replica identifies itself to the server using a `clientId` in the form of a UUID. +The replica identifies itself to the server using a `clientKey` in the form of a UUID. ### AddVersion diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 10a36155b..fcb45fb10 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -2,7 +2,7 @@ use crate::api::{ failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{add_version, AddVersionResult, ClientId, VersionId, NO_VERSION_ID}; +use crate::server::{add_version, AddVersionResult, ClientKey, VersionId, NO_VERSION_ID}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use futures::StreamExt; @@ -19,11 +19,11 @@ const MAX_SIZE: usize = 100 * 1024 * 1024; /// parent version ID in the `X-Parent-Version-Id` header. /// /// Returns other 4xx or 5xx responses on other errors. -#[post("/client/{client_id}/add-version/{parent_version_id}")] +#[post("/client/{client_key}/add-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, server_state: web::Data, - web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, + web::Path((client_key, parent_version_id)): web::Path<(ClientKey, VersionId)>, mut payload: web::Payload, ) -> Result { // check content-type @@ -52,16 +52,16 @@ pub(crate) async fn service( let mut txn = server_state.txn().map_err(failure_to_ise)?; // get, or create, the client - let client = match txn.get_client(client_id).map_err(failure_to_ise)? { + let client = match txn.get_client(client_key).map_err(failure_to_ise)? { Some(client) => client, None => { - txn.new_client(client_id, NO_VERSION_ID) + txn.new_client(client_key, NO_VERSION_ID) .map_err(failure_to_ise)?; - txn.get_client(client_id).map_err(failure_to_ise)?.unwrap() + txn.get_client(client_key).map_err(failure_to_ise)?.unwrap() } }; - let result = add_version(txn, client_id, client, parent_version_id, body.to_vec()) + let result = add_version(txn, client_key, client, parent_version_id, body.to_vec()) .map_err(failure_to_ise)?; Ok(match result { AddVersionResult::Ok(version_id) => HttpResponse::Ok() @@ -83,7 +83,7 @@ mod test { #[actix_rt::test] async fn test_success() { - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let server_box: Box = Box::new(InMemoryStorage::new()); @@ -91,13 +91,13 @@ mod test { // set up the storage contents.. { let mut txn = server_box.txn().unwrap(); - txn.new_client(client_id, Uuid::nil()).unwrap(); + txn.new_client(client_key, Uuid::nil()).unwrap(); } let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( @@ -119,7 +119,7 @@ mod test { #[actix_rt::test] async fn test_conflict() { - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let server_box: Box = Box::new(InMemoryStorage::new()); @@ -127,13 +127,13 @@ mod test { // set up the storage contents.. { let mut txn = server_box.txn().unwrap(); - txn.new_client(client_id, version_id).unwrap(); + txn.new_client(client_key, version_id).unwrap(); } let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( @@ -153,13 +153,13 @@ mod test { #[actix_rt::test] async fn test_bad_content_type() { - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let server_box: Box = Box::new(InMemoryStorage::new()); let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header("Content-Type", "not/correct") @@ -171,13 +171,13 @@ mod test { #[actix_rt::test] async fn test_empty_body() { - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let server_box: Box = Box::new(InMemoryStorage::new()); let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 901c46440..84a9fafa3 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -2,7 +2,7 @@ use crate::api::{ failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{get_child_version, ClientId, VersionId}; +use crate::server::{get_child_version, ClientKey, VersionId}; use actix_web::{error, get, web, HttpResponse, Result}; /// Get a child version. @@ -13,18 +13,18 @@ use actix_web::{error, get, web, HttpResponse, Result}; /// /// If no such child exists, returns a 404 with no content. /// Returns other 4xx or 5xx responses on other errors. -#[get("/client/{client_id}/get-child-version/{parent_version_id}")] +#[get("/client/{client_key}/get-child-version/{parent_version_id}")] pub(crate) async fn service( server_state: web::Data, - web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, + web::Path((client_key, parent_version_id)): web::Path<(ClientKey, VersionId)>, ) -> Result { let mut txn = server_state.txn().map_err(failure_to_ise)?; - txn.get_client(client_id) + txn.get_client(client_key) .map_err(failure_to_ise)? .ok_or_else(|| error::ErrorNotFound("no such client"))?; - let result = get_child_version(txn, client_id, parent_version_id).map_err(failure_to_ise)?; + let result = get_child_version(txn, client_key, parent_version_id).map_err(failure_to_ise)?; if let Some(result) = result { Ok(HttpResponse::Ok() .content_type(HISTORY_SEGMENT_CONTENT_TYPE) @@ -49,7 +49,7 @@ mod test { #[actix_rt::test] async fn test_success() { - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let server_box: Box = Box::new(InMemoryStorage::new()); @@ -57,8 +57,8 @@ mod test { // set up the storage contents.. { let mut txn = server_box.txn().unwrap(); - txn.new_client(client_id, Uuid::new_v4()).unwrap(); - txn.add_version(client_id, version_id, parent_version_id, b"abcd".to_vec()) + txn.new_client(client_key, Uuid::new_v4()).unwrap(); + txn.add_version(client_key, version_id, parent_version_id, b"abcd".to_vec()) .unwrap(); } @@ -67,7 +67,7 @@ mod test { let uri = format!( "/client/{}/get-child-version/{}", - client_id, parent_version_id + client_key, parent_version_id ); let req = test::TestRequest::get().uri(&uri).to_request(); let mut resp = test::call_service(&mut app, req).await; @@ -92,7 +92,7 @@ mod test { #[actix_rt::test] async fn test_client_not_found() { - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let server_box: Box = Box::new(InMemoryStorage::new()); let server_state = ServerState::new(server_box); @@ -100,7 +100,7 @@ mod test { let uri = format!( "/client/{}/get-child-version/{}", - client_id, parent_version_id + client_key, parent_version_id ); let req = test::TestRequest::get().uri(&uri).to_request(); let resp = test::call_service(&mut app, req).await; @@ -111,21 +111,21 @@ mod test { #[actix_rt::test] async fn test_version_not_found() { - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let server_box: Box = Box::new(InMemoryStorage::new()); // create the client, but not the version { let mut txn = server_box.txn().unwrap(); - txn.new_client(client_id, Uuid::new_v4()).unwrap(); + txn.new_client(client_key, Uuid::new_v4()).unwrap(); } let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; let uri = format!( "/client/{}/get-child-version/{}", - client_id, parent_version_id + client_key, parent_version_id ); let req = test::TestRequest::get().uri(&uri).to_request(); let resp = test::call_service(&mut app, req).await; diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index a518189ce..6c0e183cc 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -8,7 +8,7 @@ use uuid::Uuid; pub const NO_VERSION_ID: VersionId = Uuid::nil(); pub(crate) type HistorySegment = Vec; -pub(crate) type ClientId = Uuid; +pub(crate) type ClientKey = Uuid; pub(crate) type VersionId = Uuid; /// Response to get_child_version @@ -21,11 +21,11 @@ pub(crate) struct GetVersionResult { pub(crate) fn get_child_version<'a>( mut txn: Box, - client_id: ClientId, + client_key: ClientKey, parent_version_id: VersionId, ) -> Fallible> { Ok(txn - .get_version_by_parent(client_id, parent_version_id)? + .get_version_by_parent(client_key, parent_version_id)? .map(|version| GetVersionResult { version_id: version.version_id, parent_version_id: version.parent_version_id, @@ -44,14 +44,14 @@ pub(crate) enum AddVersionResult { pub(crate) fn add_version<'a>( mut txn: Box, - client_id: ClientId, + client_key: ClientKey, client: Client, parent_version_id: VersionId, history_segment: HistorySegment, ) -> Fallible { log::debug!( - "add_version(client_id: {}, parent_version_id: {})", - client_id, + "add_version(client_key: {}, parent_version_id: {})", + client_key, parent_version_id, ); @@ -71,8 +71,8 @@ pub(crate) fn add_version<'a>( ); // update the DB - txn.add_version(client_id, version_id, parent_version_id, history_segment)?; - txn.set_client_latest_version_id(client_id, version_id)?; + txn.add_version(client_key, version_id, parent_version_id, history_segment)?; + txn.set_client_latest_version_id(client_key, version_id)?; txn.commit()?; Ok(AddVersionResult::Ok(version_id)) @@ -87,9 +87,9 @@ mod test { fn gcv_not_found() -> Fallible<()> { let storage = InMemoryStorage::new(); let txn = storage.txn()?; - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - assert_eq!(get_child_version(txn, client_id, parent_version_id)?, None); + assert_eq!(get_child_version(txn, client_key, parent_version_id)?, None); Ok(()) } @@ -97,20 +97,20 @@ mod test { fn gcv_found() -> Fallible<()> { let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let history_segment = b"abcd".to_vec(); txn.add_version( - client_id, + client_key, version_id, parent_version_id, history_segment.clone(), )?; assert_eq!( - get_child_version(txn, client_id, parent_version_id)?, + get_child_version(txn, client_key, parent_version_id)?, Some(GetVersionResult { version_id, parent_version_id, @@ -124,7 +124,7 @@ mod test { fn av_conflict() -> Fallible<()> { let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let history_segment = b"abcd".to_vec(); let existing_parent_version_id = Uuid::new_v4(); @@ -135,7 +135,7 @@ mod test { assert_eq!( add_version( txn, - client_id, + client_key, client, parent_version_id, history_segment.clone() @@ -145,9 +145,9 @@ mod test { // verify that the storage wasn't updated txn = storage.txn()?; - assert_eq!(txn.get_client(client_id)?, None); + assert_eq!(txn.get_client(client_key)?, None); assert_eq!( - txn.get_version_by_parent(client_id, parent_version_id)?, + txn.get_version_by_parent(client_key, parent_version_id)?, None ); @@ -157,7 +157,7 @@ mod test { fn test_av_success(latest_version_id_nil: bool) -> Fallible<()> { let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let history_segment = b"abcd".to_vec(); let latest_version_id = if latest_version_id_nil { @@ -166,12 +166,12 @@ mod test { parent_version_id }; - txn.new_client(client_id, latest_version_id)?; - let client = txn.get_client(client_id)?.unwrap(); + txn.new_client(client_key, latest_version_id)?; + let client = txn.get_client(client_key)?.unwrap(); let result = add_version( txn, - client_id, + client_key, client, parent_version_id, history_segment.clone(), @@ -182,10 +182,10 @@ mod test { // verify that the storage was updated txn = storage.txn()?; - let client = txn.get_client(client_id)?.unwrap(); + let client = txn.get_client(client_key)?.unwrap(); assert_eq!(client.latest_version_id, new_version_id); let version = txn - .get_version_by_parent(client_id, parent_version_id)? + .get_version_by_parent(client_key, parent_version_id)? .unwrap(); assert_eq!(version.version_id, new_version_id); assert_eq!(version.parent_version_id, parent_version_id); diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs index 68f25cf92..fbd56db65 100644 --- a/sync-server/src/storage/inmemory.rs +++ b/sync-server/src/storage/inmemory.rs @@ -4,10 +4,10 @@ use std::collections::HashMap; use std::sync::{Mutex, MutexGuard}; struct Inner { - /// Clients, indexed by client_id + /// Clients, indexed by client_key clients: HashMap, - /// Versions, indexed by (client_id, parent_version_id) + /// Versions, indexed by (client_key, parent_version_id) versions: HashMap<(Uuid, Uuid), Version>, } @@ -34,48 +34,48 @@ impl Storage for InMemoryStorage { } impl<'a> StorageTxn for InnerTxn<'a> { - fn get_client(&mut self, client_id: Uuid) -> Fallible> { - Ok(self.0.clients.get(&client_id).cloned()) + fn get_client(&mut self, client_key: Uuid) -> Fallible> { + Ok(self.0.clients.get(&client_key).cloned()) } - fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()> { - if self.0.clients.get(&client_id).is_some() { - return Err(format_err!("Client {} already exists", client_id)); + fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> Fallible<()> { + if self.0.clients.get(&client_key).is_some() { + return Err(format_err!("Client {} already exists", client_key)); } self.0 .clients - .insert(client_id, Client { latest_version_id }); + .insert(client_key, Client { latest_version_id }); Ok(()) } fn set_client_latest_version_id( &mut self, - client_id: Uuid, + client_key: Uuid, latest_version_id: Uuid, ) -> Fallible<()> { - if let Some(client) = self.0.clients.get_mut(&client_id) { + if let Some(client) = self.0.clients.get_mut(&client_key) { client.latest_version_id = latest_version_id; Ok(()) } else { - Err(format_err!("Client {} does not exist", client_id)) + Err(format_err!("Client {} does not exist", client_key)) } } fn get_version_by_parent( &mut self, - client_id: Uuid, + client_key: Uuid, parent_version_id: Uuid, ) -> Fallible> { Ok(self .0 .versions - .get(&(client_id, parent_version_id)) + .get(&(client_key, parent_version_id)) .cloned()) } fn add_version( &mut self, - client_id: Uuid, + client_key: Uuid, version_id: Uuid, parent_version_id: Uuid, history_segment: Vec, @@ -88,7 +88,7 @@ impl<'a> StorageTxn for InnerTxn<'a> { }; self.0 .versions - .insert((client_id, version.parent_version_id), version); + .insert((client_key, version.parent_version_id), version); Ok(()) } diff --git a/sync-server/src/storage/kv.rs b/sync-server/src/storage/kv.rs index d258da8f3..f3cc178b2 100644 --- a/sync-server/src/storage/kv.rs +++ b/sync-server/src/storage/kv.rs @@ -4,28 +4,28 @@ use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Serde, Store, ValueBuf}; use std::path::Path; -/// Key for versions: concatenation of client_id and parent_version_id -type VersionKey = [u8; 32]; +/// DB Key for versions: concatenation of client_key and parent_version_id +type VersionDbKey = [u8; 32]; -fn version_key(client_id: Uuid, parent_version_id: Uuid) -> VersionKey { +fn version_db_key(client_key: Uuid, parent_version_id: Uuid) -> VersionDbKey { let mut key = [0u8; 32]; - key[..16].clone_from_slice(client_id.as_bytes()); + key[..16].clone_from_slice(client_key.as_bytes()); key[16..].clone_from_slice(parent_version_id.as_bytes()); key } -/// Key for clients: just the client_id -type ClientKey = [u8; 16]; +/// Key for clients: just the client_key +type ClientDbKey = [u8; 16]; -fn client_key(client_id: Uuid) -> ClientKey { - *client_id.as_bytes() +fn client_db_key(client_key: Uuid) -> ClientDbKey { + *client_key.as_bytes() } /// KVStorage is an on-disk storage backend which uses LMDB via the `kv` crate. pub(crate) struct KVStorage<'t> { store: Store, - clients_bucket: Bucket<'t, ClientKey, ValueBuf>>, - versions_bucket: Bucket<'t, VersionKey, ValueBuf>>, + clients_bucket: Bucket<'t, ClientDbKey, ValueBuf>>, + versions_bucket: Bucket<'t, VersionDbKey, ValueBuf>>, } impl<'t> KVStorage<'t> { @@ -37,9 +37,9 @@ impl<'t> KVStorage<'t> { let store = Store::new(config)?; let clients_bucket = - store.bucket::>>(Some("clients"))?; + store.bucket::>>(Some("clients"))?; let versions_bucket = - store.bucket::>>(Some("versions"))?; + store.bucket::>>(Some("versions"))?; Ok(KVStorage { store, @@ -73,17 +73,17 @@ impl<'t> Txn<'t> { } } - fn clients_bucket(&self) -> &'t Bucket<'t, ClientKey, ValueBuf>> { + fn clients_bucket(&self) -> &'t Bucket<'t, ClientDbKey, ValueBuf>> { &self.storage.clients_bucket } - fn versions_bucket(&self) -> &'t Bucket<'t, VersionKey, ValueBuf>> { + fn versions_bucket(&self) -> &'t Bucket<'t, VersionDbKey, ValueBuf>> { &self.storage.versions_bucket } } impl<'t> StorageTxn for Txn<'t> { - fn get_client(&mut self, client_id: Uuid) -> Fallible> { - let key = client_key(client_id); + fn get_client(&mut self, client_key: Uuid) -> Fallible> { + let key = client_db_key(client_key); let bucket = self.clients_bucket(); let kvtxn = self.kvtxn(); @@ -97,8 +97,8 @@ impl<'t> StorageTxn for Txn<'t> { Ok(Some(client)) } - fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()> { - let key = client_key(client_id); + fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> Fallible<()> { + let key = client_db_key(client_key); let bucket = self.clients_bucket(); let kvtxn = self.kvtxn(); let client = Client { latest_version_id }; @@ -108,19 +108,19 @@ impl<'t> StorageTxn for Txn<'t> { fn set_client_latest_version_id( &mut self, - client_id: Uuid, + client_key: Uuid, latest_version_id: Uuid, ) -> Fallible<()> { // implementation is the same as new_client.. - self.new_client(client_id, latest_version_id) + self.new_client(client_key, latest_version_id) } fn get_version_by_parent( &mut self, - client_id: Uuid, + client_key: Uuid, parent_version_id: Uuid, ) -> Fallible> { - let key = version_key(client_id, parent_version_id); + let key = version_db_key(client_key, parent_version_id); let bucket = self.versions_bucket(); let kvtxn = self.kvtxn(); let version = match kvtxn.get(&bucket, key) { @@ -135,12 +135,12 @@ impl<'t> StorageTxn for Txn<'t> { fn add_version( &mut self, - client_id: Uuid, + client_key: Uuid, version_id: Uuid, parent_version_id: Uuid, history_segment: Vec, ) -> Fallible<()> { - let key = version_key(client_id, parent_version_id); + let key = version_db_key(client_key, parent_version_id); let bucket = self.versions_bucket(); let kvtxn = self.kvtxn(); let version = Version { @@ -184,17 +184,17 @@ mod test { let storage = KVStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let latest_version_id = Uuid::new_v4(); - txn.new_client(client_id, latest_version_id)?; + txn.new_client(client_key, latest_version_id)?; - let client = txn.get_client(client_id)?.unwrap(); + let client = txn.get_client(client_key)?.unwrap(); assert_eq!(client.latest_version_id, latest_version_id); let latest_version_id = Uuid::new_v4(); - txn.set_client_latest_version_id(client_id, latest_version_id)?; + txn.set_client_latest_version_id(client_key, latest_version_id)?; - let client = txn.get_client(client_id)?.unwrap(); + let client = txn.get_client(client_key)?.unwrap(); assert_eq!(client.latest_version_id, latest_version_id); Ok(()) @@ -216,18 +216,18 @@ mod test { let storage = KVStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; - let client_id = Uuid::new_v4(); + let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let history_segment = b"abc".to_vec(); txn.add_version( - client_id, + client_key, version_id, parent_version_id, history_segment.clone(), )?; let version = txn - .get_version_by_parent(client_id, parent_version_id)? + .get_version_by_parent(client_key, parent_version_id)? .unwrap(); assert_eq!( diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 2a95917b9..c02d24fba 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -24,29 +24,29 @@ pub(crate) struct Version { pub(crate) trait StorageTxn { /// Get information about the given client - fn get_client(&mut self, client_id: Uuid) -> Fallible>; + fn get_client(&mut self, client_key: Uuid) -> Fallible>; /// Create a new client with the given latest_version_id - fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()>; + fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> Fallible<()>; /// Set the client's latest_version_id fn set_client_latest_version_id( &mut self, - client_id: Uuid, + client_key: Uuid, latest_version_id: Uuid, ) -> Fallible<()>; /// Get a version, indexed by parent version id fn get_version_by_parent( &mut self, - client_id: Uuid, + client_key: Uuid, parent_version_id: Uuid, ) -> Fallible>; /// Add a version (that must not already exist) fn add_version( &mut self, - client_id: Uuid, + client_key: Uuid, version_id: Uuid, parent_version_id: Uuid, history_segment: Vec, diff --git a/taskchampion/src/config.rs b/taskchampion/src/config.rs index 7347bcd5a..63fef6527 100644 --- a/taskchampion/src/config.rs +++ b/taskchampion/src/config.rs @@ -20,8 +20,8 @@ pub enum ServerConfig { /// Sync server "origin"; a URL with schema and hostname but no path or trailing `/` origin: String, - /// Client ID to identify this replica to the server - client_id: Uuid, + /// Client Key to identify and authenticate this replica to the server + client_key: Uuid, /// Private encryption secret used to encrypt all data sent to the server. This can /// be any suitably un-guessable string of bytes. diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 4d214ee7e..40a8d4d2c 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -18,8 +18,8 @@ pub fn from_config(config: ServerConfig) -> Fallible> { ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), ServerConfig::Remote { origin, - client_id, + client_key, encryption_secret, - } => Box::new(RemoteServer::new(origin, client_id, encryption_secret)), + } => Box::new(RemoteServer::new(origin, client_key, encryption_secret)), }) } diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index 51da87072..44b713b20 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -8,7 +8,7 @@ use crypto::{HistoryCiphertext, HistoryCleartext, Secret}; pub struct RemoteServer { origin: String, - client_id: Uuid, + client_key: Uuid, encryption_secret: Secret, agent: ureq::Agent, } @@ -17,13 +17,13 @@ pub struct RemoteServer { /// taskchampion-sync-server). impl RemoteServer { /// Construct a new RemoteServer. The `origin` is the sync server's protocol and hostname - /// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_id to + /// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_key to /// identify this client to the server. Multiple replicas synchronizing the same task history - /// should use the same client_id. - pub fn new(origin: String, client_id: Uuid, encryption_secret: Vec) -> RemoteServer { + /// should use the same client_key. + pub fn new(origin: String, client_key: Uuid, encryption_secret: Vec) -> RemoteServer { RemoteServer { origin, - client_id, + client_key, encryption_secret: encryption_secret.into(), agent: ureq::agent(), } @@ -58,7 +58,7 @@ impl Server for RemoteServer { ) -> Fallible { let url = format!( "{}/client/{}/add-version/{}", - self.origin, self.client_id, parent_version_id + self.origin, self.client_key, parent_version_id ); let history_cleartext = HistoryCleartext { parent_version_id, @@ -89,7 +89,7 @@ impl Server for RemoteServer { fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible { let url = format!( "{}/client/{}/get-child-version/{}", - self.origin, self.client_id, parent_version_id + self.origin, self.client_key, parent_version_id ); let resp = self .agent From 31378cb8d47b0bc582d88e5c9782b435228db7a4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 28 Dec 2020 23:08:42 +0000 Subject: [PATCH 155/548] Include client key in a header, not the URL Since this value is used both for identification and authentication, it shouldn't be in the URL where it might be logged or otherwise discovered. --- docs/src/sync-protocol.md | 5 +-- sync-server/src/api/add_version.rs | 24 ++++++++----- sync-server/src/api/get_child_version.rs | 45 +++++++++++++----------- sync-server/src/api/mod.rs | 24 +++++++++++-- taskchampion/src/server/remote/mod.rs | 11 +++--- 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md index 1a4799630..e96f7cc83 100644 --- a/docs/src/sync-protocol.md +++ b/docs/src/sync-protocol.md @@ -69,10 +69,11 @@ The transactions above are realized for an HTTP server at `` using the H The `origin` *should* be an HTTPS endpoint on general principle, but nothing in the functonality or security of the protocol depends on connection encryption. The replica identifies itself to the server using a `clientKey` in the form of a UUID. +This value is passed with every request in the `X-Client-Id` header, in its dashed-hex format. ### AddVersion -The request is a `POST` to `/client//add-version/`. +The request is a `POST` to `/client/add-version/`. The request body contains the history segment, optionally encoded using any encoding supported by actix-web. The content-type must be `application/vnd.taskchampion.history-segment`. @@ -86,7 +87,7 @@ Other error responses (4xx or 5xx) may be returned and should be treated appropr ### GetChildVersion -The request is a `GET` to `/client//get-child-version/`. +The request is a `GET` to `/client/get-child-version/`. The response is 404 NOT FOUND if no such version exists. Otherwise, the response is a 200 OK. The version's history segment is returned in the response body, with content-type `application/vnd.taskchampion.history-segment`. diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index fcb45fb10..4ca7cbdb1 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -1,8 +1,8 @@ use crate::api::{ - failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, - VERSION_ID_HEADER, + client_key_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, + PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{add_version, AddVersionResult, ClientKey, VersionId, NO_VERSION_ID}; +use crate::server::{add_version, AddVersionResult, VersionId, NO_VERSION_ID}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use futures::StreamExt; @@ -19,11 +19,11 @@ const MAX_SIZE: usize = 100 * 1024 * 1024; /// parent version ID in the `X-Parent-Version-Id` header. /// /// Returns other 4xx or 5xx responses on other errors. -#[post("/client/{client_key}/add-version/{parent_version_id}")] +#[post("/client/add-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, server_state: web::Data, - web::Path((client_key, parent_version_id)): web::Path<(ClientKey, VersionId)>, + web::Path((parent_version_id,)): web::Path<(VersionId,)>, mut payload: web::Payload, ) -> Result { // check content-type @@ -31,6 +31,8 @@ pub(crate) async fn service( return Err(error::ErrorBadRequest("Bad content-type")); } + let client_key = client_key_header(&req)?; + // read the body in its entirety let mut body = web::BytesMut::new(); while let Some(chunk) = payload.next().await { @@ -97,13 +99,14 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id); + let uri = format!("/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( "Content-Type", "application/vnd.taskchampion.history-segment", ) + .header("X-Client-Key", client_key.to_string()) .set_payload(b"abcd".to_vec()) .to_request(); let resp = test::call_service(&mut app, req).await; @@ -133,13 +136,14 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id); + let uri = format!("/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( "Content-Type", "application/vnd.taskchampion.history-segment", ) + .header("X-Client-Key", client_key.to_string()) .set_payload(b"abcd".to_vec()) .to_request(); let resp = test::call_service(&mut app, req).await; @@ -159,10 +163,11 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id); + let uri = format!("/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header("Content-Type", "not/correct") + .header("X-Client-Key", client_key.to_string()) .set_payload(b"abcd".to_vec()) .to_request(); let resp = test::call_service(&mut app, req).await; @@ -177,13 +182,14 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id); + let uri = format!("/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( "Content-Type", "application/vnd.taskchampion.history-segment", ) + .header("X-Client-Key", client_key.to_string()) .to_request(); let resp = test::call_service(&mut app, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 84a9fafa3..0bf04d96a 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -1,9 +1,9 @@ use crate::api::{ - failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, - VERSION_ID_HEADER, + client_key_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, + PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{get_child_version, ClientKey, VersionId}; -use actix_web::{error, get, web, HttpResponse, Result}; +use crate::server::{get_child_version, VersionId}; +use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; /// Get a child version. /// @@ -13,13 +13,16 @@ use actix_web::{error, get, web, HttpResponse, Result}; /// /// If no such child exists, returns a 404 with no content. /// Returns other 4xx or 5xx responses on other errors. -#[get("/client/{client_key}/get-child-version/{parent_version_id}")] +#[get("/client/get-child-version/{parent_version_id}")] pub(crate) async fn service( + req: HttpRequest, server_state: web::Data, - web::Path((client_key, parent_version_id)): web::Path<(ClientKey, VersionId)>, + web::Path((parent_version_id,)): web::Path<(VersionId,)>, ) -> Result { let mut txn = server_state.txn().map_err(failure_to_ise)?; + let client_key = client_key_header(&req)?; + txn.get_client(client_key) .map_err(failure_to_ise)? .ok_or_else(|| error::ErrorNotFound("no such client"))?; @@ -65,11 +68,11 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!( - "/client/{}/get-child-version/{}", - client_key, parent_version_id - ); - let req = test::TestRequest::get().uri(&uri).to_request(); + let uri = format!("/client/get-child-version/{}", parent_version_id); + let req = test::TestRequest::get() + .uri(&uri) + .header("X-Client-Key", client_key.to_string()) + .to_request(); let mut resp = test::call_service(&mut app, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( @@ -98,11 +101,11 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!( - "/client/{}/get-child-version/{}", - client_key, parent_version_id - ); - let req = test::TestRequest::get().uri(&uri).to_request(); + let uri = format!("/client/get-child-version/{}", parent_version_id); + let req = test::TestRequest::get() + .uri(&uri) + .header("X-Client-Key", client_key.to_string()) + .to_request(); let resp = test::call_service(&mut app, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.headers().get("X-Version-Id"), None); @@ -123,11 +126,11 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!( - "/client/{}/get-child-version/{}", - client_key, parent_version_id - ); - let req = test::TestRequest::get().uri(&uri).to_request(); + let uri = format!("/client/get-child-version/{}", parent_version_id); + let req = test::TestRequest::get() + .uri(&uri) + .header("X-Client-Key", client_key.to_string()) + .to_request(); let resp = test::call_service(&mut app, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.headers().get("X-Version-Id"), None); diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index cddcab59c..5929f28c1 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -1,5 +1,6 @@ +use crate::server::ClientKey; use crate::storage::Storage; -use actix_web::{error, http::StatusCode, web, Scope}; +use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope}; use std::sync::Arc; mod add_version; @@ -9,10 +10,13 @@ mod get_child_version; pub(crate) const HISTORY_SEGMENT_CONTENT_TYPE: &str = "application/vnd.taskchampion.history-segment"; -/// The header names for version ID +/// The header name for version ID pub(crate) const VERSION_ID_HEADER: &str = "X-Version-Id"; -/// The header names for parent version ID +/// The header name for client key +pub(crate) const CLIENT_KEY_HEADER: &str = "X-Client-Key"; + +/// The header name for parent version ID pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; /// The type containing a reference to the Storage object in the Actix state. @@ -28,3 +32,17 @@ pub(crate) fn api_scope() -> Scope { fn failure_to_ise(err: failure::Error) -> impl actix_web::ResponseError { error::InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR) } + +/// Get the client key +fn client_key_header(req: &HttpRequest) -> Result { + fn badrequest() -> error::Error { + error::ErrorBadRequest("bad x-client-id") + } + if let Some(client_key_hdr) = req.headers().get(CLIENT_KEY_HEADER) { + let client_key = client_key_hdr.to_str().map_err(|_| badrequest())?; + let client_key = ClientKey::parse_str(client_key).map_err(|_| badrequest())?; + Ok(client_key) + } else { + Err(badrequest()) + } +} diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index 44b713b20..cd20618ec 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -56,10 +56,7 @@ impl Server for RemoteServer { parent_version_id: VersionId, history_segment: HistorySegment, ) -> Fallible { - let url = format!( - "{}/client/{}/add-version/{}", - self.origin, self.client_key, parent_version_id - ); + let url = format!("{}/client/add-version/{}", self.origin, parent_version_id); let history_cleartext = HistoryCleartext { parent_version_id, history_segment, @@ -74,6 +71,7 @@ impl Server for RemoteServer { "Content-Type", "application/vnd.taskchampion.history-segment", ) + .set("X-Client-Key", &self.client_key.to_string()) .send_bytes(history_ciphertext.as_ref()); if resp.ok() { let version_id = get_uuid_header(&resp, "X-Version-Id")?; @@ -88,14 +86,15 @@ impl Server for RemoteServer { fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible { let url = format!( - "{}/client/{}/get-child-version/{}", - self.origin, self.client_key, parent_version_id + "{}/client/get-child-version/{}", + self.origin, parent_version_id ); let resp = self .agent .get(&url) .timeout_connect(10_000) .timeout_read(60_000) + .set("X-Client-Key", &self.client_key.to_string()) .call(); if resp.ok() { From 0a458b5f5b5534daac851a1ec3a026c18c5ddcfb Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 29 Dec 2020 22:54:07 +0000 Subject: [PATCH 156/548] Treat any bare word in the command line as a report name --- cli/src/argparse/args.rs | 5 + cli/src/argparse/mod.rs | 2 - cli/src/argparse/subcommand.rs | 172 ++++++++++-------- cli/src/invocation/cmd/mod.rs | 2 +- cli/src/invocation/cmd/{list.rs => report.rs} | 26 ++- cli/src/invocation/mod.rs | 8 +- cli/src/invocation/report.rs | 58 +++++- cli/src/lib.rs | 1 + cli/src/{argparse => }/report.rs | 4 +- 9 files changed, 177 insertions(+), 101 deletions(-) rename cli/src/invocation/cmd/{list.rs => report.rs} (55%) rename cli/src/{argparse => }/report.rs (94%) diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs index c58b5ac51..dc568e7bf 100644 --- a/cli/src/argparse/args.rs +++ b/cli/src/argparse/args.rs @@ -30,6 +30,11 @@ pub(super) fn any(input: &str) -> IResult<&str, &str> { rest(input) } +/// Recognizes a report name +pub(super) fn report_name(input: &str) -> IResult<&str, &str> { + all_consuming(recognize(pair(alpha1, alphanumeric0)))(input) +} + /// Recognizes a literal string pub(super) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, &str> { move |input: &str| all_consuming(nomtag(literal))(input) diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index 01df66bed..0bf656a8f 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -15,14 +15,12 @@ mod args; mod command; mod filter; mod modification; -mod report; mod subcommand; pub(crate) use args::TaskId; pub(crate) use command::Command; pub(crate) use filter::{Condition, Filter, Universe}; pub(crate) use modification::{DescriptionMod, Modification}; -pub(crate) use report::{Column, Property, Report, Sort, SortBy}; pub(crate) use subcommand::Subcommand; use crate::usage::Usage; diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 5884cc4a9..0caf17ebd 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -1,7 +1,5 @@ use super::args::*; -use super::{ - ArgList, Column, DescriptionMod, Filter, Modification, Property, Report, Sort, SortBy, -}; +use super::{ArgList, DescriptionMod, Filter, Modification}; use crate::usage; use nom::{branch::alt, combinator::*, sequence::*, IResult}; use taskchampion::Status; @@ -39,8 +37,12 @@ pub(crate) enum Subcommand { }, /// Lists (reports) - List { - report: Report, + Report { + /// The name of the report to show + report_name: String, + + /// Additional filter terms beyond those in the report + filter: Filter, }, /// Per-task information (typically one task) @@ -56,16 +58,17 @@ pub(crate) enum Subcommand { impl Subcommand { pub(super) fn parse(input: ArgList) -> IResult { - alt(( + all_consuming(alt(( Version::parse, Help::parse, Add::parse, Modify::parse, - List::parse, Info::parse, Gc::parse, Sync::parse, - ))(input) + // This must come last since it accepts arbitrary report names + Report::parse, + )))(input) } pub(super) fn get_usage(u: &mut usage::Usage) { @@ -73,10 +76,10 @@ impl Subcommand { Help::get_usage(u); Add::get_usage(u); Modify::get_usage(u); - List::get_usage(u); Info::get_usage(u); Gc::get_usage(u); Sync::get_usage(u); + Report::get_usage(u); } } @@ -251,59 +254,43 @@ impl Modify { } } -struct List; - -impl List { - // temporary - fn default_report() -> Report { - Report { - columns: vec![ - Column { - label: "Id".to_owned(), - property: Property::Id, - }, - Column { - label: "Description".to_owned(), - property: Property::Description, - }, - Column { - label: "Active".to_owned(), - property: Property::Active, - }, - Column { - label: "Tags".to_owned(), - property: Property::Tags, - }, - ], - sort: vec![Sort { - ascending: false, - sort_by: SortBy::Uuid, - }], - ..Default::default() - } - } +struct Report; +impl Report { fn parse(input: ArgList) -> IResult { - fn to_subcommand(input: (Filter, &str)) -> Result { - let report = Report { - filter: input.0, - ..List::default_report() - }; - Ok(Subcommand::List { report }) + fn to_subcommand(filter: Filter, report_name: &str) -> Result { + Ok(Subcommand::Report { + filter, + report_name: report_name.to_owned(), + }) } - map_res( - pair(Filter::parse, arg_matching(literal("list"))), - to_subcommand, - )(input) + // allow the filter expression before or after the report name + alt(( + map_res(pair(arg_matching(report_name), Filter::parse), |input| { + to_subcommand(input.1, input.0) + }), + map_res(pair(Filter::parse, arg_matching(report_name)), |input| { + to_subcommand(input.0, input.1) + }), + // default to a "next" report + map_res(Filter::parse, |input| to_subcommand(input, "next")), + ))(input) } fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "list", - syntax: "[filter] list", - summary: "List tasks", + name: "report", + syntax: "[filter] [report-name] *or* [report-name] [filter]", + summary: "Show a report", description: " - Show a list of the tasks matching the filter", + Show the named report, including only tasks matching the filter", + }); + u.subcommands.push(usage::Subcommand { + name: "next", + syntax: "[filter]", + summary: "Show the 'next' report", + description: " + Show the report named 'next', including only tasks matching the filter", }); } } @@ -634,31 +621,72 @@ mod test { } #[test] - fn test_list() { - let subcommand = Subcommand::List { - report: Report { - ..List::default_report() - }, + fn test_report() { + let subcommand = Subcommand::Report { + filter: Default::default(), + report_name: "myreport".to_owned(), }; assert_eq!( - Subcommand::parse(argv!["list"]).unwrap(), + Subcommand::parse(argv!["myreport"]).unwrap(), (&EMPTY[..], subcommand) ); } #[test] - fn test_list_filter() { - let subcommand = Subcommand::List { - report: Report { - filter: Filter { - universe: Universe::for_ids(vec![12, 13]), - ..Default::default() - }, - ..List::default_report() + fn test_report_filter_before() { + let subcommand = Subcommand::Report { + filter: Filter { + universe: Universe::for_ids(vec![12, 13]), + ..Default::default() }, + report_name: "foo".to_owned(), }; assert_eq!( - Subcommand::parse(argv!["12,13", "list"]).unwrap(), + Subcommand::parse(argv!["12,13", "foo"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_report_filter_after() { + let subcommand = Subcommand::Report { + filter: Filter { + universe: Universe::for_ids(vec![12, 13]), + ..Default::default() + }, + report_name: "foo".to_owned(), + }; + assert_eq!( + Subcommand::parse(argv!["foo", "12,13"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_report_filter_next() { + let subcommand = Subcommand::Report { + filter: Filter { + universe: Universe::for_ids(vec![12, 13]), + ..Default::default() + }, + report_name: "next".to_owned(), + }; + assert_eq!( + Subcommand::parse(argv!["12,13"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + + #[test] + fn test_report_next() { + let subcommand = Subcommand::Report { + filter: Filter { + ..Default::default() + }, + report_name: "next".to_owned(), + }; + assert_eq!( + Subcommand::parse(argv![]).unwrap(), (&EMPTY[..], subcommand) ); } @@ -704,11 +732,7 @@ mod test { #[test] fn test_gc_extra_args() { - let subcommand = Subcommand::Gc; - assert_eq!( - Subcommand::parse(argv!["gc", "foo"]).unwrap(), - (&vec!["foo"][..], subcommand) - ); + assert!(Subcommand::parse(argv!["gc", "foo"]).is_err()); } #[test] diff --git a/cli/src/invocation/cmd/mod.rs b/cli/src/invocation/cmd/mod.rs index 43050215b..18a973ebb 100644 --- a/cli/src/invocation/cmd/mod.rs +++ b/cli/src/invocation/cmd/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod add; pub(crate) mod gc; pub(crate) mod help; pub(crate) mod info; -pub(crate) mod list; pub(crate) mod modify; +pub(crate) mod report; pub(crate) mod sync; pub(crate) mod version; diff --git a/cli/src/invocation/cmd/list.rs b/cli/src/invocation/cmd/report.rs similarity index 55% rename from cli/src/invocation/cmd/list.rs rename to cli/src/invocation/cmd/report.rs index 48f8543b6..38a72b3e7 100644 --- a/cli/src/invocation/cmd/list.rs +++ b/cli/src/invocation/cmd/report.rs @@ -1,4 +1,4 @@ -use crate::argparse::Report; +use crate::argparse::Filter; use crate::invocation::display_report; use failure::Fallible; use taskchampion::Replica; @@ -7,35 +7,33 @@ use termcolor::WriteColor; pub(crate) fn execute( w: &mut W, replica: &mut Replica, - report: Report, + report_name: String, + filter: Filter, ) -> Fallible<()> { - display_report(w, replica, &report) + display_report(w, replica, report_name, filter) } #[cfg(test)] mod test { use super::*; - use crate::argparse::{Column, Filter, Property}; + use crate::argparse::Filter; use crate::invocation::test::*; use taskchampion::Status; #[test] - fn test_list() { + fn test_report() { let mut w = test_writer(); let mut replica = test_replica(); replica.new_task(Status::Pending, s!("my task")).unwrap(); - let report = Report { - filter: Filter { - ..Default::default() - }, - columns: vec![Column { - label: "Description".to_owned(), - property: Property::Description, - }], + // The function being tested is only one line long, so this is sort of an integration test + // for display_report. + + let report_name = "next".to_owned(); + let filter = Filter { ..Default::default() }; - execute(&mut w, &mut replica, report).unwrap(); + execute(&mut w, &mut replica, report_name, filter).unwrap(); assert!(w.into_string().contains("my task")); } } diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 685c96cf2..6cbc0df03 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -60,9 +60,13 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { } => return cmd::modify::execute(&mut w, &mut replica, filter, modification), Command { - subcommand: Subcommand::List { report }, + subcommand: + Subcommand::Report { + report_name, + filter, + }, .. - } => return cmd::list::execute(&mut w, &mut replica, report), + } => return cmd::report::execute(&mut w, &mut replica, report_name, filter), Command { subcommand: Subcommand::Info { filter, debug }, diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index db5d577df..0ed793f7d 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -1,7 +1,8 @@ -use crate::argparse::{Column, Property, Report, SortBy}; +use crate::argparse::Filter; use crate::invocation::filtered_tasks; +use crate::report::{Column, Property, Report, Sort, SortBy}; use crate::table; -use failure::Fallible; +use failure::{bail, Fallible}; use prettytable::{Row, Table}; use std::cmp::Ordering; use taskchampion::{Replica, Task, Uuid}; @@ -101,20 +102,64 @@ fn task_column(task: &Task, column: &Column, working_set: &WorkingSet) -> String } } +fn get_report(report_name: String, filter: Filter) -> Fallible { + let columns = vec![ + Column { + label: "Id".to_owned(), + property: Property::Id, + }, + Column { + label: "Description".to_owned(), + property: Property::Description, + }, + Column { + label: "Active".to_owned(), + property: Property::Active, + }, + Column { + label: "Tags".to_owned(), + property: Property::Tags, + }, + ]; + let sort = vec![Sort { + ascending: false, + sort_by: SortBy::Uuid, + }]; + use crate::argparse::Universe; + Ok(match report_name.as_ref() { + "list" => Report { + columns, + sort, + filter, + }, + "next" => Report { + columns, + sort, + // TODO: merge invocation filter and report filter + filter: Filter { + universe: Universe::PendingTasks, + ..Default::default() + }, + }, + _ => bail!("Unknown report {:?}", report_name), + }) +} + pub(super) fn display_report( w: &mut W, replica: &mut Replica, - report: &Report, + report_name: String, + filter: Filter, ) -> Fallible<()> { let mut t = Table::new(); - + let report = get_report(report_name, filter)?; let working_set = WorkingSet::new(replica)?; // Get the tasks from the filter let mut tasks: Vec<_> = filtered_tasks(replica, &report.filter)?.collect(); // ..sort them as desired - sort_tasks(&mut tasks, report, &working_set); + sort_tasks(&mut tasks, &report, &working_set); // ..set up the column titles t.set_format(table::format()); @@ -138,8 +183,8 @@ pub(super) fn display_report( #[cfg(test)] mod test { use super::*; - use crate::argparse::{Column, Property, Report, Sort, SortBy}; use crate::invocation::test::*; + use crate::report::Sort; use std::convert::TryInto; use taskchampion::Status; @@ -371,4 +416,3 @@ mod test { assert_eq!(task_column(&task, &column, &working_set), s!("")); } } -// TODO: test task_column diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 379b3ea81..38546f6cb 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -38,6 +38,7 @@ mod macros; mod argparse; mod invocation; +mod report; mod settings; mod table; mod usage; diff --git a/cli/src/argparse/report.rs b/cli/src/report.rs similarity index 94% rename from cli/src/argparse/report.rs rename to cli/src/report.rs index 3b569652c..370e48706 100644 --- a/cli/src/argparse/report.rs +++ b/cli/src/report.rs @@ -1,4 +1,6 @@ -use super::Filter; +//! This module contains the data structures used to define reports. + +use crate::argparse::Filter; /// A report specifies a filter as well as a sort order and information about which /// task attributes to display From fc977a0fe65884edf40de829e1e218eaa39763d0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 30 Dec 2020 00:23:18 +0000 Subject: [PATCH 157/548] Limit Filter "universes" to invocation::filter Universes are really an optimization of filtering tasks, so let's define them there, and derive them from the set of conditions. This means that complex filters might get missed and end up doing a full task scan, but that's probably OK. Note that this does not fix the working-set API issues (#108 and #123). --- cli/src/argparse/filter.rs | 150 ++++++++++++++++++++----------- cli/src/argparse/mod.rs | 2 +- cli/src/argparse/subcommand.rs | 56 ++++++------ cli/src/invocation/filter.rs | 155 +++++++++++++++++++++------------ cli/src/invocation/report.rs | 20 +++-- 5 files changed, 236 insertions(+), 147 deletions(-) diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index f381e5862..4645de5da 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -2,6 +2,7 @@ use super::args::{arg_matching, id_list, minus_tag, plus_tag, TaskId}; use super::ArgList; use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; +use taskchampion::Status; /// A filter represents a selection of a particular set of tasks. /// @@ -10,40 +11,11 @@ use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; /// pending tasks, or all tasks. #[derive(Debug, PartialEq, Default, Clone)] pub(crate) struct Filter { - /// The universe of tasks from which this filter can select - pub(crate) universe: Universe, - /// A set of filter conditions, all of which must match a task in order for that task to be /// selected. pub(crate) conditions: Vec, } -/// The universe of tasks over which a filter should be applied. -#[derive(Debug, PartialEq, Clone)] -pub(crate) enum Universe { - /// Only the identified tasks. Note that this may contain duplicates. - IdList(Vec), - /// All tasks in the task database - AllTasks, - /// Only pending tasks (or as an approximation, the working set) - #[allow(dead_code)] // currently only used in tests - PendingTasks, -} - -impl Universe { - /// Testing shorthand to construct a simple universe - #[cfg(test)] - pub(super) fn for_ids(mut ids: Vec) -> Self { - Universe::IdList(ids.drain(..).map(|id| TaskId::WorkingSetId(id)).collect()) - } -} - -impl Default for Universe { - fn default() -> Self { - Self::AllTasks - } -} - /// A condition which tasks must match to be accepted by the filter. #[derive(Debug, PartialEq, Clone)] pub(crate) enum Condition { @@ -52,10 +24,18 @@ pub(crate) enum Condition { /// Task does not have the given tag NoTag(String), + + // TODO: add command-line syntax for this + /// Task has the given status + Status(Status), + + /// Task has one of the given IDs + IdList(Vec), } /// Internal struct representing a parsed filter argument enum FilterArg { + // TODO: get rid of this whole enum IdList(Vec), Condition(Condition), } @@ -67,30 +47,46 @@ impl Filter { Filter { ..Default::default() }, - Self::fold_args, + |acc, arg| acc.with_arg(arg), )(input) } /// fold multiple filter args into a single Filter instance - fn fold_args(mut acc: Filter, mod_arg: FilterArg) -> Filter { + fn with_arg(mut self, mod_arg: FilterArg) -> Filter { match mod_arg { FilterArg::IdList(mut id_list) => { - // If any IDs are specified, then the filter's universe - // is those IDs. If there are already IDs, append to the - // list. - if let Universe::IdList(ref mut existing) = acc.universe { + // If there is already an IdList condition, concatenate this one + // to it. Thus multiple IdList command-line args represent an OR + // operation. This assumes that the filter is still being built + // from command-line arguments and thus has at most one IdList + // condition. + if let Some(Condition::IdList(existing)) = self + .conditions + .iter_mut() + .find(|c| matches!(c, Condition::IdList(_))) + { existing.append(&mut id_list); } else { - acc.universe = Universe::IdList(id_list); + self.conditions.push(Condition::IdList(id_list)); } } FilterArg::Condition(cond) => { - acc.conditions.push(cond); + self.conditions.push(cond); } } - acc + self } + /// combine this filter with another filter in an AND operation + pub(crate) fn intersect(mut self, mut other: Filter) -> Filter { + // simply concatenate the conditions + self.conditions.append(&mut other.conditions); + + self + } + + // parsers + fn id_list(input: ArgList) -> IResult { fn to_filterarg(input: Vec) -> Result { Ok(FilterArg::IdList(input)) @@ -112,6 +108,8 @@ impl Filter { map_res(arg_matching(minus_tag), to_filterarg)(input) } + // usage + pub(super) fn get_usage(u: &mut usage::Usage) { u.filters.push(usage::Filter { syntax: "TASKID[,TASKID,..]", @@ -160,8 +158,7 @@ mod test { assert_eq!( filter, Filter { - universe: Universe::IdList(vec![TaskId::WorkingSetId(1)]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(1)])], } ); } @@ -173,12 +170,11 @@ mod test { assert_eq!( filter, Filter { - universe: Universe::IdList(vec![ + conditions: vec![Condition::IdList(vec![ TaskId::WorkingSetId(1), TaskId::WorkingSetId(2), TaskId::WorkingSetId(3), - ]), - ..Default::default() + ])], } ); } @@ -190,13 +186,12 @@ mod test { assert_eq!( filter, Filter { - universe: Universe::IdList(vec![ + conditions: vec![Condition::IdList(vec![ TaskId::WorkingSetId(1), TaskId::WorkingSetId(2), TaskId::WorkingSetId(3), TaskId::WorkingSetId(4), - ]), - ..Default::default() + ])], } ); } @@ -208,11 +203,10 @@ mod test { assert_eq!( filter, Filter { - universe: Universe::IdList(vec![ + conditions: vec![Condition::IdList(vec![ TaskId::WorkingSetId(1), TaskId::PartialUuid(s!("abcd1234")), - ]), - ..Default::default() + ])], } ); } @@ -224,12 +218,66 @@ mod test { assert_eq!( filter, Filter { - universe: Universe::IdList(vec![TaskId::WorkingSetId(1),]), conditions: vec![ + Condition::IdList(vec![TaskId::WorkingSetId(1),]), Condition::HasTag("yes".into()), Condition::NoTag("no".into()), ], - ..Default::default() + } + ); + } + + #[test] + fn intersect_idlist_idlist() { + let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1; + let right = Filter::parse(argv!["2,3", "+no"]).unwrap().1; + let both = left.intersect(right); + assert_eq!( + both, + Filter { + conditions: vec![ + // from first filter + Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]), + Condition::HasTag("yes".into()), + // from second filter + Condition::IdList(vec![TaskId::WorkingSetId(2), TaskId::WorkingSetId(3)]), + Condition::HasTag("no".into()), + ], + } + ); + } + + #[test] + fn intersect_idlist_alltasks() { + let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1; + let right = Filter::parse(argv!["+no"]).unwrap().1; + let both = left.intersect(right); + assert_eq!( + both, + Filter { + conditions: vec![ + // from first filter + Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]), + Condition::HasTag("yes".into()), + // from second filter + Condition::HasTag("no".into()), + ], + } + ); + } + + #[test] + fn intersect_alltasks_alltasks() { + let left = Filter::parse(argv!["+yes"]).unwrap().1; + let right = Filter::parse(argv!["+no"]).unwrap().1; + let both = left.intersect(right); + assert_eq!( + both, + Filter { + conditions: vec![ + Condition::HasTag("yes".into()), + Condition::HasTag("no".into()), + ], } ); } diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index 0bf656a8f..ab54326f1 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -19,7 +19,7 @@ mod subcommand; pub(crate) use args::TaskId; pub(crate) use command::Command; -pub(crate) use filter::{Condition, Filter, Universe}; +pub(crate) use filter::{Condition, Filter}; pub(crate) use modification::{DescriptionMod, Modification}; pub(crate) use subcommand::Subcommand; diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 0caf17ebd..1d7ddc2ba 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -383,7 +383,7 @@ impl Sync { #[cfg(test)] mod test { use super::*; - use crate::argparse::Universe; + use crate::argparse::Condition; const EMPTY: Vec<&str> = vec![]; @@ -459,8 +459,7 @@ mod test { fn test_modify_description_multi() { let subcommand = Subcommand::Modify { filter: Filter { - universe: Universe::for_ids(vec![123]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], }, modification: Modification { description: DescriptionMod::Set(s!("foo bar")), @@ -477,8 +476,7 @@ mod test { fn test_append() { let subcommand = Subcommand::Modify { filter: Filter { - universe: Universe::for_ids(vec![123]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], }, modification: Modification { description: DescriptionMod::Append(s!("foo bar")), @@ -495,8 +493,7 @@ mod test { fn test_prepend() { let subcommand = Subcommand::Modify { filter: Filter { - universe: Universe::for_ids(vec![123]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], }, modification: Modification { description: DescriptionMod::Prepend(s!("foo bar")), @@ -513,8 +510,7 @@ mod test { fn test_done() { let subcommand = Subcommand::Modify { filter: Filter { - universe: Universe::for_ids(vec![123]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], }, modification: Modification { status: Some(Status::Completed), @@ -531,8 +527,7 @@ mod test { fn test_done_modify() { let subcommand = Subcommand::Modify { filter: Filter { - universe: Universe::for_ids(vec![123]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], }, modification: Modification { description: DescriptionMod::Set(s!("now-finished")), @@ -550,8 +545,7 @@ mod test { fn test_start() { let subcommand = Subcommand::Modify { filter: Filter { - universe: Universe::for_ids(vec![123]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], }, modification: Modification { active: Some(true), @@ -568,8 +562,7 @@ mod test { fn test_start_modify() { let subcommand = Subcommand::Modify { filter: Filter { - universe: Universe::for_ids(vec![123]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], }, modification: Modification { active: Some(true), @@ -587,8 +580,7 @@ mod test { fn test_stop() { let subcommand = Subcommand::Modify { filter: Filter { - universe: Universe::for_ids(vec![123]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], }, modification: Modification { active: Some(false), @@ -605,8 +597,7 @@ mod test { fn test_stop_modify() { let subcommand = Subcommand::Modify { filter: Filter { - universe: Universe::for_ids(vec![123]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], }, modification: Modification { description: DescriptionMod::Set(s!("mod")), @@ -636,8 +627,10 @@ mod test { fn test_report_filter_before() { let subcommand = Subcommand::Report { filter: Filter { - universe: Universe::for_ids(vec![12, 13]), - ..Default::default() + conditions: vec![Condition::IdList(vec![ + TaskId::WorkingSetId(12), + TaskId::WorkingSetId(13), + ])], }, report_name: "foo".to_owned(), }; @@ -651,8 +644,10 @@ mod test { fn test_report_filter_after() { let subcommand = Subcommand::Report { filter: Filter { - universe: Universe::for_ids(vec![12, 13]), - ..Default::default() + conditions: vec![Condition::IdList(vec![ + TaskId::WorkingSetId(12), + TaskId::WorkingSetId(13), + ])], }, report_name: "foo".to_owned(), }; @@ -666,8 +661,10 @@ mod test { fn test_report_filter_next() { let subcommand = Subcommand::Report { filter: Filter { - universe: Universe::for_ids(vec![12, 13]), - ..Default::default() + conditions: vec![Condition::IdList(vec![ + TaskId::WorkingSetId(12), + TaskId::WorkingSetId(13), + ])], }, report_name: "next".to_owned(), }; @@ -696,8 +693,10 @@ mod test { let subcommand = Subcommand::Info { debug: false, filter: Filter { - universe: Universe::for_ids(vec![12, 13]), - ..Default::default() + conditions: vec![Condition::IdList(vec![ + TaskId::WorkingSetId(12), + TaskId::WorkingSetId(13), + ])], }, }; assert_eq!( @@ -711,8 +710,7 @@ mod test { let subcommand = Subcommand::Info { debug: true, filter: Filter { - universe: Universe::for_ids(vec![12]), - ..Default::default() + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(12)])], }, }; assert_eq!( diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index f87263270..34fc1b1e1 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -1,10 +1,10 @@ -use crate::argparse::{Condition, Filter, TaskId, Universe}; +use crate::argparse::{Condition, Filter, TaskId}; use failure::Fallible; use std::collections::HashSet; use std::convert::TryInto; -use taskchampion::{Replica, Tag, Task}; +use taskchampion::{Replica, Status, Tag, Task, Uuid}; -fn match_task(filter: &Filter, task: &Task) -> bool { +fn match_task(filter: &Filter, task: &Task, uuid: Uuid, working_set_id: Option) -> bool { for cond in &filter.conditions { match cond { Condition::HasTag(ref tag) => { @@ -21,11 +21,85 @@ fn match_task(filter: &Filter, task: &Task) -> bool { return false; } } + Condition::Status(status) => { + if task.get_status() != *status { + return false; + } + } + Condition::IdList(ids) => { + let uuid_str = uuid.to_string(); + let mut found = false; + for id in ids { + if match id { + TaskId::WorkingSetId(i) => Some(*i) == working_set_id, + TaskId::PartialUuid(partial) => uuid_str.starts_with(partial), + TaskId::Uuid(i) => *i == uuid, + } { + found = true; + break; + } + } + if !found { + return false; + } + } } } true } +// the universe of tasks we must consider +enum Universe { + /// Scan all the tasks + AllTasks, + /// Scan the working set (for pending tasks) + WorkingSet, + /// Scan an explicit set of tasks, "Absolute" meaning either full UUID or a working set + /// index + AbsoluteIdList(Vec), +} + +/// Determine the universe for the given filter; avoiding the need to scan all tasks in most cases. +fn universe_for_filter(filter: &Filter) -> Universe { + /// If there is a condition with Status::Pending, return true + fn has_pending_condition(filter: &Filter) -> bool { + filter + .conditions + .iter() + .any(|cond| matches!(cond, Condition::Status(Status::Pending))) + } + + /// If there is a condition with an IdList containing no partial UUIDs, + /// return that. + fn absolute_id_list_condition(filter: &Filter) -> Option> { + filter + .conditions + .iter() + .find(|cond| { + if let Condition::IdList(ids) = cond { + !ids.iter().any(|id| matches!(id, TaskId::PartialUuid(_))) + } else { + false + } + }) + .map(|cond| { + if let Condition::IdList(ids) = cond { + ids.to_vec() + } else { + unreachable!() // any condition found above must be an IdList(_) + } + }) + } + + if let Some(ids) = absolute_id_list_condition(filter) { + Universe::AbsoluteIdList(ids) + } else if has_pending_condition(filter) { + Universe::WorkingSet + } else { + Universe::AllTasks + } +} + /// Return the tasks matching the given filter. This will return each matching /// task once, even if the user specified the same task multiple times on the /// command line. @@ -35,38 +109,14 @@ pub(super) fn filtered_tasks( ) -> Fallible> { let mut res = vec![]; - fn is_partial_uuid(taskid: &TaskId) -> bool { - matches!(taskid, TaskId::PartialUuid(_)) - } + log::debug!("Applying filter {:?}", filter); // We will enumerate the universe of tasks for this filter, checking // each resulting task with match_task - match filter.universe { + match universe_for_filter(filter) { // A list of IDs, but some are partial so we need to iterate over // all tasks and pattern-match their Uuids - Universe::IdList(ref ids) if ids.iter().any(is_partial_uuid) => { - log::debug!("Scanning entire task database due to partial UUIDs in the filter"); - 'task: for (uuid, task) in replica.all_tasks()?.drain() { - for id in ids { - let in_universe = match id { - TaskId::WorkingSetId(id) => { - // NOTE: (#108) this results in many reads of the working set; it - // may be better to cache this information here or in the Replica. - replica.get_working_set_index(&uuid)? == Some(*id) - } - TaskId::PartialUuid(prefix) => uuid.to_string().starts_with(prefix), - TaskId::Uuid(id) => id == &uuid, - }; - if in_universe && match_task(filter, &task) { - res.push(task); - continue 'task; - } - } - } - } - - // A list of full IDs, which we can fetch directly - Universe::IdList(ref ids) => { + Universe::AbsoluteIdList(ref ids) => { log::debug!("Scanning only the tasks specified in the filter"); // this is the only case where we might accidentally return the same task // several times, so we must track the seen tasks. @@ -74,7 +124,7 @@ pub(super) fn filtered_tasks( for id in ids { let task = match id { TaskId::WorkingSetId(id) => replica.get_working_set_task(*id)?, - TaskId::PartialUuid(_) => unreachable!(), // handled above + TaskId::PartialUuid(_) => unreachable!(), // not present in absolute id list TaskId::Uuid(id) => replica.get_task(id)?, }; @@ -86,7 +136,9 @@ pub(super) fn filtered_tasks( } seen.insert(uuid); - if match_task(filter, &task) { + let working_set_id = replica.get_working_set_index(&uuid)?; + + if match_task(filter, &task, uuid, working_set_id) { res.push(task); } } @@ -96,19 +148,20 @@ pub(super) fn filtered_tasks( // All tasks -- iterate over the full set Universe::AllTasks => { log::debug!("Scanning all tasks in the task database"); - for (_, task) in replica.all_tasks()?.drain() { - if match_task(filter, &task) { + for (uuid, task) in replica.all_tasks()?.drain() { + // Yikes, slow! https://github.com/djmitche/taskchampion/issues/108 + let working_set_id = replica.get_working_set_index(&uuid)?; + if match_task(filter, &task, uuid, working_set_id) { res.push(task); } } } - - // Pending tasks -- just scan the working set - Universe::PendingTasks => { + Universe::WorkingSet => { log::debug!("Scanning only the working set (pending tasks)"); - for task in replica.working_set()?.drain(..) { + for (i, task) in replica.working_set()?.drain(..).enumerate() { if let Some(task) = task { - if match_task(filter, &task) { + let uuid = *task.get_uuid(); + if match_task(filter, &task, uuid, Some(i)) { res.push(task); } } @@ -136,12 +189,11 @@ mod test { let t1uuid = *t1.get_uuid(); let filter = Filter { - universe: Universe::IdList(vec![ + conditions: vec![Condition::IdList(vec![ TaskId::Uuid(t1uuid), // A TaskId::WorkingSetId(1), // A (again, dups filtered) TaskId::Uuid(*t2.get_uuid()), // B - ]), - ..Default::default() + ])], }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) .unwrap() @@ -165,12 +217,11 @@ mod test { let t2partial = t2uuid[..13].to_owned(); let filter = Filter { - universe: Universe::IdList(vec![ + conditions: vec![Condition::IdList(vec![ TaskId::Uuid(t1uuid), // A TaskId::WorkingSetId(1), // A (again, dups filtered) TaskId::PartialUuid(t2partial), // B - ]), - ..Default::default() + ])], }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) .unwrap() @@ -189,10 +240,7 @@ mod test { replica.new_task(Status::Deleted, s!("C")).unwrap(); replica.gc().unwrap(); - let filter = Filter { - universe: Universe::AllTasks, - ..Default::default() - }; + let filter = Filter { conditions: vec![] }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) .unwrap() .map(|t| t.get_description().to_owned()) @@ -224,9 +272,7 @@ mod test { // look for just "yes" (A and B) let filter = Filter { - universe: Universe::AllTasks, conditions: vec![Condition::HasTag(s!("yes"))], - ..Default::default() }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) @@ -236,9 +282,7 @@ mod test { // look for tags without "no" (A, D) let filter = Filter { - universe: Universe::AllTasks, conditions: vec![Condition::NoTag(s!("no"))], - ..Default::default() }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) @@ -248,9 +292,7 @@ mod test { // look for tags with "yes" and "no" (B) let filter = Filter { - universe: Universe::AllTasks, conditions: vec![Condition::HasTag(s!("yes")), Condition::HasTag(s!("no"))], - ..Default::default() }; let filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) @@ -270,8 +312,7 @@ mod test { replica.gc().unwrap(); let filter = Filter { - universe: Universe::PendingTasks, - ..Default::default() + conditions: vec![Condition::Status(Status::Pending)], }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) .unwrap() diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 0ed793f7d..82cc67bba 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -1,11 +1,11 @@ -use crate::argparse::Filter; +use crate::argparse::{Condition, Filter}; use crate::invocation::filtered_tasks; use crate::report::{Column, Property, Report, Sort, SortBy}; use crate::table; use failure::{bail, Fallible}; use prettytable::{Row, Table}; use std::cmp::Ordering; -use taskchampion::{Replica, Task, Uuid}; +use taskchampion::{Replica, Status, Task, Uuid}; use termcolor::WriteColor; // pending #123, this is a non-fallible way of looking up a task's working set index @@ -125,24 +125,26 @@ fn get_report(report_name: String, filter: Filter) -> Fallible { ascending: false, sort_by: SortBy::Uuid, }]; - use crate::argparse::Universe; - Ok(match report_name.as_ref() { + let mut report = match report_name.as_ref() { "list" => Report { columns, sort, - filter, + filter: Default::default(), }, "next" => Report { columns, sort, - // TODO: merge invocation filter and report filter filter: Filter { - universe: Universe::PendingTasks, - ..Default::default() + conditions: vec![Condition::Status(Status::Pending)], }, }, _ => bail!("Unknown report {:?}", report_name), - }) + }; + + // intersect the report's filter with the user-supplied filter + report.filter = report.filter.intersect(filter); + + Ok(report) } pub(super) fn display_report( From 83d8fc3b4edd4c436c4f95be68f7c86de9a7ea93 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 30 Dec 2020 00:28:48 +0000 Subject: [PATCH 158/548] Factor out the unnecessary ModArg struct --- cli/src/argparse/filter.rs | 63 ++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 4645de5da..ba01a3fd0 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -33,13 +33,6 @@ pub(crate) enum Condition { IdList(Vec), } -/// Internal struct representing a parsed filter argument -enum FilterArg { - // TODO: get rid of this whole enum - IdList(Vec), - Condition(Condition), -} - impl Filter { pub(super) fn parse(input: ArgList) -> IResult { fold_many0( @@ -52,27 +45,25 @@ impl Filter { } /// fold multiple filter args into a single Filter instance - fn with_arg(mut self, mod_arg: FilterArg) -> Filter { - match mod_arg { - FilterArg::IdList(mut id_list) => { - // If there is already an IdList condition, concatenate this one - // to it. Thus multiple IdList command-line args represent an OR - // operation. This assumes that the filter is still being built - // from command-line arguments and thus has at most one IdList - // condition. - if let Some(Condition::IdList(existing)) = self - .conditions - .iter_mut() - .find(|c| matches!(c, Condition::IdList(_))) - { - existing.append(&mut id_list); - } else { - self.conditions.push(Condition::IdList(id_list)); - } - } - FilterArg::Condition(cond) => { - self.conditions.push(cond); + fn with_arg(mut self, cond: Condition) -> Filter { + if let Condition::IdList(mut id_list) = cond { + // If there is already an IdList condition, concatenate this one + // to it. Thus multiple IdList command-line args represent an OR + // operation. This assumes that the filter is still being built + // from command-line arguments and thus has at most one IdList + // condition. + if let Some(Condition::IdList(existing)) = self + .conditions + .iter_mut() + .find(|c| matches!(c, Condition::IdList(_))) + { + existing.append(&mut id_list); + } else { + self.conditions.push(Condition::IdList(id_list)); } + } else { + // all other command-line conditions are AND'd together + self.conditions.push(cond); } self } @@ -87,23 +78,23 @@ impl Filter { // parsers - fn id_list(input: ArgList) -> IResult { - fn to_filterarg(input: Vec) -> Result { - Ok(FilterArg::IdList(input)) + fn id_list(input: ArgList) -> IResult { + fn to_filterarg(input: Vec) -> Result { + Ok(Condition::IdList(input)) } map_res(arg_matching(id_list), to_filterarg)(input) } - fn plus_tag(input: ArgList) -> IResult { - fn to_filterarg(input: &str) -> Result { - Ok(FilterArg::Condition(Condition::HasTag(input.to_owned()))) + fn plus_tag(input: ArgList) -> IResult { + fn to_filterarg(input: &str) -> Result { + Ok(Condition::HasTag(input.to_owned())) } map_res(arg_matching(plus_tag), to_filterarg)(input) } - fn minus_tag(input: ArgList) -> IResult { - fn to_filterarg(input: &str) -> Result { - Ok(FilterArg::Condition(Condition::NoTag(input.to_owned()))) + fn minus_tag(input: ArgList) -> IResult { + fn to_filterarg(input: &str) -> Result { + Ok(Condition::NoTag(input.to_owned())) } map_res(arg_matching(minus_tag), to_filterarg)(input) } From b7c12eec1e731a769862152ae909c47a9e8d872b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 30 Dec 2020 00:51:29 +0000 Subject: [PATCH 159/548] Allow filtering by status --- cli/src/argparse/args.rs | 48 +++++++++++++++++++++++++++++++- cli/src/argparse/filter.rs | 56 ++++++++++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 13 deletions(-) diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs index dc568e7bf..337bca763 100644 --- a/cli/src/argparse/args.rs +++ b/cli/src/argparse/args.rs @@ -10,7 +10,7 @@ use nom::{ sequence::*, Err, IResult, }; -use taskchampion::Uuid; +use taskchampion::{Status, Uuid}; /// A task identifier, as given in a filter command-line expression #[derive(Debug, PartialEq, Clone)] @@ -40,6 +40,32 @@ pub(super) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, & move |input: &str| all_consuming(nomtag(literal))(input) } +/// Recognizes a colon-prefixed pair +pub(super) fn colon_prefixed(prefix: &'static str) -> impl Fn(&str) -> IResult<&str, &str> { + fn to_suffix<'a>(input: (&'a str, char, &'a str)) -> Result<&'a str, ()> { + Ok(input.2) + } + move |input: &str| { + map_res( + all_consuming(tuple((nomtag(prefix), char(':'), any))), + to_suffix, + )(input) + } +} + +/// Recognizes `status:{pending,completed,deleted}` +pub(super) fn status_colon(input: &str) -> IResult<&str, Status> { + fn to_status(input: &str) -> Result { + match input { + "pending" => Ok(Status::Pending), + "completed" => Ok(Status::Completed), + "deleted" => Ok(Status::Deleted), + _ => Err(()), + } + } + map_res(colon_prefixed("status"), to_status)(input) +} + /// Recognizes a comma-separated list of TaskIds pub(super) fn id_list(input: &str) -> IResult<&str, Vec> { fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> { @@ -164,6 +190,26 @@ mod test { assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); } + #[test] + fn test_colon_prefixed() { + assert_eq!(colon_prefixed("foo")("foo:abc").unwrap().1, "abc"); + assert_eq!(colon_prefixed("foo")("foo:").unwrap().1, ""); + assert!(colon_prefixed("foo")("foo").is_err()); + } + + #[test] + fn test_status_colon() { + assert_eq!(status_colon("status:pending").unwrap().1, Status::Pending); + assert_eq!( + status_colon("status:completed").unwrap().1, + Status::Completed + ); + assert_eq!(status_colon("status:deleted").unwrap().1, Status::Deleted); + assert!(status_colon("status:foo").is_err()); + assert!(status_colon("status:complete").is_err()); + assert!(status_colon("status").is_err()); + } + #[test] fn test_plus_tag() { assert_eq!(plus_tag("+abc").unwrap().1, "abc"); diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index ba01a3fd0..6a8033eb3 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -1,4 +1,4 @@ -use super::args::{arg_matching, id_list, minus_tag, plus_tag, TaskId}; +use super::args::{arg_matching, id_list, minus_tag, plus_tag, status_colon, TaskId}; use super::ArgList; use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; @@ -25,7 +25,6 @@ pub(crate) enum Condition { /// Task does not have the given tag NoTag(String), - // TODO: add command-line syntax for this /// Task has the given status Status(Status), @@ -36,7 +35,12 @@ pub(crate) enum Condition { impl Filter { pub(super) fn parse(input: ArgList) -> IResult { fold_many0( - alt((Self::id_list, Self::plus_tag, Self::minus_tag)), + alt(( + Self::parse_id_list, + Self::parse_plus_tag, + Self::parse_minus_tag, + Self::parse_status, + )), Filter { ..Default::default() }, @@ -78,25 +82,32 @@ impl Filter { // parsers - fn id_list(input: ArgList) -> IResult { - fn to_filterarg(input: Vec) -> Result { + fn parse_id_list(input: ArgList) -> IResult { + fn to_condition(input: Vec) -> Result { Ok(Condition::IdList(input)) } - map_res(arg_matching(id_list), to_filterarg)(input) + map_res(arg_matching(id_list), to_condition)(input) } - fn plus_tag(input: ArgList) -> IResult { - fn to_filterarg(input: &str) -> Result { + fn parse_plus_tag(input: ArgList) -> IResult { + fn to_condition(input: &str) -> Result { Ok(Condition::HasTag(input.to_owned())) } - map_res(arg_matching(plus_tag), to_filterarg)(input) + map_res(arg_matching(plus_tag), to_condition)(input) } - fn minus_tag(input: ArgList) -> IResult { - fn to_filterarg(input: &str) -> Result { + fn parse_minus_tag(input: ArgList) -> IResult { + fn to_condition(input: &str) -> Result { Ok(Condition::NoTag(input.to_owned())) } - map_res(arg_matching(minus_tag), to_filterarg)(input) + map_res(arg_matching(minus_tag), to_condition)(input) + } + + fn parse_status(input: ArgList) -> IResult { + fn to_condition(input: Status) -> Result { + Ok(Condition::Status(input)) + } + map_res(arg_matching(status_colon), to_condition)(input) } // usage @@ -123,6 +134,12 @@ impl Filter { description: " Select tasks that do not have the given tag.", }); + u.filters.push(usage::Filter { + syntax: "status:pending, status:completed, status:deleted", + summary: "Task status", + description: " + Select tasks with the given status.", + }); } } @@ -218,6 +235,21 @@ mod test { ); } + #[test] + fn test_status() { + let (input, filter) = Filter::parse(argv!["status:completed", "status:pending"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + filter, + Filter { + conditions: vec![ + Condition::Status(Status::Completed), + Condition::Status(Status::Pending), + ], + } + ); + } + #[test] fn intersect_idlist_idlist() { let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1; From 705edce82b08b22359d3e22be6571b82ae7b7d7a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 30 Dec 2020 01:02:00 +0000 Subject: [PATCH 160/548] only run CI on pushes to main, not to topic branches --- .taskcluster.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.taskcluster.yml b/.taskcluster.yml index 9cdef3214..da73d0ede 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -7,7 +7,7 @@ tasks: then: $let: run: - $if: 'tasks_for == "github-push"' + $if: 'tasks_for == "github-push" && event["ref"] == "refs/heads/main"' then: true else: {$eval: 'event.action in ["opened", "reopened", "synchronize"]'} repo_url: From 46c3b312083b7fb194b04a344a88ce7d114016a3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 30 Dec 2020 20:40:46 +0000 Subject: [PATCH 161/548] Parse reports from config, with defaults --- cli/src/argparse/filter.rs | 89 +++--- cli/src/invocation/cmd/report.rs | 8 +- cli/src/invocation/mod.rs | 2 +- cli/src/invocation/report.rs | 63 +--- cli/src/report.rs | 510 ++++++++++++++++++++++++++++++- cli/src/settings.rs | 44 ++- 6 files changed, 624 insertions(+), 92 deletions(-) diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 6a8033eb3..812917845 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -1,6 +1,7 @@ use super::args::{arg_matching, id_list, minus_tag, plus_tag, status_colon, TaskId}; use super::ArgList; use crate::usage; +use failure::{bail, Fallible}; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use taskchampion::Status; @@ -32,15 +33,61 @@ pub(crate) enum Condition { IdList(Vec), } +impl Condition { + fn parse(input: ArgList) -> IResult { + alt(( + Self::parse_id_list, + Self::parse_plus_tag, + Self::parse_minus_tag, + Self::parse_status, + ))(input) + } + + /// Parse a single condition string + pub(crate) fn parse_str(input: &str) -> Fallible { + let input = &[input]; + Ok(match Condition::parse(input) { + Ok((&[], cond)) => cond, + Ok(_) => unreachable!(), // input only has one element + Err(nom::Err::Incomplete(_)) => unreachable!(), + Err(nom::Err::Error(e)) => bail!("invalid filter condition: {:?}", e), + Err(nom::Err::Failure(e)) => bail!("invalid filter condition: {:?}", e), + }) + } + + fn parse_id_list(input: ArgList) -> IResult { + fn to_condition(input: Vec) -> Result { + Ok(Condition::IdList(input)) + } + map_res(arg_matching(id_list), to_condition)(input) + } + + fn parse_plus_tag(input: ArgList) -> IResult { + fn to_condition(input: &str) -> Result { + Ok(Condition::HasTag(input.to_owned())) + } + map_res(arg_matching(plus_tag), to_condition)(input) + } + + fn parse_minus_tag(input: ArgList) -> IResult { + fn to_condition(input: &str) -> Result { + Ok(Condition::NoTag(input.to_owned())) + } + map_res(arg_matching(minus_tag), to_condition)(input) + } + + fn parse_status(input: ArgList) -> IResult { + fn to_condition(input: Status) -> Result { + Ok(Condition::Status(input)) + } + map_res(arg_matching(status_colon), to_condition)(input) + } +} + impl Filter { pub(super) fn parse(input: ArgList) -> IResult { fold_many0( - alt(( - Self::parse_id_list, - Self::parse_plus_tag, - Self::parse_minus_tag, - Self::parse_status, - )), + Condition::parse, Filter { ..Default::default() }, @@ -80,36 +127,6 @@ impl Filter { self } - // parsers - - fn parse_id_list(input: ArgList) -> IResult { - fn to_condition(input: Vec) -> Result { - Ok(Condition::IdList(input)) - } - map_res(arg_matching(id_list), to_condition)(input) - } - - fn parse_plus_tag(input: ArgList) -> IResult { - fn to_condition(input: &str) -> Result { - Ok(Condition::HasTag(input.to_owned())) - } - map_res(arg_matching(plus_tag), to_condition)(input) - } - - fn parse_minus_tag(input: ArgList) -> IResult { - fn to_condition(input: &str) -> Result { - Ok(Condition::NoTag(input.to_owned())) - } - map_res(arg_matching(minus_tag), to_condition)(input) - } - - fn parse_status(input: ArgList) -> IResult { - fn to_condition(input: Status) -> Result { - Ok(Condition::Status(input)) - } - map_res(arg_matching(status_colon), to_condition)(input) - } - // usage pub(super) fn get_usage(u: &mut usage::Usage) { diff --git a/cli/src/invocation/cmd/report.rs b/cli/src/invocation/cmd/report.rs index 38a72b3e7..9076c7b23 100644 --- a/cli/src/invocation/cmd/report.rs +++ b/cli/src/invocation/cmd/report.rs @@ -1,5 +1,6 @@ use crate::argparse::Filter; use crate::invocation::display_report; +use config::Config; use failure::Fallible; use taskchampion::Replica; use termcolor::WriteColor; @@ -7,10 +8,11 @@ use termcolor::WriteColor; pub(crate) fn execute( w: &mut W, replica: &mut Replica, + settings: &Config, report_name: String, filter: Filter, ) -> Fallible<()> { - display_report(w, replica, report_name, filter) + display_report(w, replica, settings, report_name, filter) } #[cfg(test)] @@ -29,11 +31,13 @@ mod test { // The function being tested is only one line long, so this is sort of an integration test // for display_report. + let settings = crate::settings::default_settings().unwrap(); let report_name = "next".to_owned(); let filter = Filter { ..Default::default() }; - execute(&mut w, &mut replica, report_name, filter).unwrap(); + + execute(&mut w, &mut replica, &settings, report_name, filter).unwrap(); assert!(w.into_string().contains("my task")); } } diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 6cbc0df03..3d5a953dc 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -66,7 +66,7 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { filter, }, .. - } => return cmd::report::execute(&mut w, &mut replica, report_name, filter), + } => return cmd::report::execute(&mut w, &mut replica, &settings, report_name, filter), Command { subcommand: Subcommand::Info { filter, debug }, diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 82cc67bba..a6e35e72f 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -1,11 +1,12 @@ -use crate::argparse::{Condition, Filter}; +use crate::argparse::Filter; use crate::invocation::filtered_tasks; -use crate::report::{Column, Property, Report, Sort, SortBy}; +use crate::report::{Column, Property, Report, SortBy}; use crate::table; -use failure::{bail, Fallible}; +use config::Config; +use failure::{format_err, Fallible}; use prettytable::{Row, Table}; use std::cmp::Ordering; -use taskchampion::{Replica, Status, Task, Uuid}; +use taskchampion::{Replica, Task, Uuid}; use termcolor::WriteColor; // pending #123, this is a non-fallible way of looking up a task's working set index @@ -102,61 +103,23 @@ fn task_column(task: &Task, column: &Column, working_set: &WorkingSet) -> String } } -fn get_report(report_name: String, filter: Filter) -> Fallible { - let columns = vec![ - Column { - label: "Id".to_owned(), - property: Property::Id, - }, - Column { - label: "Description".to_owned(), - property: Property::Description, - }, - Column { - label: "Active".to_owned(), - property: Property::Active, - }, - Column { - label: "Tags".to_owned(), - property: Property::Tags, - }, - ]; - let sort = vec![Sort { - ascending: false, - sort_by: SortBy::Uuid, - }]; - let mut report = match report_name.as_ref() { - "list" => Report { - columns, - sort, - filter: Default::default(), - }, - "next" => Report { - columns, - sort, - filter: Filter { - conditions: vec![Condition::Status(Status::Pending)], - }, - }, - _ => bail!("Unknown report {:?}", report_name), - }; - - // intersect the report's filter with the user-supplied filter - report.filter = report.filter.intersect(filter); - - Ok(report) -} - pub(super) fn display_report( w: &mut W, replica: &mut Replica, + settings: &Config, report_name: String, filter: Filter, ) -> Fallible<()> { let mut t = Table::new(); - let report = get_report(report_name, filter)?; let working_set = WorkingSet::new(replica)?; + // Get the report from settings + let mut report = Report::from_config(settings.get(&format!("reports.{}", report_name))?) + .map_err(|e| format_err!("report.{}{}", report_name, e))?; + + // include any user-supplied filter conditions + report.filter = report.filter.intersect(filter); + // Get the tasks from the filter let mut tasks: Vec<_> = filtered_tasks(replica, &report.filter)?.collect(); diff --git a/cli/src/report.rs b/cli/src/report.rs index 370e48706..12ba0af99 100644 --- a/cli/src/report.rs +++ b/cli/src/report.rs @@ -1,6 +1,7 @@ //! This module contains the data structures used to define reports. -use crate::argparse::Filter; +use crate::argparse::{Condition, Filter}; +use failure::{bail, format_err, Fallible}; /// A report specifies a filter as well as a sort order and information about which /// task attributes to display @@ -68,3 +69,510 @@ pub(crate) enum SortBy { /// The task's description Description, } + +// Conversions from config::Value. Note that these cannot ergonomically use TryFrom/TryInto; see +// https://github.com/mehcode/config-rs/issues/162 + +impl Report { + /// Create a Report from a config value. This should be the `report.` value. + /// The error message begins with any additional path information, e.g., `.sort[1].sort_by: + /// ..`. + pub(crate) fn from_config(cfg: config::Value) -> Fallible { + let mut map = cfg.into_table().map_err(|e| format_err!(": {}", e))?; + let sort = if let Some(sort_array) = map.remove("sort") { + sort_array + .into_array() + .map_err(|e| format_err!(".sort: {}", e))? + .drain(..) + .enumerate() + .map(|(i, v)| Sort::from_config(v).map_err(|e| format_err!(".sort[{}]{}", i, e))) + .collect::>>()? + } else { + vec![] + }; + + let columns = map + .remove("columns") + .ok_or_else(|| format_err!(": 'columns' property is required"))? + .into_array() + .map_err(|e| format_err!(".columns: {}", e))? + .drain(..) + .enumerate() + .map(|(i, v)| Column::from_config(v).map_err(|e| format_err!(".columns[{}]{}", i, e))) + .collect::>>()?; + + let conditions = if let Some(conditions) = map.remove("filter") { + conditions + .into_array() + .map_err(|e| format_err!(".filter: {}", e))? + .drain(..) + .enumerate() + .map(|(i, v)| { + v.into_str() + .map_err(|e| e.into()) + .and_then(|s| Condition::parse_str(&s)) + .map_err(|e| format_err!(".filter[{}]: {}", i, e)) + }) + .collect::>>()? + } else { + vec![] + }; + + let filter = Filter { conditions }; + + if !map.is_empty() { + bail!(": unknown properties"); + } + + Ok(Report { + columns, + sort, + filter, + }) + } +} + +impl Column { + pub(crate) fn from_config(cfg: config::Value) -> Fallible { + let mut map = cfg.into_table().map_err(|e| format_err!(": {}", e))?; + let label = map + .remove("label") + .ok_or_else(|| format_err!(": 'label' property is required"))? + .into_str() + .map_err(|e| format_err!(".label: {}", e))?; + let property: config::Value = map + .remove("property") + .ok_or_else(|| format_err!(": 'property' property is required"))?; + let property = + Property::from_config(property).map_err(|e| format_err!(".property{}", e))?; + + if !map.is_empty() { + bail!(": unknown properties"); + } + + Ok(Column { label, property }) + } +} + +impl Property { + pub(crate) fn from_config(cfg: config::Value) -> Fallible { + let s = cfg.into_str().map_err(|e| format_err!(": {}", e))?; + Ok(match s.as_ref() { + "id" => Property::Id, + "uuid" => Property::Uuid, + "active" => Property::Active, + "description" => Property::Description, + "tags" => Property::Tags, + _ => bail!(": unknown property {}", s), + }) + } +} + +impl Sort { + pub(crate) fn from_config(cfg: config::Value) -> Fallible { + let mut map = cfg.into_table().map_err(|e| format_err!(": {}", e))?; + let ascending = match map.remove("ascending") { + Some(v) => v + .into_bool() + .map_err(|e| format_err!(".ascending: {}", e))?, + None => true, // default + }; + let sort_by: config::Value = map + .remove("sort_by") + .ok_or_else(|| format_err!(": 'sort_by' property is required"))?; + let sort_by = SortBy::from_config(sort_by).map_err(|e| format_err!(".sort_by{}", e))?; + + if !map.is_empty() { + bail!(": unknown properties"); + } + + Ok(Sort { ascending, sort_by }) + } +} + +impl SortBy { + pub(crate) fn from_config(cfg: config::Value) -> Fallible { + let s = cfg.into_str().map_err(|e| format_err!(": {}", e))?; + Ok(match s.as_ref() { + "id" => SortBy::Id, + "uuid" => SortBy::Uuid, + "description" => SortBy::Description, + _ => bail!(": unknown sort_by {}", s), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use config::{Config, File, FileFormat, FileSourceString}; + use taskchampion::Status; + use textwrap::{dedent, indent}; + + fn config_from(cfg: &str) -> config::Value { + // wrap this in a "table" so that we can get any type of value at the top level. + let yaml = format!("val:\n{}", indent(&dedent(&cfg), " ")); + let mut settings = Config::new(); + let cfg_file: File = File::from_str(&yaml, FileFormat::Yaml); + settings.merge(cfg_file).unwrap(); + settings.cache.into_table().unwrap().remove("val").unwrap() + } + + #[test] + fn test_report_ok() { + let val = config_from( + " + filter: [] + sort: [] + columns: [] + filter: + - status:pending", + ); + let report = Report::from_config(val).unwrap(); + assert_eq!( + report.filter, + Filter { + conditions: vec![Condition::Status(Status::Pending),], + } + ); + assert_eq!(report.columns, vec![]); + assert_eq!(report.sort, vec![]); + } + + #[test] + fn test_report_no_sort() { + let val = config_from( + " + filter: [] + columns: []", + ); + let report = Report::from_config(val).unwrap(); + assert_eq!(report.sort, vec![]); + } + + #[test] + fn test_report_sort_not_array() { + let val = config_from( + " + filter: [] + sort: true + columns: []", + ); + assert_eq!( + &Report::from_config(val).unwrap_err().to_string(), + ".sort: invalid type: boolean `true`, expected an array" + ); + } + + #[test] + fn test_report_sort_error() { + let val = config_from( + " + filter: [] + sort: + - sort_by: id + - true + columns: []", + ); + assert!(&Report::from_config(val) + .unwrap_err() + .to_string() + .starts_with(".sort[1]")); + } + + #[test] + fn test_report_unknown_prop() { + let val = config_from( + " + columns: [] + filter: [] + sort: [] + nosuch: true + ", + ); + assert_eq!( + &Report::from_config(val).unwrap_err().to_string(), + ": unknown properties" + ); + } + + #[test] + fn test_report_no_columns() { + let val = config_from( + " + filter: [] + sort: []", + ); + assert_eq!( + &Report::from_config(val).unwrap_err().to_string(), + ": \'columns\' property is required" + ); + } + + #[test] + fn test_report_columns_not_array() { + let val = config_from( + " + filter: [] + sort: [] + columns: true", + ); + assert_eq!( + &Report::from_config(val).unwrap_err().to_string(), + ".columns: invalid type: boolean `true`, expected an array" + ); + } + + #[test] + fn test_report_column_error() { + let val = config_from( + " + filter: [] + sort: [] + columns: + - label: ID + property: id + - true", + ); + assert!(&Report::from_config(val) + .unwrap_err() + .to_string() + .starts_with(".columns[1]:")); + } + + #[test] + fn test_report_filter_not_array() { + let val = config_from( + " + filter: [] + sort: [] + columns: [] + filter: true", + ); + assert_eq!( + &Report::from_config(val).unwrap_err().to_string(), + ".filter: invalid type: boolean `true`, expected an array" + ); + } + + #[test] + fn test_report_filter_error() { + let val = config_from( + " + filter: [] + sort: [] + columns: [] + filter: + - nosuchfilter", + ); + assert!(&Report::from_config(val) + .unwrap_err() + .to_string() + .starts_with(".filter[0]: invalid filter condition:")); + } + + #[test] + fn test_column() { + let val = config_from( + " + label: ID + property: id", + ); + let column = Column::from_config(val).unwrap(); + assert_eq!( + column, + Column { + label: "ID".to_owned(), + property: Property::Id, + } + ); + } + + #[test] + fn test_column_unknown_prop() { + let val = config_from( + " + label: ID + property: id + nosuch: foo", + ); + assert_eq!( + &Column::from_config(val).unwrap_err().to_string(), + ": unknown properties" + ); + } + + #[test] + fn test_column_no_label() { + let val = config_from( + " + property: id", + ); + assert_eq!( + &Column::from_config(val).unwrap_err().to_string(), + ": 'label' property is required" + ); + } + + #[test] + fn test_column_invalid_label() { + let val = config_from( + " + label: [] + property: id", + ); + assert_eq!( + &Column::from_config(val).unwrap_err().to_string(), + ".label: invalid type: sequence, expected a string" + ); + } + + #[test] + fn test_column_no_property() { + let val = config_from( + " + label: ID", + ); + assert_eq!( + &Column::from_config(val).unwrap_err().to_string(), + ": 'property' property is required" + ); + } + + #[test] + fn test_column_invalid_property() { + let val = config_from( + " + label: ID + property: []", + ); + assert_eq!( + &Column::from_config(val).unwrap_err().to_string(), + ".property: invalid type: sequence, expected a string" + ); + } + + #[test] + fn test_property() { + let val = config_from("uuid"); + let prop = Property::from_config(val).unwrap(); + assert_eq!(prop, Property::Uuid); + } + + #[test] + fn test_property_invalid_type() { + let val = config_from("{}"); + assert_eq!( + &Property::from_config(val).unwrap_err().to_string(), + ": invalid type: map, expected a string" + ); + } + + #[test] + fn test_sort() { + let val = config_from( + " + ascending: false + sort_by: id", + ); + let sort = Sort::from_config(val).unwrap(); + assert_eq!( + sort, + Sort { + ascending: false, + sort_by: SortBy::Id, + } + ); + } + + #[test] + fn test_sort_no_ascending() { + let val = config_from( + " + sort_by: id", + ); + let sort = Sort::from_config(val).unwrap(); + assert_eq!( + sort, + Sort { + ascending: true, + sort_by: SortBy::Id, + } + ); + } + + #[test] + fn test_sort_unknown_prop() { + let val = config_from( + " + sort_by: id + nosuch: foo", + ); + assert_eq!( + &Sort::from_config(val).unwrap_err().to_string(), + ": unknown properties" + ); + } + + #[test] + fn test_sort_no_sort_by() { + let val = config_from( + " + ascending: true", + ); + assert_eq!( + &Sort::from_config(val).unwrap_err().to_string(), + ": 'sort_by' property is required" + ); + } + + #[test] + fn test_sort_invalid_ascending() { + let val = config_from( + " + sort_by: id + ascending: {}", + ); + assert_eq!( + &Sort::from_config(val).unwrap_err().to_string(), + ".ascending: invalid type: map, expected a boolean" + ); + } + + #[test] + fn test_sort_invalid_sort_by() { + let val = config_from( + " + sort_by: {}", + ); + assert_eq!( + &Sort::from_config(val).unwrap_err().to_string(), + ".sort_by: invalid type: map, expected a string" + ); + } + + #[test] + fn test_sort_by() { + let val = config_from("uuid"); + let prop = SortBy::from_config(val).unwrap(); + assert_eq!(prop, SortBy::Uuid); + } + + #[test] + fn test_sort_by_unknown() { + let val = config_from("nosuch"); + assert_eq!( + &SortBy::from_config(val).unwrap_err().to_string(), + ": unknown sort_by nosuch" + ); + } + + #[test] + fn test_sort_by_invalid_type() { + let val = config_from("{}"); + assert_eq!( + &SortBy::from_config(val).unwrap_err().to_string(), + ": invalid type: map, expected a string" + ); + } +} diff --git a/cli/src/settings.rs b/cli/src/settings.rs index 4d59b00be..88d972dc4 100644 --- a/cli/src/settings.rs +++ b/cli/src/settings.rs @@ -1,9 +1,40 @@ -use config::{Config, Environment, File, FileSourceFile}; +use config::{Config, Environment, File, FileFormat, FileSourceFile, FileSourceString}; use failure::Fallible; use std::env; use std::path::PathBuf; -pub(crate) fn read_settings() -> Fallible { +const DEFAULTS: &str = r#" +reports: + list: + sort: + - sort_by: uuid + columns: + - label: Id + property: id + - label: Description + property: description + - label: Active + property: active + - label: Tags + property: tags + next: + filter: + - "status:pending" + sort: + - sort_by: uuid + columns: + - label: Id + property: id + - label: Description + property: description + - label: Active + property: active + - label: Tags + property: tags +"#; + +/// Get the default settings for this application +pub(crate) fn default_settings() -> Fallible { let mut settings = Config::default(); // set up defaults @@ -25,6 +56,15 @@ pub(crate) fn read_settings() -> Fallible { )?; } + let defaults: File = File::from_str(DEFAULTS, FileFormat::Yaml); + settings.merge(defaults)?; + + Ok(settings) +} + +pub(crate) fn read_settings() -> Fallible { + let mut settings = default_settings()?; + // load either from the path in TASKCHAMPION_CONFIG, or from CONFIG_DIR/taskchampion if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") { log::debug!("Loading configuration from {:?}", config_file); From b29f75a075e30687ef5f3852d8f04e3f4cb840c0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 30 Dec 2020 21:46:23 +0000 Subject: [PATCH 162/548] fix to tc.yml to only build on pushes to main --- .taskcluster.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.taskcluster.yml b/.taskcluster.yml index da73d0ede..2204a3540 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -7,8 +7,8 @@ tasks: then: $let: run: - $if: 'tasks_for == "github-push" && event["ref"] == "refs/heads/main"' - then: true + $if: 'tasks_for == "github-push"' + then: {$eval: 'event.ref == "refs/heads/main"'} else: {$eval: 'event.action in ["opened", "reopened", "synchronize"]'} repo_url: $if: 'tasks_for == "github-push"' From 8b4f34538762ed6d9aa7f7bbd8221251af0634d0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 1 Jan 2021 21:17:42 +0000 Subject: [PATCH 163/548] Update documentation for reports, new CLI This is basically a redraft of the documentation to be more complete and better cover some of the features added since it was written. --- cli/src/invocation/mod.rs | 4 +- docs/src/SUMMARY.md | 7 ++- docs/src/config-file.md | 42 +++++++++++++++++ docs/src/debugging.md | 9 ++++ docs/src/reports.md | 81 +++++++++++++++++++++++++++++++++ docs/src/running-sync-server.md | 8 ++++ docs/src/task-sync.md | 23 ++++++++++ docs/src/usage.md | 75 ------------------------------ docs/src/using-task-command.md | 9 ++++ docs/src/welcome.md | 16 ++++--- 10 files changed, 189 insertions(+), 85 deletions(-) create mode 100644 docs/src/config-file.md create mode 100644 docs/src/debugging.md create mode 100644 docs/src/reports.md create mode 100644 docs/src/running-sync-server.md create mode 100644 docs/src/task-sync.md delete mode 100644 docs/src/usage.md create mode 100644 docs/src/using-task-command.md diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 3d5a953dc..16c9b0374 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -110,10 +110,10 @@ fn get_replica(settings: &Config) -> Fallible { /// Get the server for this invocation fn get_server(settings: &Config) -> Fallible> { - // if server_client_id and server_origin are both set, use + // if server_client_key and server_origin are both set, use // the remote server if let (Ok(client_key), Ok(origin)) = ( - settings.get_str("server_client_id"), + settings.get_str("server_client_key"), settings.get_str("server_origin"), ) { let client_key = Uuid::parse_str(&client_key)?; diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 40871f638..c8f56bdc6 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -2,7 +2,12 @@ - [Welcome to TaskChampion](./welcome.md) - [Installation](./installation.md) - - [Usage](./usage.md) + * [Using the Task Command](./using-task-command.md) + * [Configuration](./config-file.md) + * [Reports](./reports.md) + * [Synchronization](./task-sync.md) + * [Running the Sync Server](./running-sync-server.md) + * [Debugging](./debugging.md) - [Internal Details](./internals.md) - [Data Model](./data-model.md) - [Replica Storage](./storage.md) diff --git a/docs/src/config-file.md b/docs/src/config-file.md new file mode 100644 index 000000000..74737b98c --- /dev/null +++ b/docs/src/config-file.md @@ -0,0 +1,42 @@ +# Configuration + +The `task` command will work out-of-the-box with no configuration file, using default values. + +Configuration is read from `taskchampion.yaml` in your config directory. +On Linux systems, that directory is `~/.config`. +On OS X, it's `~/Library/Preferences`. +On Windows, it's `AppData/Roaming` in your home directory. +The path can be overridden by setting `$TASKCHAMPION_CONFIG`. + +Individual configuration parameters can be overridden by environment variables, converted to upper-case and prefixed with `TASKCHAMPION_`, e.g., `TASKCHAMPION_DATA_DIR`. +Nested configuration parameters such as `reports` cannot be overridden by environment variables. + +## Directories + +* `data_dir` - path to a directory containing the replica's task data (which will be created if necessary). + Default: `taskchampion` in the local data directory. + +## Sync Server + +If using a local server: + +* `server_dir` - path to a directory containing the local server's data. + This is only used if `server_origin` or `server_client_key` are not set. + Default: `taskchampion-sync-server` in the local data directory. + +If using a remote server: + +* `server_origin` - Origin of the TaskChampion sync server, e.g., `https://taskchampion.example.com`. + If not set, then sync is done to a local server. +* `encryption_secret` - Secret value used to encrypt all data stored on the server. + This should be a long random string. + If you have `openssl` installed, a command like `openssl rand -hex 35` will generate a suitable value. + This value is only used when synchronizing with a remote server -- local servers are unencrypted. + Treat this value as a password. +* `server_client_key` - Client key to identify this replica to the sync server (a UUID) + If not set, then sync is done to a local server. + +# Reports + +* `reports` - a mapping of each report's name to its definition. + See [Reports](./reports.md) for details. diff --git a/docs/src/debugging.md b/docs/src/debugging.md new file mode 100644 index 000000000..f3de1c7a7 --- /dev/null +++ b/docs/src/debugging.md @@ -0,0 +1,9 @@ +# Debugging + +Both `task` and `taskchampion-sync-server` use [env-logger](https://docs.rs/env_logger) and can be configured to log at various levels with the `RUST_LOG` environment variable. +For example: +```shell +$ RUST_LOG=taskchampion=trace task add foo +``` + +The output may provide valuable clues in debugging problems. diff --git a/docs/src/reports.md b/docs/src/reports.md new file mode 100644 index 000000000..376bf3531 --- /dev/null +++ b/docs/src/reports.md @@ -0,0 +1,81 @@ +# Reports + +As a to-do list manager, listing tasks is an important TaskChampion feature. +Reports are tabular displays of tasks, and allow very flexible filtering, sorting, and customization of columns. + +TaskChampion includes several "built-in" reports, as well as supporting custom reports in the [configuration file](./config-file.md). + +## Built-In Reports + +The `next` report is the default, and lists all pending tasks: + +```text +$ task +Id Description Active Tags +1 learn about TaskChampion +next +2 buy wedding gift * +buy +``` + +The `Id` column contains short numeric IDs that are assigned to pending tasks. +These IDs are easy to type, such as to mark task 2 done (`task 2 done`). + +The `list` report lists all tasks, with a similar set of columns. + +## Custom Reports + +Custom reports are defined in the configuration file's `reports` property. +This is a mapping from each report's name to its definition. +Each definition has the following properties: + +* `filter` - criteria for the tasks to include in the report +* `sort` - how to order the tasks +* `columns` - the columns of information to display for each task + +The filter is a list of filter arguments, just like those that can be used on the command line. +See the `task help` output for more details on this syntax. +For example: + +```yaml +reports: + garden: + filter: + - "status:pending" + - "+garden" +``` + +The sort order is defined by an array of objects containing a `sort_by` property and an optional `ascending` property. +Tasks are compared by the first criterion, and if that is equal by the second, and so on. +For example: + +```yaml +reports: + garden: + sort: + - sort_by: description + - sort_by: uuid + ascending: false +``` +If `ascending` is given, it can be `true` for the default sort order, or `false` for the reverse. + +The available values of `sort_by` are + +(TODO: generate automatically) + +Finally, the configuration specifies the list of columns to display in the `columns` property. +Each element has a `label` and a `property`: + +```yaml +reports: + garden: + columns: + - label: Id + property: id + - label: Description + property: description + - label: Tags + property: tags +``` + +The avaliable properties are: + +(TODO: generate automatically) diff --git a/docs/src/running-sync-server.md b/docs/src/running-sync-server.md new file mode 100644 index 000000000..2cc3b7454 --- /dev/null +++ b/docs/src/running-sync-server.md @@ -0,0 +1,8 @@ +# Running the Sync Server + +> NOTE: TaskChampion is still in development and not yet feature-complete. +> The server is functional, but lacks any administrative features. + +Run `taskchampion-sync-server` to start the sync server. +Use `--port` to specify the port it should listen on, and `--data-dir` to specify the directory which it should store its data. +It only serves HTTP; the expectation is that a frontend proxy will be used for HTTPS support. diff --git a/docs/src/task-sync.md b/docs/src/task-sync.md new file mode 100644 index 000000000..7d874760c --- /dev/null +++ b/docs/src/task-sync.md @@ -0,0 +1,23 @@ +# Synchronization + +A single TaskChampion task database is known as a "replica". +A replica "synchronizes" its local information with other replicas via a sync server. +Many replicas can thus share the same task history. + +This operation is triggered by running `task sync`. +Typically this runs frequently in a cron task. +Synchronization is quick, especially if no changes have occurred. + +Each replica expects to be synchronized frequently, even if no server is involved. +Without periodic syncs, the storage space used for the task database will grow quickly, and performance will suffer. + +By default, TaskChampion syncs to a "local server", as specified by the `server_dir` configuration parameter. +Every replica sharing a task history should have precisely the same configuration for `server_origin`, `server_client_key`, and `encryption_secret`. + +Synchronizing a new replica to an existing task history is easy: begin with an empty replica, configured for the remote server, and run `task sync`. +The replica will download the entire task history. + +It is possible to switch a single replica to a remote server by simply configuring for the remote server and running `task sync`. +The replica will upload the entire task history to the server. +Once this is complete, additional replicas can be configured with the same settings in order to share the task history. + diff --git a/docs/src/usage.md b/docs/src/usage.md deleted file mode 100644 index 39cf18bf7..000000000 --- a/docs/src/usage.md +++ /dev/null @@ -1,75 +0,0 @@ -# Usage - -## `task` - -The main interface to your tasks is the `task` command, which supports various subcommands. -You can find a quick list of all subcommands with `task help`. - -Note that the `task` interface does not match that of TaskWarrior. - -### Configuration - -The `task` command will work out-of-the-box with no configuration file, using default values. - -Configuration is read from `taskchampion.yaml` in your config directory. -On Linux systems, that directory is `~/.config`. -On OS X, it's `~/Library/Preferences`. -On Windows, it's `AppData/Roaming` in your home directory. -The path can be overridden by setting `$TASKCHAMPION_CONFIG`. - -Individual configuration parameters can be overridden by environment variables, converted to upper-case and prefixed with `TASKCHAMPION_`, e.g., `TASKCHAMPION_DATA_DIR`. -Nested configuration parameters cannot be overridden by environment variables. - -The following configuration parameters are available: - -* `data_dir` - path to a directory containing the replica's task data (which will be created if necessary). - Default: `taskchampion` in the local data directory. -* `server_dir` - path to a directory containing the local server's data. - This is only used if `server_origin` or `server_client_id` are not set. - Default: `taskchampion-sync-server` in the local data directory. -* `encryption_secret` - Secret value used to encrypt all data stored on the server. - This should be a long random string. - If you have `openssl` installed, a command like `openssl rand -hex 35` will generate a suitable value. - This value is only used when synchronizing with a remote server -- local servers are unencrypted. - Treat this value as a password. -* `server_origin` - Origin of the TaskChampion sync server, e.g., `https://taskchampion.example.com`. - If not set, then sync is done to a local server. -* `server_client_id` - Client ID to identify this replica to the sync server (a UUID) - If not set, then sync is done to a local server. - -### Synchronization - -A single TaskChampion task database is known as a "replica". -A replica "synchronizes" its local information with other replicas via a sync server. -Many replicas can thus share the same task history. - -This operation is triggered by running `task sync`. -Typically this runs frequently in a cron task. -Synchronization is quick, especially if no changes have occurred. - -Each replica expects to be synchronized frequently, even if no server is involved. -Without periodic syncs, the storage space used for the task database will grow quickly, and performance will suffer. - -By default, TaskChampion syncs to a "local server", as specified by the `server_dir` configuration parameter. -Every replica sharing a task history should have precisely the same configuration for `server_origin`, `server_client_id`, and `encryption_secret`. - -Synchronizing a new replica to an existing task history is easy: begin with an empty replica, configured for the remote server, and run `task sync`. -The replica will download the entire task history. - -It is possible to switch a single replica to a remote server by simply configuring for the remote server and running `task sync`. -The replica will upload the entire task history to the server. -Once this is complete, additional replicas can be configured with the same settings in order to share the task history. - -## `taskchampion-sync-server` - -Run `taskchampion-sync-server` to start the sync server. -Use `--port` to specify the port it should listen on, and `--data-dir` to specify the directory which it should store its data. -It only serves HTTP; the expectation is that a frontend proxy will be used for HTTPS support. - -## Debugging - -Both `task` and `taskchampio-sync-server` use [env-logger](https://docs.rs/env_logger) and can be configured to log at various levels with the `RUST_LOG` environment variable. -For example: -```shell -$ RUST_LOG=taskchampion=trace task add foo -``` diff --git a/docs/src/using-task-command.md b/docs/src/using-task-command.md new file mode 100644 index 000000000..dcc57b6f5 --- /dev/null +++ b/docs/src/using-task-command.md @@ -0,0 +1,9 @@ +# Using the Task Command + +The main interface to your tasks is the `task` command, which supports various subcommands such as `add`, `modify`, `start`, and `done`. +Customizable [reports](./reports.md) are also available as subcommands, such as `next`. +The command reads a [configuration file](./config-file.md) for its settings, including where to find the task database. +And the `sync` subcommand [synchronizes tasks with a sync server](./task-sync.md). +You can find a list of all subcommands, as well as the built-in reports, with `task help`. + +> NOTE: the `task` interface does not precisely match that of TaskWarrior. diff --git a/docs/src/welcome.md b/docs/src/welcome.md index c0e45c16a..a650c543a 100644 --- a/docs/src/welcome.md +++ b/docs/src/welcome.md @@ -3,7 +3,7 @@ TaskChampion is a personal task-tracking tool. It works from the command line, with simple commands like `task add "fix the kitchen sink"`. It can synchronize tasks on multiple devices, and does so in an "offline" mode so you can update your tasks even when you can't reach the server. -If you've heard of [TaskWarrior](https://taskwarrior.org/), this tool is very similar, but actively maintained and with a more reliable synchronization system. +If you've heard of [TaskWarrior](https://taskwarrior.org/), this tool is very similar, but with some different design choices and greater reliability. ## Getting Started @@ -14,16 +14,16 @@ Once you've [installed TaskChampion](./installation.md), your interface will be Start by adding a task: ```shell -$ task add "learn how to use taskchampion" +$ task add learn how to use taskchampion added task ba57deaf-f97b-4e9c-b9ab-04bc1ecb22b8 ``` -You can see all of your pending tasks with `task pending`, or just `task` for short: +You can see all of your pending tasks with `task next`, or just `task` for short: ```shell $ task - id act description - 1 learn how to use taskchampion + Id Description Active Tags + 1 learn how to use taskchampion ``` Tell TaskChampion you're working on the task, using the shorthand id: @@ -49,13 +49,15 @@ $ task sync Typically sync is run from a crontab, on whatever schedule fits your needs. -To synchronize multiple replicas of your tasks, you will need a sync server and a client ID on that server. +To synchronize multiple replicas of your tasks, you will need a sync server and a client key for that server. Configure these in `~/.config/taskchampion.yml`, for example: ```yaml -server_client_id: "f8d4d09d-f6c7-4dd2-ab50-634ed20a3ff2" +server_client_key: "f8d4d09d-f6c7-4dd2-ab50-634ed20a3ff2" server_origin: "https://taskchampion.example.com" ``` The next run of `task sync` will upload your task history to that server. Configuring another device identically and running `task sync` will download that task history, and continue to stay in sync with subsequent runs of the command. + +See [Usage](./usage.md) for more detailed information on using TaskChampion. From b62370c150a9fc409bbf6242f6e4319dbec29671 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 2 Jan 2021 13:23:48 -0500 Subject: [PATCH 164/548] Rename replica.gc to replica.rebuild_working_set The command-line operation is still `gc`, but we'll break that down into finer pieces in the replica. --- cli/src/invocation/cmd/gc.rs | 3 ++- cli/src/invocation/filter.rs | 8 ++++---- cli/src/invocation/report.rs | 2 +- taskchampion/src/replica.rs | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs index 5fe20b74e..cb32aec9f 100644 --- a/cli/src/invocation/cmd/gc.rs +++ b/cli/src/invocation/cmd/gc.rs @@ -3,7 +3,8 @@ use taskchampion::Replica; use termcolor::WriteColor; pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Fallible<()> { - replica.gc()?; + log::debug!("rebuilding working set"); + replica.rebuild_working_set()?; writeln!(w, "garbage collected.")?; Ok(()) } diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index 34fc1b1e1..23fc2edc8 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -184,7 +184,7 @@ mod test { let t1 = replica.new_task(Status::Pending, s!("A")).unwrap(); let t2 = replica.new_task(Status::Completed, s!("B")).unwrap(); let _t = replica.new_task(Status::Pending, s!("C")).unwrap(); - replica.gc().unwrap(); + replica.rebuild_working_set().unwrap(); let t1uuid = *t1.get_uuid(); @@ -210,7 +210,7 @@ mod test { let t1 = replica.new_task(Status::Pending, s!("A")).unwrap(); let t2 = replica.new_task(Status::Completed, s!("B")).unwrap(); let _t = replica.new_task(Status::Pending, s!("C")).unwrap(); - replica.gc().unwrap(); + replica.rebuild_working_set().unwrap(); let t1uuid = *t1.get_uuid(); let t2uuid = t2.get_uuid().to_string(); @@ -238,7 +238,7 @@ mod test { replica.new_task(Status::Pending, s!("A")).unwrap(); replica.new_task(Status::Completed, s!("B")).unwrap(); replica.new_task(Status::Deleted, s!("C")).unwrap(); - replica.gc().unwrap(); + replica.rebuild_working_set().unwrap(); let filter = Filter { conditions: vec![] }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) @@ -309,7 +309,7 @@ mod test { replica.new_task(Status::Pending, s!("A")).unwrap(); replica.new_task(Status::Completed, s!("B")).unwrap(); replica.new_task(Status::Deleted, s!("C")).unwrap(); - replica.gc().unwrap(); + replica.rebuild_working_set().unwrap(); let filter = Filter { conditions: vec![Condition::Status(Status::Pending)], diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index a6e35e72f..e1df81db3 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -163,7 +163,7 @@ mod test { t2.set_status(Status::Completed).unwrap(); let t2 = t2.into_immut(); - replica.gc().unwrap(); + replica.rebuild_working_set().unwrap(); [*t1.get_uuid(), *t2.get_uuid(), *t3.get_uuid()] } diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 215578647..5190ca16c 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -172,7 +172,7 @@ impl Replica { /// Perform "garbage collection" on this replica. In particular, this renumbers the working /// set to contain only pending tasks. - pub fn gc(&mut self) -> Fallible<()> { + pub fn rebuild_working_set(&mut self) -> Fallible<()> { let pending = String::from(Status::Pending.to_taskmap()); self.taskdb .rebuild_working_set(|t| t.get("status") == Some(&pending))?; @@ -251,7 +251,7 @@ mod tests { assert_eq!(t.get_status(), Status::Deleted); assert_eq!(t.get_description(), "gone"); - rep.gc().unwrap(); + rep.rebuild_working_set().unwrap(); assert!(rep.get_working_set_index(t.get_uuid()).unwrap().is_none()); } From dc2df10158221bc66a08a83ef552d72d69eb91c8 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 2 Jan 2021 14:40:05 -0500 Subject: [PATCH 165/548] Control whether to renumber the working set when rebuilding it --- cli/src/invocation/cmd/gc.rs | 2 +- cli/src/invocation/filter.rs | 8 +- cli/src/invocation/report.rs | 2 +- taskchampion/src/replica.rs | 12 +-- taskchampion/src/taskdb.rs | 109 ++++++++++++++++------- taskchampion/src/taskstorage/inmemory.rs | 11 ++- taskchampion/src/taskstorage/kv.rs | 31 ++++++- taskchampion/src/taskstorage/mod.rs | 4 + 8 files changed, 135 insertions(+), 44 deletions(-) diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs index cb32aec9f..75451808e 100644 --- a/cli/src/invocation/cmd/gc.rs +++ b/cli/src/invocation/cmd/gc.rs @@ -4,7 +4,7 @@ use termcolor::WriteColor; pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Fallible<()> { log::debug!("rebuilding working set"); - replica.rebuild_working_set()?; + replica.rebuild_working_set(true)?; writeln!(w, "garbage collected.")?; Ok(()) } diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index 23fc2edc8..0cab337ae 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -184,7 +184,7 @@ mod test { let t1 = replica.new_task(Status::Pending, s!("A")).unwrap(); let t2 = replica.new_task(Status::Completed, s!("B")).unwrap(); let _t = replica.new_task(Status::Pending, s!("C")).unwrap(); - replica.rebuild_working_set().unwrap(); + replica.rebuild_working_set(true).unwrap(); let t1uuid = *t1.get_uuid(); @@ -210,7 +210,7 @@ mod test { let t1 = replica.new_task(Status::Pending, s!("A")).unwrap(); let t2 = replica.new_task(Status::Completed, s!("B")).unwrap(); let _t = replica.new_task(Status::Pending, s!("C")).unwrap(); - replica.rebuild_working_set().unwrap(); + replica.rebuild_working_set(true).unwrap(); let t1uuid = *t1.get_uuid(); let t2uuid = t2.get_uuid().to_string(); @@ -238,7 +238,7 @@ mod test { replica.new_task(Status::Pending, s!("A")).unwrap(); replica.new_task(Status::Completed, s!("B")).unwrap(); replica.new_task(Status::Deleted, s!("C")).unwrap(); - replica.rebuild_working_set().unwrap(); + replica.rebuild_working_set(true).unwrap(); let filter = Filter { conditions: vec![] }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) @@ -309,7 +309,7 @@ mod test { replica.new_task(Status::Pending, s!("A")).unwrap(); replica.new_task(Status::Completed, s!("B")).unwrap(); replica.new_task(Status::Deleted, s!("C")).unwrap(); - replica.rebuild_working_set().unwrap(); + replica.rebuild_working_set(true).unwrap(); let filter = Filter { conditions: vec![Condition::Status(Status::Pending)], diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index e1df81db3..c1e744238 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -163,7 +163,7 @@ mod test { t2.set_status(Status::Completed).unwrap(); let t2 = t2.into_immut(); - replica.rebuild_working_set().unwrap(); + replica.rebuild_working_set(true).unwrap(); [*t1.get_uuid(), *t2.get_uuid(), *t3.get_uuid()] } diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 5190ca16c..a463ee6cd 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -170,12 +170,14 @@ impl Replica { self.taskdb.sync(server) } - /// Perform "garbage collection" on this replica. In particular, this renumbers the working - /// set to contain only pending tasks. - pub fn rebuild_working_set(&mut self) -> Fallible<()> { + /// 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. + pub fn rebuild_working_set(&mut self, renumber: bool) -> Fallible<()> { let pending = String::from(Status::Pending.to_taskmap()); self.taskdb - .rebuild_working_set(|t| t.get("status") == Some(&pending))?; + .rebuild_working_set(|t| t.get("status") == Some(&pending), renumber)?; Ok(()) } } @@ -251,7 +253,7 @@ mod tests { assert_eq!(t.get_status(), Status::Deleted); assert_eq!(t.get_description(), "gone"); - rep.rebuild_working_set().unwrap(); + rep.rebuild_working_set(true).unwrap(); assert!(rep.get_working_set_index(t.get_uuid()).unwrap().is_none()); } diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 0dd759814..71e49b042 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -8,6 +8,9 @@ use std::collections::HashSet; use std::str; use uuid::Uuid; +/// A TaskDB is the backend for a replica. It manages the storage, operations, synchronization, +/// and so on, and all the invariants that come with it. It leaves the meaning of particular task +/// properties to the replica and task implementations. pub struct TaskDB { storage: Box, } @@ -105,7 +108,7 @@ impl TaskDB { /// renumbers the existing working-set tasks to eliminate gaps, and also adds any tasks that /// are not already in the working set but should be. The rebuild occurs in a single /// trasnsaction against the storage backend. - pub fn rebuild_working_set(&mut self, in_working_set: F) -> Fallible<()> + pub fn rebuild_working_set(&mut self, in_working_set: F, renumber: bool) -> Fallible<()> where F: Fn(&TaskMap) -> bool, { @@ -122,27 +125,44 @@ impl TaskDB { if let Some(uuid) = elt { if let Some(task) = txn.get_task(&uuid)? { if in_working_set(&task) { - new_ws.push(uuid); + new_ws.push(Some(uuid)); seen.insert(uuid); + continue; } } } + + // if we are not renumbering, then insert a blank working-set entry here + if !renumber { + new_ws.push(None); + } + } + + // if renumbering, clear the working set and re-add + if renumber { + txn.clear_working_set()?; + for elt in new_ws.drain(0..new_ws.len()) { + if let Some(uuid) = elt { + txn.add_to_working_set(&uuid)?; + } + } + } else { + // ..otherwise, just clear the None items determined above from the working set + for (i, elt) in new_ws.iter().enumerate() { + if elt.is_none() { + txn.set_working_set_item(i, None)?; + } + } } // Now go hunting for tasks that should be in this list but are not, adding them at the - // end of the list. + // end of the list, whether renumbering or not for (uuid, task) in txn.all_tasks()? { if !seen.contains(&uuid) && in_working_set(&task) { - new_ws.push(uuid); + txn.add_to_working_set(&uuid)?; } } - // clear and re-write the entire working set, in order - txn.clear_working_set()?; - for uuid in new_ws.drain(0..new_ws.len()) { - txn.add_to_working_set(&uuid)?; - } - txn.commit()?; Ok(()) } @@ -482,15 +502,28 @@ mod tests { } #[test] - fn rebuild_working_set() -> Fallible<()> { + fn rebuild_working_set_renumber() -> Fallible<()> { + rebuild_working_set(true) + } + + #[test] + fn rebuild_working_set_no_renumber() -> Fallible<()> { + rebuild_working_set(false) + } + + fn rebuild_working_set(renumber: bool) -> Fallible<()> { let mut db = TaskDB::new_inmemory(); - let uuids = vec![ - Uuid::new_v4(), // 0: pending, not already in working set - Uuid::new_v4(), // 1: pending, already in working set - Uuid::new_v4(), // 2: not pending, not already in working set - Uuid::new_v4(), // 3: not pending, already in working set - Uuid::new_v4(), // 4: pending, already in working set - ]; + let mut uuids = vec![]; + uuids.push(Uuid::new_v4()); + println!("uuids[0]: {:?} - pending, not in working set", uuids[0]); + uuids.push(Uuid::new_v4()); + println!("uuids[1]: {:?} - pending, in working set", uuids[1]); + uuids.push(Uuid::new_v4()); + println!("uuids[2]: {:?} - not pending, not in working set", uuids[2]); + uuids.push(Uuid::new_v4()); + println!("uuids[3]: {:?} - not pending, in working set", uuids[3]); + uuids.push(Uuid::new_v4()); + println!("uuids[4]: {:?} - pending, in working set", uuids[4]); // add everything to the TaskDB for uuid in &uuids { @@ -527,25 +560,39 @@ mod tests { ] ); - db.rebuild_working_set(|t| { - if let Some(status) = t.get("status") { - status == "pending" - } else { - false - } - })?; + db.rebuild_working_set( + |t| { + if let Some(status) = t.get("status") { + status == "pending" + } else { + false + } + }, + renumber, + )?; - // uuids[1] and uuids[4] are already in the working set, so are compressed - // to the top, and then uuids[0] is added. - assert_eq!( - db.working_set()?, + let exp = if renumber { + // uuids[1] and uuids[4] are already in the working set, so are compressed + // to the top, and then uuids[0] is added. vec![ None, Some(uuids[1].clone()), Some(uuids[4].clone()), - Some(uuids[0].clone()) + Some(uuids[0].clone()), ] - ); + } else { + // uuids[1] and uuids[4] are already in the working set, at indexes 1 and 3, + // and then uuids[0] is added. + vec![ + None, + Some(uuids[1].clone()), + None, + Some(uuids[4].clone()), + Some(uuids[0].clone()), + ] + }; + + assert_eq!(db.working_set()?, exp); Ok(()) } diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs index 2c3899cb6..163312f8c 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/taskstorage/inmemory.rs @@ -3,7 +3,7 @@ use crate::taskstorage::{ Operation, TaskMap, TaskStorage, TaskStorageTxn, VersionId, DEFAULT_BASE_VERSION, }; -use failure::Fallible; +use failure::{bail, Fallible}; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; @@ -114,6 +114,15 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(working_set.len()) } + fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Fallible<()> { + let working_set = &mut self.mut_data_ref().working_set; + if index >= working_set.len() { + bail!("Index {} is not in the working set", index); + } + working_set[index] = uuid; + Ok(()) + } + fn clear_working_set(&mut self) -> Fallible<()> { self.mut_data_ref().working_set = vec![None]; Ok(()) diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index 5e1cfde41..65d1f331f 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -2,7 +2,7 @@ use crate::taskstorage::{ Operation, TaskMap, TaskStorage, TaskStorageTxn, VersionId, DEFAULT_BASE_VERSION, }; use crate::utils::Key; -use failure::Fallible; +use failure::{bail, Fallible}; use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; use std::path::Path; @@ -299,6 +299,35 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(next_index as usize) } + fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Fallible<()> { + let working_set_bucket = self.working_set_bucket(); + let numbers_bucket = self.numbers_bucket(); + let kvtxn = self.kvtxn(); + let index = index as u64; + + let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { + Ok(buf) => buf.inner()?.to_serde(), + Err(Error::NotFound) => 1, + Err(e) => return Err(e.into()), + }; + + if index >= next_index { + bail!("Index {} is not in the working set", index); + } + + if let Some(uuid) = uuid { + kvtxn.set( + working_set_bucket, + index.into(), + Msgpack::to_value_buf(uuid)?, + )?; + } else { + kvtxn.del(working_set_bucket, index.into())?; + } + + Ok(()) + } + fn clear_working_set(&mut self) -> Fallible<()> { let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index 4ddd7df75..2b1eecbb5 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -88,6 +88,10 @@ pub trait TaskStorageTxn { /// than the highest used index. fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible; + /// Update the working set task at the given index. This cannot add a new item to the + /// working set. + fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Fallible<()>; + /// Clear all tasks from the working set in preparation for a garbage-collection operation. /// Note that this is the only way items are removed from the set. fn clear_working_set(&mut self) -> Fallible<()>; From 5e92770eb867037e4e875022ae1434c40228461d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 2 Jan 2021 14:43:15 -0500 Subject: [PATCH 166/548] Automatically rebuild working set after sync --- taskchampion/src/replica.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index a463ee6cd..7c3df1e30 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -165,9 +165,12 @@ impl Replica { Ok(()) } - /// Synchronize this replica against the given server. + /// Synchronize this replica against the given server. The working set is rebuilt after + /// this occurs, but without renumbering, so any newly-pending tasks should appear in + /// the working set. pub fn sync(&mut self, server: &mut Box) -> Fallible<()> { - self.taskdb.sync(server) + self.taskdb.sync(server)?; + self.rebuild_working_set(false) } /// Rebuild this replica's working set, based on whether tasks are pending or not. If From 45d3e38c63dbc0fa3844bfceacaa41bdef647ebc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 9 Jan 2021 22:09:06 +0000 Subject: [PATCH 167/548] Always pass Uuids by value Rust handles this well. Fixes #125. --- cli/src/invocation/filter.rs | 20 ++++++++-------- cli/src/invocation/report.rs | 20 ++++++++-------- taskchampion/src/replica.rs | 30 ++++++++++++------------ taskchampion/src/task.rs | 8 +++---- taskchampion/src/taskdb.rs | 18 +++++++------- taskchampion/src/taskstorage/inmemory.rs | 24 +++++++++---------- taskchampion/src/taskstorage/kv.rs | 30 ++++++++++++------------ taskchampion/src/taskstorage/mod.rs | 6 ++--- 8 files changed, 78 insertions(+), 78 deletions(-) diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index 0cab337ae..f9440dcd9 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -125,18 +125,18 @@ pub(super) fn filtered_tasks( let task = match id { TaskId::WorkingSetId(id) => replica.get_working_set_task(*id)?, TaskId::PartialUuid(_) => unreachable!(), // not present in absolute id list - TaskId::Uuid(id) => replica.get_task(id)?, + TaskId::Uuid(id) => replica.get_task(*id)?, }; if let Some(task) = task { // if we have already seen this task, skip ahead.. - let uuid = *task.get_uuid(); + let uuid = task.get_uuid(); if seen.contains(&uuid) { continue; } seen.insert(uuid); - let working_set_id = replica.get_working_set_index(&uuid)?; + let working_set_id = replica.get_working_set_index(uuid)?; if match_task(filter, &task, uuid, working_set_id) { res.push(task); @@ -150,7 +150,7 @@ pub(super) fn filtered_tasks( log::debug!("Scanning all tasks in the task database"); for (uuid, task) in replica.all_tasks()?.drain() { // Yikes, slow! https://github.com/djmitche/taskchampion/issues/108 - let working_set_id = replica.get_working_set_index(&uuid)?; + let working_set_id = replica.get_working_set_index(uuid)?; if match_task(filter, &task, uuid, working_set_id) { res.push(task); } @@ -160,7 +160,7 @@ pub(super) fn filtered_tasks( log::debug!("Scanning only the working set (pending tasks)"); for (i, task) in replica.working_set()?.drain(..).enumerate() { if let Some(task) = task { - let uuid = *task.get_uuid(); + let uuid = task.get_uuid(); if match_task(filter, &task, uuid, Some(i)) { res.push(task); } @@ -186,13 +186,13 @@ mod test { let _t = replica.new_task(Status::Pending, s!("C")).unwrap(); replica.rebuild_working_set(true).unwrap(); - let t1uuid = *t1.get_uuid(); + let t1uuid = t1.get_uuid(); let filter = Filter { conditions: vec![Condition::IdList(vec![ - TaskId::Uuid(t1uuid), // A - TaskId::WorkingSetId(1), // A (again, dups filtered) - TaskId::Uuid(*t2.get_uuid()), // B + TaskId::Uuid(t1uuid), // A + TaskId::WorkingSetId(1), // A (again, dups filtered) + TaskId::Uuid(t2.get_uuid()), // B ])], }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) @@ -212,7 +212,7 @@ mod test { let _t = replica.new_task(Status::Pending, s!("C")).unwrap(); replica.rebuild_working_set(true).unwrap(); - let t1uuid = *t1.get_uuid(); + let t1uuid = t1.get_uuid(); let t2uuid = t2.get_uuid().to_string(); let t2partial = t2uuid[..13].to_owned(); diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index c1e744238..12497553c 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -18,15 +18,15 @@ impl WorkingSet { Ok(Self( working_set .iter() - .map(|opt| opt.as_ref().map(|t| *t.get_uuid())) + .map(|opt| opt.as_ref().map(|t| t.get_uuid())) .collect(), )) } - fn index(&self, target: &Uuid) -> Option { + fn index(&self, target: Uuid) -> Option { for (i, uuid) in self.0.iter().enumerate() { if let Some(uuid) = uuid { - if uuid == target { + if *uuid == target { return Some(i); } } @@ -51,10 +51,10 @@ fn sort_tasks(tasks: &mut Vec, report: &Report, working_set: &WorkingSet) (Some(a_id), Some(b_id)) => a_id.cmp(&b_id), (Some(_), None) => Ordering::Less, (None, Some(_)) => Ordering::Greater, - (None, None) => a_uuid.cmp(b_uuid), + (None, None) => a_uuid.cmp(&b_uuid), } } - SortBy::Uuid => a.get_uuid().cmp(b.get_uuid()), + SortBy::Uuid => a.get_uuid().cmp(&b.get_uuid()), SortBy::Description => a.get_description().cmp(b.get_description()), }; // If this sort property is equal, go on to the next.. @@ -165,7 +165,7 @@ mod test { replica.rebuild_working_set(true).unwrap(); - [*t1.get_uuid(), *t2.get_uuid(), *t3.get_uuid()] + [t1.get_uuid(), t2.get_uuid(), t3.get_uuid()] } #[test] @@ -237,7 +237,7 @@ mod test { let mut tasks: Vec<_> = replica.all_tasks().unwrap().values().cloned().collect(); sort_tasks(&mut tasks, &report, &working_set); - let got_uuids: Vec<_> = tasks.iter().map(|t| *t.get_uuid()).collect(); + let got_uuids: Vec<_> = tasks.iter().map(|t| t.get_uuid()).collect(); let mut exp_uuids = uuids.to_vec(); exp_uuids.sort(); assert_eq!(got_uuids, exp_uuids); @@ -291,7 +291,7 @@ mod test { // get the task that's not in the working set, which should show // a uuid for its id column - let task = replica.get_task(&uuids[1]).unwrap().unwrap(); + let task = replica.get_task(uuids[1]).unwrap().unwrap(); assert_eq!( task_column(&task, &column, &working_set), uuids[1].to_string() @@ -323,7 +323,7 @@ mod test { // make task A active replica - .get_task(&uuids[0]) + .get_task(uuids[0]) .unwrap() .unwrap() .into_mut(&mut replica) @@ -363,7 +363,7 @@ mod test { // add some tags to task A let mut t1 = replica - .get_task(&uuids[0]) + .get_task(uuids[0]) .unwrap() .unwrap() .into_mut(&mut replica); diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 7c3df1e30..fa20c7c3b 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -69,7 +69,7 @@ impl Replica { } /// Add the given uuid to the working set, returning its index. - pub(crate) fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + pub(crate) fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { self.taskdb.add_to_working_set(uuid) } @@ -94,7 +94,7 @@ impl Replica { let mut res = Vec::with_capacity(working_set.len()); for item in working_set.iter() { res.push(match item { - Some(u) => match self.taskdb.get_task(u)? { + Some(u) => match self.taskdb.get_task(*u)? { Some(tm) => Some(Task::new(*u, tm)), None => None, }, @@ -105,11 +105,11 @@ impl Replica { } /// Get an existing task by its UUID - pub fn get_task(&mut self, uuid: &Uuid) -> Fallible> { + pub fn get_task(&mut self, uuid: Uuid) -> Fallible> { Ok(self .taskdb .get_task(uuid)? - .map(move |tm| Task::new(*uuid, tm))) + .map(move |tm| Task::new(uuid, tm))) } /// Get an existing task by its working set index @@ -119,7 +119,7 @@ impl Replica { if let Some(uuid) = working_set[i as usize] { return Ok(self .taskdb - .get_task(&uuid)? + .get_task(uuid)? .map(move |tm| Task::new(uuid, tm))); } } @@ -127,11 +127,11 @@ impl Replica { } /// Get the working set index for the given task uuid - pub fn get_working_set_index(&mut self, uuid: &Uuid) -> Fallible> { + pub fn get_working_set_index(&mut self, uuid: Uuid) -> Fallible> { let working_set = self.taskdb.working_set()?; for (i, u) in working_set.iter().enumerate() { - if let Some(ref u) = u { - if u == uuid { + if let Some(u) = u { + if *u == uuid { return Ok(Some(i)); } } @@ -154,13 +154,13 @@ impl Replica { /// Deleted; this is the final purge of the task. This is not a public method as deletion /// should only occur through expiration. #[allow(dead_code)] - fn delete_task(&mut self, uuid: &Uuid) -> Fallible<()> { + fn delete_task(&mut self, uuid: Uuid) -> Fallible<()> { // check that it already exists; this is a convenience check, as the task may already exist // when this Create operation is finally sync'd with operations from other replicas if self.taskdb.get_task(uuid)?.is_none() { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } - self.taskdb.apply(Operation::Delete { uuid: *uuid })?; + self.taskdb.apply(Operation::Delete { uuid })?; trace!("task {} deleted", uuid); Ok(()) } @@ -220,7 +220,7 @@ mod tests { assert_eq!(t.get_status(), Status::Completed); // check tha values have changed in storage, too - let t = rep.get_task(&t.get_uuid()).unwrap().unwrap(); + let t = rep.get_task(t.get_uuid()).unwrap().unwrap(); assert_eq!(t.get_description(), "past tense"); assert_eq!(t.get_status(), Status::Completed); } @@ -233,7 +233,7 @@ mod tests { let uuid = t.get_uuid(); rep.delete_task(uuid).unwrap(); - assert_eq!(rep.get_task(&uuid).unwrap(), None); + assert_eq!(rep.get_task(uuid).unwrap(), None); } #[test] @@ -245,14 +245,14 @@ mod tests { .unwrap(); let uuid = t.get_uuid(); - let t = rep.get_task(&uuid).unwrap().unwrap(); + let t = rep.get_task(uuid).unwrap().unwrap(); assert_eq!(t.get_description(), String::from("another task")); let mut t = t.into_mut(&mut rep); t.set_status(Status::Deleted).unwrap(); t.set_description("gone".into()).unwrap(); - let t = rep.get_task(&uuid).unwrap().unwrap(); + let t = rep.get_task(uuid).unwrap().unwrap(); assert_eq!(t.get_status(), Status::Deleted); assert_eq!(t.get_description(), "gone"); @@ -286,6 +286,6 @@ mod tests { fn get_does_not_exist() { let mut rep = Replica::new_inmemory(); let uuid = Uuid::new_v4(); - assert_eq!(rep.get_task(&uuid).unwrap(), None); + assert_eq!(rep.get_task(uuid).unwrap(), None); } } diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 8736a97da..99b92873a 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -167,8 +167,8 @@ impl Task { Task { uuid, taskmap } } - pub fn get_uuid(&self) -> &Uuid { - &self.uuid + pub fn get_uuid(&self) -> Uuid { + self.uuid } pub fn get_taskmap(&self) -> &TaskMap { @@ -254,7 +254,7 @@ impl<'r> TaskMut<'r> { pub fn set_status(&mut self, status: Status) -> Fallible<()> { if status == Status::Pending { let uuid = self.uuid; - self.replica.add_to_working_set(&uuid)?; + self.replica.add_to_working_set(uuid)?; } self.set_string("status", Some(String::from(status.to_taskmap()))) } @@ -353,7 +353,7 @@ impl<'r> TaskMut<'r> { #[cfg(test)] fn reload(&mut self) -> Fallible<()> { let uuid = self.uuid; - let task = self.replica.get_task(&uuid)?.unwrap(); + let task = self.replica.get_task(uuid)?.unwrap(); self.task.taskmap = task.taskmap; Ok(()) } diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 71e49b042..c04a26c36 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -54,7 +54,7 @@ impl TaskDB { } } Operation::Delete { ref uuid } => { - if !txn.delete_task(uuid)? { + if !txn.delete_task(*uuid)? { return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); } } @@ -65,7 +65,7 @@ impl TaskDB { timestamp: _, } => { // update if this task exists, otherwise ignore - if let Some(mut task) = txn.get_task(uuid)? { + if let Some(mut task) = txn.get_task(*uuid)? { match value { Some(ref val) => task.insert(property.to_string(), val.clone()), None => task.remove(property), @@ -99,7 +99,7 @@ impl TaskDB { } /// Get a single task, by uuid. - pub fn get_task(&mut self, uuid: &Uuid) -> Fallible> { + pub fn get_task(&mut self, uuid: Uuid) -> Fallible> { let mut txn = self.storage.txn()?; txn.get_task(uuid) } @@ -123,7 +123,7 @@ impl TaskDB { // working set. for elt in txn.get_working_set()? { if let Some(uuid) = elt { - if let Some(task) = txn.get_task(&uuid)? { + if let Some(task) = txn.get_task(uuid)? { if in_working_set(&task) { new_ws.push(Some(uuid)); seen.insert(uuid); @@ -143,7 +143,7 @@ impl TaskDB { txn.clear_working_set()?; for elt in new_ws.drain(0..new_ws.len()) { if let Some(uuid) = elt { - txn.add_to_working_set(&uuid)?; + txn.add_to_working_set(uuid)?; } } } else { @@ -159,7 +159,7 @@ impl TaskDB { // end of the list, whether renumbering or not for (uuid, task) in txn.all_tasks()? { if !seen.contains(&uuid) && in_working_set(&task) { - txn.add_to_working_set(&uuid)?; + txn.add_to_working_set(uuid)?; } } @@ -169,11 +169,11 @@ impl TaskDB { /// Add the given uuid to the working set and return its index; if it is already in the working /// set, its index is returned. This does *not* renumber any existing tasks. - pub fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + pub fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { let mut txn = self.storage.txn()?; // search for an existing entry for this task.. for (i, elt) in txn.get_working_set()?.iter().enumerate() { - if *elt == Some(*uuid) { + if *elt == Some(uuid) { // (note that this drops the transaction with no changes made) return Ok(i); } @@ -544,7 +544,7 @@ mod tests { txn.clear_working_set()?; for i in &[1usize, 3, 4] { - txn.add_to_working_set(&uuids[*i])?; + txn.add_to_working_set(uuids[*i])?; } txn.commit()?; diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/taskstorage/inmemory.rs index 163312f8c..718ff1b1e 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/taskstorage/inmemory.rs @@ -43,8 +43,8 @@ impl<'t> Txn<'t> { } impl<'t> TaskStorageTxn for Txn<'t> { - fn get_task(&mut self, uuid: &Uuid) -> Fallible> { - match self.data_ref().tasks.get(uuid) { + fn get_task(&mut self, uuid: Uuid) -> Fallible> { + match self.data_ref().tasks.get(&uuid) { None => Ok(None), Some(t) => Ok(Some(t.clone())), } @@ -64,8 +64,8 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(()) } - fn delete_task(&mut self, uuid: &Uuid) -> Fallible { - Ok(self.mut_data_ref().tasks.remove(uuid).is_some()) + fn delete_task(&mut self, uuid: Uuid) -> Fallible { + Ok(self.mut_data_ref().tasks.remove(&uuid).is_some()) } fn all_tasks<'a>(&mut self) -> Fallible> { @@ -108,9 +108,9 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(self.data_ref().working_set.clone()) } - fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { let working_set = &mut self.mut_data_ref().working_set; - working_set.push(Some(*uuid)); + working_set.push(Some(uuid)); Ok(working_set.len()) } @@ -194,8 +194,8 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(&uuid1)?; - txn.add_to_working_set(&uuid2)?; + txn.add_to_working_set(uuid1)?; + txn.add_to_working_set(uuid2)?; txn.commit()?; } @@ -216,16 +216,16 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(&uuid1)?; - txn.add_to_working_set(&uuid2)?; + txn.add_to_working_set(uuid1)?; + txn.add_to_working_set(uuid2)?; txn.commit()?; } { let mut txn = storage.txn()?; txn.clear_working_set()?; - txn.add_to_working_set(&uuid2)?; - txn.add_to_working_set(&uuid1)?; + txn.add_to_working_set(uuid2)?; + txn.add_to_working_set(uuid1)?; txn.commit()?; } diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/taskstorage/kv.rs index 65d1f331f..4bbd087be 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/taskstorage/kv.rs @@ -105,7 +105,7 @@ impl<'t> Txn<'t> { } impl<'t> TaskStorageTxn for Txn<'t> { - fn get_task(&mut self, uuid: &Uuid) -> Fallible> { + fn get_task(&mut self, uuid: Uuid) -> Fallible> { let bucket = self.tasks_bucket(); let buf = match self.kvtxn().get(bucket, uuid.into()) { Ok(buf) => buf, @@ -136,7 +136,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(()) } - fn delete_task(&mut self, uuid: &Uuid) -> Fallible { + fn delete_task(&mut self, uuid: Uuid) -> Fallible { let bucket = self.tasks_bucket(); let kvtxn = self.kvtxn(); match kvtxn.del(bucket, uuid.into()) { @@ -275,7 +275,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { Ok(res) } - fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible { + fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); let kvtxn = self.kvtxn(); @@ -289,7 +289,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { kvtxn.set( working_set_bucket, next_index.into(), - Msgpack::to_value_buf(*uuid)?, + Msgpack::to_value_buf(uuid)?, )?; kvtxn.set( numbers_bucket, @@ -372,7 +372,7 @@ mod test { } { let mut txn = storage.txn()?; - let task = txn.get_task(&uuid)?; + let task = txn.get_task(uuid)?; assert_eq!(task, Some(taskmap_with(vec![]))); } Ok(()) @@ -403,7 +403,7 @@ mod test { let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; - let task = txn.get_task(&uuid)?; + let task = txn.get_task(uuid)?; assert_eq!(task, None); } Ok(()) @@ -421,7 +421,7 @@ mod test { } { let mut txn = storage.txn()?; - let task = txn.get_task(&uuid)?; + let task = txn.get_task(uuid)?; assert_eq!( task, Some(taskmap_with(vec![("k".to_string(), "v".to_string())])) @@ -437,7 +437,7 @@ mod test { let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; - assert!(!txn.delete_task(&uuid)?); + assert!(!txn.delete_task(uuid)?); } Ok(()) } @@ -454,7 +454,7 @@ mod test { } { let mut txn = storage.txn()?; - assert!(txn.delete_task(&uuid)?); + assert!(txn.delete_task(uuid)?); } Ok(()) } @@ -640,8 +640,8 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(&uuid1)?; - txn.add_to_working_set(&uuid2)?; + txn.add_to_working_set(uuid1)?; + txn.add_to_working_set(uuid2)?; txn.commit()?; } @@ -663,16 +663,16 @@ mod test { { let mut txn = storage.txn()?; - txn.add_to_working_set(&uuid1)?; - txn.add_to_working_set(&uuid2)?; + txn.add_to_working_set(uuid1)?; + txn.add_to_working_set(uuid2)?; txn.commit()?; } { let mut txn = storage.txn()?; txn.clear_working_set()?; - txn.add_to_working_set(&uuid2)?; - txn.add_to_working_set(&uuid1)?; + txn.add_to_working_set(uuid2)?; + txn.add_to_working_set(uuid1)?; txn.commit()?; } diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index 2b1eecbb5..571c0d8b5 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -44,7 +44,7 @@ pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NO_VERSION_ID; /// It is safe and performant to drop transactions that did not modify any data without committing. pub trait TaskStorageTxn { /// Get an (immutable) task, if it is in the storage - fn get_task(&mut self, uuid: &Uuid) -> Fallible>; + fn get_task(&mut self, uuid: Uuid) -> Fallible>; /// Create an (empty) task, only if it does not already exist. Returns true if /// the task was created (did not already exist). @@ -55,7 +55,7 @@ pub trait TaskStorageTxn { fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible<()>; /// Delete a task, if it exists. Returns true if the task was deleted (already existed) - fn delete_task(&mut self, uuid: &Uuid) -> Fallible; + fn delete_task(&mut self, uuid: Uuid) -> Fallible; /// Get the uuids and bodies of all tasks in the storage, in undefined order. fn all_tasks(&mut self) -> Fallible>; @@ -86,7 +86,7 @@ pub trait TaskStorageTxn { /// Add a task to the working set and return its (one-based) index. This index will be one greater /// than the highest used index. - fn add_to_working_set(&mut self, uuid: &Uuid) -> Fallible; + fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible; /// Update the working set task at the given index. This cannot add a new item to the /// working set. From 087769146e5e8e684dae3f8a9d0fb68d6ce08c78 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 9 Jan 2021 22:57:33 +0000 Subject: [PATCH 168/548] Centralize API for working set to a single struct Rather than allow addressing tasks either by working set ID or uuid, with attendant performance issues, this moves the API for the working set to a single struct that just serves as a 1-1 mapping of indexes to UUIDs. It's up to the caller to use this information. --- cli/src/invocation/cmd/add.rs | 6 +- cli/src/invocation/cmd/info.rs | 4 +- cli/src/invocation/filter.rs | 29 ++++---- cli/src/invocation/report.rs | 74 ++++++------------ taskchampion/src/lib.rs | 2 + taskchampion/src/replica.rs | 63 +++------------- taskchampion/src/workingset.rs | 132 +++++++++++++++++++++++++++++++++ 7 files changed, 194 insertions(+), 116 deletions(-) create mode 100644 taskchampion/src/workingset.rs diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index 104a6357c..1175b587e 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -33,7 +33,11 @@ mod test { execute(&mut w, &mut replica, modification).unwrap(); // check that the task appeared.. - let task = replica.get_working_set_task(1).unwrap().unwrap(); + let working_set = replica.working_set().unwrap(); + let task = replica + .get_task(working_set.by_index(1).unwrap()) + .unwrap() + .unwrap(); assert_eq!(task.get_description(), "my description"); assert_eq!(task.get_status(), Status::Pending); diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index 5d70163c7..603093315 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -12,6 +12,8 @@ pub(crate) fn execute( filter: Filter, debug: bool, ) -> Fallible<()> { + let working_set = replica.working_set()?; + for task in filtered_tasks(replica, &filter)? { let uuid = task.get_uuid(); @@ -24,7 +26,7 @@ pub(crate) fn execute( } } else { t.add_row(row![b->"Uuid", uuid]); - if let Some(i) = replica.get_working_set_index(uuid)? { + if let Some(i) = working_set.by_uuid(uuid) { t.add_row(row![b->"Id", i]); } t.add_row(row![b->"Description", task.get_description()]); diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index f9440dcd9..28e25c238 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -2,9 +2,9 @@ use crate::argparse::{Condition, Filter, TaskId}; use failure::Fallible; use std::collections::HashSet; use std::convert::TryInto; -use taskchampion::{Replica, Status, Tag, Task, Uuid}; +use taskchampion::{Replica, Status, Tag, Task, Uuid, WorkingSet}; -fn match_task(filter: &Filter, task: &Task, uuid: Uuid, working_set_id: Option) -> bool { +fn match_task(filter: &Filter, task: &Task, uuid: Uuid, working_set: &WorkingSet) -> bool { for cond in &filter.conditions { match cond { Condition::HasTag(ref tag) => { @@ -29,6 +29,8 @@ fn match_task(filter: &Filter, task: &Task, uuid: Uuid, working_set_id: Option { let uuid_str = uuid.to_string(); let mut found = false; + let working_set_id = working_set.by_uuid(uuid); + for id in ids { if match id { TaskId::WorkingSetId(i) => Some(*i) == working_set_id, @@ -111,6 +113,8 @@ pub(super) fn filtered_tasks( log::debug!("Applying filter {:?}", filter); + let working_set = replica.working_set()?; + // We will enumerate the universe of tasks for this filter, checking // each resulting task with match_task match universe_for_filter(filter) { @@ -123,7 +127,11 @@ pub(super) fn filtered_tasks( let mut seen = HashSet::new(); for id in ids { let task = match id { - TaskId::WorkingSetId(id) => replica.get_working_set_task(*id)?, + TaskId::WorkingSetId(id) => working_set + .by_index(*id) + .map(|uuid| replica.get_task(uuid)) + .transpose()? + .flatten(), TaskId::PartialUuid(_) => unreachable!(), // not present in absolute id list TaskId::Uuid(id) => replica.get_task(*id)?, }; @@ -136,9 +144,7 @@ pub(super) fn filtered_tasks( } seen.insert(uuid); - let working_set_id = replica.get_working_set_index(uuid)?; - - if match_task(filter, &task, uuid, working_set_id) { + if match_task(filter, &task, uuid, &working_set) { res.push(task); } } @@ -149,19 +155,16 @@ pub(super) fn filtered_tasks( Universe::AllTasks => { log::debug!("Scanning all tasks in the task database"); for (uuid, task) in replica.all_tasks()?.drain() { - // Yikes, slow! https://github.com/djmitche/taskchampion/issues/108 - let working_set_id = replica.get_working_set_index(uuid)?; - if match_task(filter, &task, uuid, working_set_id) { + if match_task(filter, &task, uuid, &working_set) { res.push(task); } } } Universe::WorkingSet => { log::debug!("Scanning only the working set (pending tasks)"); - for (i, task) in replica.working_set()?.drain(..).enumerate() { - if let Some(task) = task { - let uuid = task.get_uuid(); - if match_task(filter, &task, uuid, Some(i)) { + for (_, uuid) in working_set.iter() { + if let Some(task) = replica.get_task(uuid)? { + if match_task(filter, &task, uuid, &working_set) { res.push(task); } } diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 12497553c..185eaa4b3 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -6,35 +6,9 @@ use config::Config; use failure::{format_err, Fallible}; use prettytable::{Row, Table}; use std::cmp::Ordering; -use taskchampion::{Replica, Task, Uuid}; +use taskchampion::{Replica, Task, WorkingSet}; use termcolor::WriteColor; -// pending #123, this is a non-fallible way of looking up a task's working set index -struct WorkingSet(Vec>); - -impl WorkingSet { - fn new(replica: &mut Replica) -> Fallible { - let working_set = replica.working_set()?; - Ok(Self( - working_set - .iter() - .map(|opt| opt.as_ref().map(|t| t.get_uuid())) - .collect(), - )) - } - - fn index(&self, target: Uuid) -> Option { - for (i, uuid) in self.0.iter().enumerate() { - if let Some(uuid) = uuid { - if *uuid == target { - return Some(i); - } - } - } - None - } -} - /// Sort tasks for the given report. fn sort_tasks(tasks: &mut Vec, report: &Report, working_set: &WorkingSet) { tasks.sort_by(|a, b| { @@ -43,8 +17,8 @@ fn sort_tasks(tasks: &mut Vec, report: &Report, working_set: &WorkingSet) SortBy::Id => { let a_uuid = a.get_uuid(); let b_uuid = b.get_uuid(); - let a_id = working_set.index(a_uuid); - let b_id = working_set.index(b_uuid); + let a_id = working_set.by_uuid(a_uuid); + let b_id = working_set.by_uuid(b_uuid); println!("a_uuid {} -> a_id {:?}", a_uuid, a_id); println!("b_uuid {} -> b_id {:?}", b_uuid, b_id); match (a_id, b_id) { @@ -78,7 +52,7 @@ fn task_column(task: &Task, column: &Column, working_set: &WorkingSet) -> String Property::Id => { let uuid = task.get_uuid(); let mut id = uuid.to_string(); - if let Some(i) = working_set.index(uuid) { + if let Some(i) = working_set.by_uuid(uuid) { id = i.to_string(); } id @@ -111,7 +85,7 @@ pub(super) fn display_report( filter: Filter, ) -> Fallible<()> { let mut t = Table::new(); - let working_set = WorkingSet::new(replica)?; + let working_set = replica.working_set()?; // Get the report from settings let mut report = Report::from_config(settings.get(&format!("reports.{}", report_name))?) @@ -151,7 +125,7 @@ mod test { use crate::invocation::test::*; use crate::report::Sort; use std::convert::TryInto; - use taskchampion::Status; + use taskchampion::{Status, Uuid}; fn create_tasks(replica: &mut Replica) -> [Uuid; 3] { let t1 = replica.new_task(Status::Pending, s!("A")).unwrap(); @@ -172,7 +146,7 @@ mod test { fn sorting_by_descr() { let mut replica = test_replica(); create_tasks(&mut replica); - let working_set = WorkingSet::new(&mut replica).unwrap(); + let working_set = replica.working_set().unwrap(); let mut report = Report { sort: vec![Sort { ascending: true, @@ -199,7 +173,7 @@ mod test { fn sorting_by_id() { let mut replica = test_replica(); create_tasks(&mut replica); - let working_set = WorkingSet::new(&mut replica).unwrap(); + let working_set = replica.working_set().unwrap(); let mut report = Report { sort: vec![Sort { ascending: true, @@ -226,7 +200,7 @@ mod test { fn sorting_by_uuid() { let mut replica = test_replica(); let uuids = create_tasks(&mut replica); - let working_set = WorkingSet::new(&mut replica).unwrap(); + let working_set = replica.working_set().unwrap(); let report = Report { sort: vec![Sort { ascending: true, @@ -254,7 +228,7 @@ mod test { .add_tag(&("second".try_into().unwrap())) .unwrap(); - let working_set = WorkingSet::new(&mut replica).unwrap(); + let working_set = replica.working_set().unwrap(); let report = Report { sort: vec![ Sort { @@ -280,9 +254,9 @@ mod test { fn task_column_id() { let mut replica = test_replica(); let uuids = create_tasks(&mut replica); - let working_set = WorkingSet::new(&mut replica).unwrap(); + let working_set = replica.working_set().unwrap(); - let task = replica.get_working_set_task(1).unwrap().unwrap(); + let task = replica.get_task(uuids[0]).unwrap().unwrap(); let column = Column { label: s!(""), property: Property::Id, @@ -301,10 +275,10 @@ mod test { #[test] fn task_column_uuid() { let mut replica = test_replica(); - create_tasks(&mut replica); - let working_set = WorkingSet::new(&mut replica).unwrap(); + let uuids = create_tasks(&mut replica); + let working_set = replica.working_set().unwrap(); - let task = replica.get_working_set_task(1).unwrap().unwrap(); + let task = replica.get_task(uuids[0]).unwrap().unwrap(); let column = Column { label: s!(""), property: Property::Uuid, @@ -319,7 +293,7 @@ mod test { fn task_column_active() { let mut replica = test_replica(); let uuids = create_tasks(&mut replica); - let working_set = WorkingSet::new(&mut replica).unwrap(); + let working_set = replica.working_set().unwrap(); // make task A active replica @@ -335,19 +309,19 @@ mod test { property: Property::Active, }; - let task = replica.get_working_set_task(1).unwrap().unwrap(); + let task = replica.get_task(uuids[0]).unwrap().unwrap(); assert_eq!(task_column(&task, &column, &working_set), s!("*")); - let task = replica.get_working_set_task(2).unwrap().unwrap(); + let task = replica.get_task(uuids[2]).unwrap().unwrap(); assert_eq!(task_column(&task, &column, &working_set), s!("")); } #[test] fn task_column_description() { let mut replica = test_replica(); - create_tasks(&mut replica); - let working_set = WorkingSet::new(&mut replica).unwrap(); + let uuids = create_tasks(&mut replica); + let working_set = replica.working_set().unwrap(); - let task = replica.get_working_set_task(2).unwrap().unwrap(); + let task = replica.get_task(uuids[2]).unwrap().unwrap(); let column = Column { label: s!(""), property: Property::Description, @@ -359,7 +333,7 @@ mod test { fn task_column_tags() { let mut replica = test_replica(); let uuids = create_tasks(&mut replica); - let working_set = WorkingSet::new(&mut replica).unwrap(); + let working_set = replica.working_set().unwrap(); // add some tags to task A let mut t1 = replica @@ -375,9 +349,9 @@ mod test { property: Property::Tags, }; - let task = replica.get_working_set_task(1).unwrap().unwrap(); + let task = replica.get_task(uuids[0]).unwrap().unwrap(); assert_eq!(task_column(&task, &column, &working_set), s!("+bar +foo")); - let task = replica.get_working_set_task(2).unwrap().unwrap(); + let task = replica.get_task(uuids[2]).unwrap().unwrap(); assert_eq!(task_column(&task, &column, &working_set), s!("")); } } diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index fb91f36e7..cdb9e8cc1 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -32,10 +32,12 @@ mod task; mod taskdb; pub mod taskstorage; mod utils; +mod workingset; pub use config::{ReplicaConfig, ServerConfig}; pub use replica::Replica; pub use task::{Priority, Status, Tag, Task, TaskMut}; +pub use workingset::WorkingSet; /// Re-exported type from the `uuid` crate, for ease of compatibility for consumers of this crate. pub use uuid::Uuid; diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index fa20c7c3b..95cbaf576 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -4,6 +4,7 @@ use crate::server::Server; use crate::task::{Status, Task}; use crate::taskdb::TaskDB; use crate::taskstorage::{KVStorage, Operation, TaskMap, TaskStorage}; +use crate::workingset::WorkingSet; use chrono::Utc; use failure::Fallible; use log::trace; @@ -87,21 +88,10 @@ impl Replica { self.taskdb.all_task_uuids() } - /// Get the "working set" for this replica -- the set of pending tasks, as indexed by small - /// integers - pub fn working_set(&mut self) -> Fallible>> { - let working_set = self.taskdb.working_set()?; - let mut res = Vec::with_capacity(working_set.len()); - for item in working_set.iter() { - res.push(match item { - Some(u) => match self.taskdb.get_task(*u)? { - Some(tm) => Some(Task::new(*u, tm)), - None => None, - }, - None => None, - }) - } - Ok(res) + /// Get the "working set" for this replica. This is a snapshot of the current state, + /// and it is up to the caller to decide how long to store this value. + pub fn working_set(&mut self) -> Fallible { + Ok(WorkingSet::new(self.taskdb.working_set()?)) } /// Get an existing task by its UUID @@ -112,33 +102,6 @@ impl Replica { .map(move |tm| Task::new(uuid, tm))) } - /// Get an existing task by its working set index - pub fn get_working_set_task(&mut self, i: usize) -> Fallible> { - let working_set = self.taskdb.working_set()?; - if (i as usize) < working_set.len() { - if let Some(uuid) = working_set[i as usize] { - return Ok(self - .taskdb - .get_task(uuid)? - .map(move |tm| Task::new(uuid, tm))); - } - } - Ok(None) - } - - /// Get the working set index for the given task uuid - pub fn get_working_set_index(&mut self, uuid: Uuid) -> Fallible> { - let working_set = self.taskdb.working_set()?; - for (i, u) in working_set.iter().enumerate() { - if let Some(u) = u { - if *u == uuid { - return Ok(Some(i)); - } - } - } - Ok(None) - } - /// Create a new task. The task must not already exist. pub fn new_task(&mut self, status: Status, description: String) -> Fallible { let uuid = Uuid::new_v4(); @@ -258,7 +221,8 @@ mod tests { rep.rebuild_working_set(true).unwrap(); - assert!(rep.get_working_set_index(t.get_uuid()).unwrap().is_none()); + let ws = rep.working_set().unwrap(); + assert!(ws.by_uuid(t.get_uuid()).is_none()); } #[test] @@ -270,16 +234,13 @@ mod tests { .unwrap(); let uuid = t.get_uuid(); - let t = rep.get_working_set_task(1).unwrap().unwrap(); - assert_eq!(t.get_status(), Status::Pending); - assert_eq!(t.get_description(), "to-be-pending"); + let ws = rep.working_set().unwrap(); + assert_eq!(ws.len(), 1); // only one non-none value + assert!(ws.by_index(0).is_none()); + assert_eq!(ws.by_index(1), Some(uuid)); let ws = rep.working_set().unwrap(); - assert_eq!(ws.len(), 2); - assert!(ws[0].is_none()); - assert_eq!(ws[1].as_ref().unwrap().get_uuid(), uuid); - - assert_eq!(rep.get_working_set_index(t.get_uuid()).unwrap().unwrap(), 1); + assert_eq!(ws.by_uuid(t.get_uuid()), Some(1)); } #[test] diff --git a/taskchampion/src/workingset.rs b/taskchampion/src/workingset.rs new file mode 100644 index 000000000..5bdc3696b --- /dev/null +++ b/taskchampion/src/workingset.rs @@ -0,0 +1,132 @@ +use std::collections::HashMap; +use uuid::Uuid; + +/// A WorkingSet represents a snapshot of the working set from a replica. +/// +/// A replica's working set is a mapping from small integers to task uuids for all pending tasks. +/// The small integers are meant to be stable, easily-typed identifiers for users to interact with +/// important tasks. +/// +/// IMPORTANT: the content of the working set may change at any time that a DB transaction is not +/// in progress, and the data in this type will not be updated automatically. It is up to the +/// caller to decide how long to keep this value, and how much to trust the accuracy of its +/// contents. In practice, the answers are usually "a few milliseconds" and treating unexpected +/// results as non-fatal. +pub struct WorkingSet { + by_index: Vec>, + by_uuid: HashMap, +} + +impl WorkingSet { + /// Create a new WorkingSet. Typically this is acquired via `replica.working_set()` + pub(crate) fn new(by_index: Vec>) -> Self { + let mut by_uuid = HashMap::new(); + for (index, uuid) in by_index.iter().enumerate() { + if let Some(uuid) = uuid { + by_uuid.insert(*uuid, index); + } + } + Self { by_index, by_uuid } + } + + /// Get the "length" of the working set: the total number of uuids in the set. + pub fn len(&self) -> usize { + self.by_index.iter().filter(|e| e.is_some()).count() + } + + /// True if the length is zero + pub fn is_empty(&self) -> bool { + self.by_index.iter().all(|e| e.is_none()) + } + + /// Get the uuid with the given index, if any exists. + pub fn by_index(&self, index: usize) -> Option { + if let Some(Some(uuid)) = self.by_index.get(index) { + Some(*uuid) + } else { + None + } + } + + /// Get the index for the given uuid, if any + pub fn by_uuid(&self, uuid: Uuid) -> Option { + self.by_uuid.get(&uuid).copied() + } + + /// Iterate over pairs (index, uuid), in order by index. + pub fn iter(&self) -> impl Iterator + '_ { + self.by_index + .iter() + .enumerate() + .filter_map(|(index, uuid)| { + if let Some(uuid) = uuid { + Some((index, *uuid)) + } else { + None + } + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn make() -> (Uuid, Uuid, WorkingSet) { + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + ( + uuid1, + uuid2, + WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2), None]), + ) + } + + #[test] + fn test_new() { + let (_, uuid2, ws) = make(); + assert_eq!(ws.by_index[3], Some(uuid2)); + assert_eq!(ws.by_uuid.get(&uuid2), Some(&3)); + } + + #[test] + fn test_len_and_is_empty() { + let (_, _, ws) = make(); + assert_eq!(ws.len(), 2); + assert_eq!(ws.is_empty(), false); + + let ws = WorkingSet::new(vec![]); + assert_eq!(ws.len(), 0); + assert_eq!(ws.is_empty(), true); + + let ws = WorkingSet::new(vec![None, None, None]); + assert_eq!(ws.len(), 0); + assert_eq!(ws.is_empty(), true); + } + + #[test] + fn test_by_index() { + let (uuid1, uuid2, ws) = make(); + assert_eq!(ws.by_index(0), None); + assert_eq!(ws.by_index(1), Some(uuid1)); + assert_eq!(ws.by_index(2), None); + assert_eq!(ws.by_index(3), Some(uuid2)); + assert_eq!(ws.by_index(4), None); + assert_eq!(ws.by_index(100), None); // past the end of the vector + } + + #[test] + fn test_by_uuid() { + let (uuid1, uuid2, ws) = make(); + let nosuch = Uuid::new_v4(); + assert_eq!(ws.by_uuid(uuid1), Some(1)); + assert_eq!(ws.by_uuid(uuid2), Some(3)); + assert_eq!(ws.by_uuid(nosuch), None); + } + + #[test] + fn test_iter() { + let (uuid1, uuid2, ws) = make(); + assert_eq!(ws.iter().collect::>(), vec![(1, uuid1), (3, uuid2),]); + } +} From b95c146a7e2525edef414b8eaa96a117c5f89d15 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Jan 2021 03:43:44 +0000 Subject: [PATCH 169/548] Adjust Tag syntax to match TaskWarrior --- cli/src/argparse/args.rs | 13 +++++++--- docs/src/SUMMARY.md | 1 + docs/src/tags.md | 12 ++++++++++ taskchampion/src/task.rs | 51 +++++++++++++++++++++++++--------------- 4 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 docs/src/tags.md diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs index 337bca763..a902fd690 100644 --- a/cli/src/argparse/args.rs +++ b/cli/src/argparse/args.rs @@ -10,7 +10,8 @@ use nom::{ sequence::*, Err, IResult, }; -use taskchampion::{Status, Uuid}; +use std::convert::TryFrom; +use taskchampion::{Status, Tag, Uuid}; /// A task identifier, as given in a filter command-line expression #[derive(Debug, PartialEq, Clone)] @@ -130,7 +131,10 @@ pub(super) fn plus_tag(input: &str) -> IResult<&str, &str> { Ok(input.1) } map_res( - all_consuming(tuple((char('+'), recognize(pair(alpha1, alphanumeric0))))), + all_consuming(tuple(( + char('+'), + recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), + ))), to_tag, )(input) } @@ -141,7 +145,10 @@ pub(super) fn minus_tag(input: &str) -> IResult<&str, &str> { Ok(input.1) } map_res( - all_consuming(tuple((char('-'), recognize(pair(alpha1, alphanumeric0))))), + all_consuming(tuple(( + char('-'), + recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), + ))), to_tag, )(input) } diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index c8f56bdc6..d8112b285 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -5,6 +5,7 @@ * [Using the Task Command](./using-task-command.md) * [Configuration](./config-file.md) * [Reports](./reports.md) + * [Tags](./tags.md) * [Synchronization](./task-sync.md) * [Running the Sync Server](./running-sync-server.md) * [Debugging](./debugging.md) diff --git a/docs/src/tags.md b/docs/src/tags.md new file mode 100644 index 000000000..4148d5117 --- /dev/null +++ b/docs/src/tags.md @@ -0,0 +1,12 @@ +# Tags + +Each task has a collection of associated tags. +Tags are short words that categorize tasks, typically written with a leading `+`, such as `+next` or `+jobsearch`. + +Tags are useful for filtering tasks in reports or on the command line. +For example, when it's time to continue the job search, `task +jobsearch` will show pending tasks with the `jobsearch` tag. + +## Allowed Tags + +Specifically, tags must be at least one character long and cannot contain whitespace or any of the characters `+-*/(<>^! %=~`. +The first character cannot be a digit, and `:` is not allowed after the first character. diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 99b92873a..1434f452a 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -84,22 +84,35 @@ impl Status { } /// A Tag is a newtype around a String that limits its values to valid tags. +/// +/// Valid tags must not contain whitespace or any of the characters in [`INVALID_TAG_CHARACTERS`]. +/// The first characters additionally cannot be a digit, and subsequent characters cannot be `:`. +/// This definition is based on [that of +/// TaskWarrior](https://github.com/GothenburgBitFactory/taskwarrior/blob/663c6575ceca5bd0135ae884879339dac89d3142/src/Lexer.cpp#L146-L164). #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Tag(String); +pub const INVALID_TAG_CHARACTERS: &str = "+-*/(<>^! %=~"; + impl Tag { fn from_str(value: &str) -> Result { + fn err(value: &str) -> Result { + Err(format_err!("invalid tag {:?}", value)) + } + if let Some(c) = value.chars().next() { - if !c.is_ascii_alphabetic() { - return Err(format_err!("first character of a tag must be alphabetic")); + if c.is_whitespace() || c.is_ascii_digit() || INVALID_TAG_CHARACTERS.contains(c) { + return err(value); } } else { - return Err(format_err!("tags must have at least one character")); + return err(value); } - if !value.chars().skip(1).all(|c| c.is_ascii_alphanumeric()) { - return Err(format_err!( - "characters of a tag after the first must be alphanumeric" - )); + if !value + .chars() + .skip(1) + .all(|c| !(c.is_whitespace() || c == ':' || INVALID_TAG_CHARACTERS.contains(c))) + { + return err(value); } Ok(Self(String::from(value))) } @@ -383,23 +396,23 @@ mod test { let tag: Tag = "abc".try_into().unwrap(); assert_eq!(tag, Tag("abc".to_owned())); + let tag: Tag = ":abc".try_into().unwrap(); + assert_eq!(tag, Tag(":abc".to_owned())); + + let tag: Tag = "a123_456".try_into().unwrap(); + assert_eq!(tag, Tag("a123_456".to_owned())); + let tag: Result = "".try_into(); - assert_eq!( - tag.unwrap_err().to_string(), - "tags must have at least one character" - ); + assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"\""); + + let tag: Result = "a:b".try_into(); + assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"a:b\""); let tag: Result = "999".try_into(); - assert_eq!( - tag.unwrap_err().to_string(), - "first character of a tag must be alphabetic" - ); + assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"999\""); let tag: Result = "abc!!".try_into(); - assert_eq!( - tag.unwrap_err().to_string(), - "characters of a tag after the first must be alphanumeric" - ); + assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"abc!!\""); } #[test] From 824c14b5b855eaf679074ecb052eb500e4b910ca Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Jan 2021 03:51:46 +0000 Subject: [PATCH 170/548] Notes about active development of TW --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 135f89164..c0fbcfd13 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,15 @@ TaskChampion is an open-source personal task-tracking application. Use it to keep track of what you need to do, with a quick command-line interface and flexible sorting and filtering. It is modeled on [TaskWarrior](https://taskwarrior.org), but not a drop-in replacement for that application. +## Status + +TC is still under development. +You are welcome to [help out!](https://github.com/djmitche/taskchampion/blob/main/CONTRIBUTING.md). +Even if you just want to get some practice with Rust, your contribution is welcome. + +Since development of TaskChampion began, TaskWarrior developers have resumed work and made several releases. +Assuming that continues, it is unlikely that TaskChampion will ever be recommended for day-to-day use, as that would only serve to split the TaskWarrior community. + ## Goals * Feature parity with TaskWarrior (but not compatibility) From 15ffc62279f0352cb0aae408fcfb172686a0ccaa Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Jan 2021 21:11:55 -0500 Subject: [PATCH 171/548] rename taskstorage to storage --- cli/src/invocation/test.rs | 4 ++-- docs/src/storage.md | 2 +- taskchampion/src/lib.rs | 4 ++-- taskchampion/src/replica.rs | 6 +++--- .../src/{taskstorage => storage}/inmemory.rs | 10 ++++------ taskchampion/src/{taskstorage => storage}/kv.rs | 12 +++++------- taskchampion/src/{taskstorage => storage}/mod.rs | 12 ++++++------ .../src/{taskstorage => storage}/operation.rs | 2 +- taskchampion/src/task.rs | 2 +- taskchampion/src/taskdb.rs | 14 +++++++------- 10 files changed, 32 insertions(+), 36 deletions(-) rename taskchampion/src/{taskstorage => storage}/inmemory.rs (95%) rename taskchampion/src/{taskstorage => storage}/kv.rs (98%) rename taskchampion/src/{taskstorage => storage}/mod.rs (92%) rename taskchampion/src/{taskstorage => storage}/operation.rs (99%) diff --git a/cli/src/invocation/test.rs b/cli/src/invocation/test.rs index 8f32723b0..6a2cfdaf3 100644 --- a/cli/src/invocation/test.rs +++ b/cli/src/invocation/test.rs @@ -1,9 +1,9 @@ use std::io; -use taskchampion::{server, taskstorage, Replica, ServerConfig}; +use taskchampion::{server, storage, Replica, ServerConfig}; use tempdir::TempDir; pub(super) fn test_replica() -> Replica { - let storage = taskstorage::InMemoryStorage::new(); + let storage = storage::InMemoryStorage::new(); Replica::new(Box::new(storage)) } diff --git a/docs/src/storage.md b/docs/src/storage.md index 977aace74..c1c34f7e1 100644 --- a/docs/src/storage.md +++ b/docs/src/storage.md @@ -1,7 +1,7 @@ # Replica Storage Each replica has a storage backend. -The interface for this backend is given in `crate::taskstorage::TaskStorage` and `TaskStorageTxn`. +The interface for this backend is given in `crate::taskstorage::Storage` and `StorageTxn`. The storage is transaction-protected, with the expectation of a serializable isolation level. The storage contains the following information: diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index cdb9e8cc1..dabb9ee1a 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -9,7 +9,7 @@ synchronize with one another. # Task Storage -The [`taskstorage`](crate::taskstorage) module supports pluggable storage for a replica's data. +The [`storage`](crate::storage) module supports pluggable storage for a replica's data. An implementation is provided, but users of this crate can provide their own implementation as well. # Server @@ -30,7 +30,7 @@ mod replica; pub mod server; mod task; mod taskdb; -pub mod taskstorage; +pub mod storage; mod utils; mod workingset; diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 95cbaf576..39faa205a 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -3,7 +3,7 @@ use crate::errors::Error; use crate::server::Server; use crate::task::{Status, Task}; use crate::taskdb::TaskDB; -use crate::taskstorage::{KVStorage, Operation, TaskMap, TaskStorage}; +use crate::storage::{KVStorage, Operation, TaskMap, Storage}; use crate::workingset::WorkingSet; use chrono::Utc; use failure::Fallible; @@ -30,7 +30,7 @@ pub struct Replica { } impl Replica { - pub fn new(storage: Box) -> Replica { + pub fn new(storage: Box) -> Replica { Replica { taskdb: TaskDB::new(storage), } @@ -45,7 +45,7 @@ impl Replica { #[cfg(test)] pub fn new_inmemory() -> Replica { - Replica::new(Box::new(crate::taskstorage::InMemoryStorage::new())) + Replica::new(Box::new(crate::storage::InMemoryStorage::new())) } /// Update an existing task. If the value is Some, the property is added or updated. If the diff --git a/taskchampion/src/taskstorage/inmemory.rs b/taskchampion/src/storage/inmemory.rs similarity index 95% rename from taskchampion/src/taskstorage/inmemory.rs rename to taskchampion/src/storage/inmemory.rs index 718ff1b1e..e05f60258 100644 --- a/taskchampion/src/taskstorage/inmemory.rs +++ b/taskchampion/src/storage/inmemory.rs @@ -1,8 +1,6 @@ #![allow(clippy::new_without_default)] -use crate::taskstorage::{ - Operation, TaskMap, TaskStorage, TaskStorageTxn, VersionId, DEFAULT_BASE_VERSION, -}; +use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use failure::{bail, Fallible}; use std::collections::hash_map::Entry; use std::collections::HashMap; @@ -42,7 +40,7 @@ impl<'t> Txn<'t> { } } -impl<'t> TaskStorageTxn for Txn<'t> { +impl<'t> StorageTxn for Txn<'t> { fn get_task(&mut self, uuid: Uuid) -> Fallible> { match self.data_ref().tasks.get(&uuid) { None => Ok(None), @@ -157,8 +155,8 @@ impl InMemoryStorage { } } -impl TaskStorage for InMemoryStorage { - fn txn<'a>(&'a mut self) -> Fallible> { +impl Storage for InMemoryStorage { + fn txn<'a>(&'a mut self) -> Fallible> { Ok(Box::new(Txn { storage: self, new_data: None, diff --git a/taskchampion/src/taskstorage/kv.rs b/taskchampion/src/storage/kv.rs similarity index 98% rename from taskchampion/src/taskstorage/kv.rs rename to taskchampion/src/storage/kv.rs index 4bbd087be..5a5e06242 100644 --- a/taskchampion/src/taskstorage/kv.rs +++ b/taskchampion/src/storage/kv.rs @@ -1,6 +1,4 @@ -use crate::taskstorage::{ - Operation, TaskMap, TaskStorage, TaskStorageTxn, VersionId, DEFAULT_BASE_VERSION, -}; +use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use crate::utils::Key; use failure::{bail, Fallible}; use kv::msgpack::Msgpack; @@ -62,8 +60,8 @@ impl<'t> KVStorage<'t> { } } -impl<'t> TaskStorage for KVStorage<'t> { - fn txn<'a>(&'a mut self) -> Fallible> { +impl<'t> Storage for KVStorage<'t> { + fn txn<'a>(&'a mut self) -> Fallible> { Ok(Box::new(Txn { storage: self, txn: Some(self.store.write_txn()?), @@ -104,7 +102,7 @@ impl<'t> Txn<'t> { } } -impl<'t> TaskStorageTxn for Txn<'t> { +impl<'t> StorageTxn for Txn<'t> { fn get_task(&mut self, uuid: Uuid) -> Fallible> { let bucket = self.tasks_bucket(); let buf = match self.kvtxn().get(bucket, uuid.into()) { @@ -356,7 +354,7 @@ impl<'t> TaskStorageTxn for Txn<'t> { #[cfg(test)] mod test { use super::*; - use crate::taskstorage::taskmap_with; + use crate::storage::taskmap_with; use failure::Fallible; use tempdir::TempDir; diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/storage/mod.rs similarity index 92% rename from taskchampion/src/taskstorage/mod.rs rename to taskchampion/src/storage/mod.rs index 571c0d8b5..3184831b4 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -29,7 +29,7 @@ pub use crate::server::VersionId; /// The default for base_version. pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NO_VERSION_ID; -/// A TaskStorage transaction, in which storage operations are performed. +/// A Storage transaction, in which storage operations are performed. /// /// # Concurrency /// @@ -40,9 +40,9 @@ pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NO_VERSION_ID; /// # Commiting and Aborting /// /// A transaction is not visible to other readers until it is committed with -/// [`crate::taskstorage::TaskStorageTxn::commit`]. Transactions are aborted if they are dropped. +/// [`crate::storage::StorageTxn::commit`]. Transactions are aborted if they are dropped. /// It is safe and performant to drop transactions that did not modify any data without committing. -pub trait TaskStorageTxn { +pub trait StorageTxn { /// Get an (immutable) task, if it is in the storage fn get_task(&mut self, uuid: Uuid) -> Fallible>; @@ -102,8 +102,8 @@ pub trait TaskStorageTxn { } /// A trait for objects able to act as task storage. Most of the interesting behavior is in the -/// [`crate::taskstorage::TaskStorageTxn`] trait. -pub trait TaskStorage { +/// [`crate::storage::StorageTxn`] trait. +pub trait Storage { /// Begin a transaction - fn txn<'a>(&'a mut self) -> Fallible>; + fn txn<'a>(&'a mut self) -> Fallible>; } diff --git a/taskchampion/src/taskstorage/operation.rs b/taskchampion/src/storage/operation.rs similarity index 99% rename from taskchampion/src/taskstorage/operation.rs rename to taskchampion/src/storage/operation.rs index 665a151ef..fd88e17f9 100644 --- a/taskchampion/src/taskstorage/operation.rs +++ b/taskchampion/src/storage/operation.rs @@ -125,8 +125,8 @@ impl Operation { #[cfg(test)] mod test { use super::*; + use crate::storage::InMemoryStorage; use crate::taskdb::TaskDB; - use crate::taskstorage::InMemoryStorage; use chrono::{Duration, Utc}; use proptest::prelude::*; diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 1434f452a..fcf47f005 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -1,5 +1,5 @@ use crate::replica::Replica; -use crate::taskstorage::TaskMap; +use crate::storage::TaskMap; use chrono::prelude::*; use failure::{format_err, Fallible}; use log::trace; diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index c04a26c36..cdf924ade 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -1,6 +1,6 @@ use crate::errors::Error; use crate::server::{AddVersionResult, GetVersionResult, Server}; -use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn}; +use crate::storage::{Operation, Storage, StorageTxn, TaskMap}; use failure::{format_err, Fallible}; use log::{info, trace, warn}; use serde::{Deserialize, Serialize}; @@ -12,7 +12,7 @@ use uuid::Uuid; /// and so on, and all the invariants that come with it. It leaves the meaning of particular task /// properties to the replica and task implementations. pub struct TaskDB { - storage: Box, + storage: Box, } #[derive(Serialize, Deserialize, Debug)] @@ -22,13 +22,13 @@ struct Version { impl TaskDB { /// Create a new TaskDB with the given backend storage - pub fn new(storage: Box) -> TaskDB { + pub fn new(storage: Box) -> TaskDB { TaskDB { storage } } #[cfg(test)] pub fn new_inmemory() -> TaskDB { - TaskDB::new(Box::new(crate::taskstorage::InMemoryStorage::new())) + TaskDB::new(Box::new(crate::storage::InMemoryStorage::new())) } /// Apply an operation to the TaskDB. Aside from synchronization operations, this is the only way @@ -45,7 +45,7 @@ impl TaskDB { Ok(()) } - fn apply_op(txn: &mut dyn TaskStorageTxn, op: &Operation) -> Fallible<()> { + fn apply_op(txn: &mut dyn StorageTxn, op: &Operation) -> Fallible<()> { match op { Operation::Create { uuid } => { // insert if the task does not already exist @@ -261,7 +261,7 @@ impl TaskDB { Ok(()) } - fn apply_version(txn: &mut dyn TaskStorageTxn, mut version: Version) -> Fallible<()> { + fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> Fallible<()> { // The situation here is that the server has already applied all server operations, and we // have already applied all local operations, so states have diverged by several // operations. We need to figure out what operations to apply locally and on the server in @@ -358,7 +358,7 @@ impl TaskDB { mod tests { use super::*; use crate::server::test::TestServer; - use crate::taskstorage::InMemoryStorage; + use crate::storage::InMemoryStorage; use chrono::Utc; use proptest::prelude::*; use std::collections::HashMap; From b004b6cb9367f13ad352098bbf8e90ebb24b71c5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Jan 2021 21:35:24 -0500 Subject: [PATCH 172/548] use ServerConfig::into_server instead of server::from_config --- cli/src/invocation/mod.rs | 15 ++++++------ cli/src/invocation/test.rs | 9 +++---- taskchampion/src/config.rs | 23 ------------------ taskchampion/src/lib.rs | 12 ++++++++-- taskchampion/src/server/config.rs | 40 +++++++++++++++++++++++++++++++ taskchampion/src/server/mod.rs | 17 ++----------- 6 files changed, 65 insertions(+), 51 deletions(-) create mode 100644 taskchampion/src/server/config.rs diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 16c9b0374..b8a44759e 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -3,7 +3,7 @@ use crate::argparse::{Command, Subcommand}; use config::Config; use failure::{format_err, Fallible}; -use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Uuid}; +use taskchampion::{Replica, ReplicaConfig, Server, ServerConfig, Uuid}; use termcolor::{ColorChoice, StandardStream}; mod cmd; @@ -109,10 +109,10 @@ fn get_replica(settings: &Config) -> Fallible { } /// Get the server for this invocation -fn get_server(settings: &Config) -> Fallible> { +fn get_server(settings: &Config) -> Fallible> { // if server_client_key and server_origin are both set, use // the remote server - if let (Ok(client_key), Ok(origin)) = ( + let config = if let (Ok(client_key), Ok(origin)) = ( settings.get_str("server_client_key"), settings.get_str("server_origin"), ) { @@ -123,16 +123,17 @@ fn get_server(settings: &Config) -> Fallible> { log::debug!("Using sync-server with origin {}", origin); log::debug!("Sync client ID: {}", client_key); - Ok(server::from_config(ServerConfig::Remote { + ServerConfig::Remote { origin, client_key, encryption_secret: encryption_secret.as_bytes().to_vec(), - })?) + } } else { let server_dir = settings.get_str("server_dir")?.into(); log::debug!("Using local sync-server at `{:?}`", server_dir); - Ok(server::from_config(ServerConfig::Local { server_dir })?) - } + ServerConfig::Local { server_dir } + }; + Ok(config.into_server()?) } /// Get a WriteColor implementation based on whether the output is a tty. diff --git a/cli/src/invocation/test.rs b/cli/src/invocation/test.rs index 6a2cfdaf3..5dcac753c 100644 --- a/cli/src/invocation/test.rs +++ b/cli/src/invocation/test.rs @@ -1,5 +1,5 @@ use std::io; -use taskchampion::{server, storage, Replica, ServerConfig}; +use taskchampion::{storage, Replica, Server, ServerConfig}; use tempdir::TempDir; pub(super) fn test_replica() -> Replica { @@ -7,10 +7,11 @@ pub(super) fn test_replica() -> Replica { Replica::new(Box::new(storage)) } -pub(super) fn test_server(dir: &TempDir) -> Box { - server::from_config(ServerConfig::Local { +pub(super) fn test_server(dir: &TempDir) -> Box { + ServerConfig::Local { server_dir: dir.path().to_path_buf(), - }) + } + .into_server() .unwrap() } diff --git a/taskchampion/src/config.rs b/taskchampion/src/config.rs index 63fef6527..d8ebe81fb 100644 --- a/taskchampion/src/config.rs +++ b/taskchampion/src/config.rs @@ -1,30 +1,7 @@ use std::path::PathBuf; -use uuid::Uuid; /// The configuration required for a replica. Use with [`crate::Replica::from_config`]. pub struct ReplicaConfig { /// Path containing the task DB. pub taskdb_dir: PathBuf, } - -/// The configuration for a replica's access to a sync server. Use with -/// [`crate::server::from_config`]. -pub enum ServerConfig { - /// A local task database, for situations with a single replica. - Local { - /// Path containing the server's DB - server_dir: PathBuf, - }, - /// A remote taskchampion-sync-server instance - Remote { - /// Sync server "origin"; a URL with schema and hostname but no path or trailing `/` - origin: String, - - /// Client Key to identify and authenticate this replica to the server - client_key: Uuid, - - /// Private encryption secret used to encrypt all data sent to the server. This can - /// be any suitably un-guessable string of bytes. - encryption_secret: Vec, - }, -} diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index dabb9ee1a..f7c896dff 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -3,10 +3,14 @@ This crate implements the core of TaskChampion, the [replica](crate::Replica). +# Replica + A TaskChampion replica is a local copy of a user's task data. As the name suggests, several replicas of the same data can exist (such as on a user's laptop and on their phone) and can synchronize with one another. +Replicas are accessed using the [`Replica`](crate::replica) type. + # Task Storage The [`storage`](crate::storage) module supports pluggable storage for a replica's data. @@ -15,7 +19,10 @@ An implementation is provided, but users of this crate can provide their own imp # Server Replica synchronization takes place against a server. +Create a server with [`ServerConfig`](crate::ServerConfig). + The [`server`](crate::server) module defines the interface a server must meet. +Users can define their own server impelementations. # See Also @@ -28,14 +35,15 @@ mod config; mod errors; mod replica; pub mod server; +pub mod storage; mod task; mod taskdb; -pub mod storage; mod utils; mod workingset; -pub use config::{ReplicaConfig, ServerConfig}; +pub use config::ReplicaConfig; pub use replica::Replica; +pub use server::{Server, ServerConfig}; pub use task::{Priority, Status, Tag, Task, TaskMut}; pub use workingset::WorkingSet; diff --git a/taskchampion/src/server/config.rs b/taskchampion/src/server/config.rs new file mode 100644 index 000000000..3de8243b4 --- /dev/null +++ b/taskchampion/src/server/config.rs @@ -0,0 +1,40 @@ +use super::types::Server; +use super::{LocalServer, RemoteServer}; +use failure::Fallible; +use std::path::PathBuf; +use uuid::Uuid; + +/// The configuration for a replica's access to a sync server. +pub enum ServerConfig { + /// A local task database, for situations with a single replica. + Local { + /// Path containing the server's DB + server_dir: PathBuf, + }, + /// A remote taskchampion-sync-server instance + Remote { + /// Sync server "origin"; a URL with schema and hostname but no path or trailing `/` + origin: String, + + /// Client Key to identify and authenticate this replica to the server + client_key: Uuid, + + /// Private encryption secret used to encrypt all data sent to the server. This can + /// be any suitably un-guessable string of bytes. + encryption_secret: Vec, + }, +} + +impl ServerConfig { + /// Get a server based on this configuration + pub fn into_server(self) -> Fallible> { + Ok(match self { + ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), + ServerConfig::Remote { + origin, + client_key, + encryption_secret, + } => Box::new(RemoteServer::new(origin, client_key, encryption_secret)), + }) + } +} diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 40a8d4d2c..21411241c 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -1,25 +1,12 @@ -use crate::ServerConfig; -use failure::Fallible; - #[cfg(test)] pub(crate) mod test; +mod config; mod local; mod remote; mod types; +pub use config::ServerConfig; pub use local::LocalServer; pub use remote::RemoteServer; pub use types::*; - -/// Create a new server based on the given configuration. -pub fn from_config(config: ServerConfig) -> Fallible> { - Ok(match config { - ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), - ServerConfig::Remote { - origin, - client_key, - encryption_secret, - } => Box::new(RemoteServer::new(origin, client_key, encryption_secret)), - }) -} From 02d9c577ab233964c0a578bef77c0a9b85424064 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Jan 2021 21:48:28 -0500 Subject: [PATCH 173/548] use StorageConfig instead of ReplicaConfig --- cli/src/invocation/mod.rs | 6 +++--- taskchampion/src/config.rs | 7 ------- taskchampion/src/lib.rs | 3 +-- taskchampion/src/replica.rs | 10 +--------- taskchampion/src/storage/config.rs | 23 +++++++++++++++++++++++ taskchampion/src/storage/mod.rs | 2 ++ 6 files changed, 30 insertions(+), 21 deletions(-) delete mode 100644 taskchampion/src/config.rs create mode 100644 taskchampion/src/storage/config.rs diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index b8a44759e..32e66a940 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -3,7 +3,7 @@ use crate::argparse::{Command, Subcommand}; use config::Config; use failure::{format_err, Fallible}; -use taskchampion::{Replica, ReplicaConfig, Server, ServerConfig, Uuid}; +use taskchampion::{Replica, Server, ServerConfig, StorageConfig, Uuid}; use termcolor::{ColorChoice, StandardStream}; mod cmd; @@ -104,8 +104,8 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { fn get_replica(settings: &Config) -> Fallible { let taskdb_dir = settings.get_str("data_dir")?.into(); log::debug!("Replica data_dir: {:?}", taskdb_dir); - let replica_config = ReplicaConfig { taskdb_dir }; - Ok(Replica::from_config(replica_config)?) + let storage_config = StorageConfig::OnDisk { taskdb_dir }; + Ok(Replica::new(storage_config.into_storage()?)) } /// Get the server for this invocation diff --git a/taskchampion/src/config.rs b/taskchampion/src/config.rs deleted file mode 100644 index d8ebe81fb..000000000 --- a/taskchampion/src/config.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::path::PathBuf; - -/// The configuration required for a replica. Use with [`crate::Replica::from_config`]. -pub struct ReplicaConfig { - /// Path containing the task DB. - pub taskdb_dir: PathBuf, -} diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index f7c896dff..9a1cd4635 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -31,7 +31,6 @@ for more information about the design and usage of the tool. */ -mod config; mod errors; mod replica; pub mod server; @@ -41,9 +40,9 @@ mod taskdb; mod utils; mod workingset; -pub use config::ReplicaConfig; pub use replica::Replica; pub use server::{Server, ServerConfig}; +pub use storage::StorageConfig; pub use task::{Priority, Status, Tag, Task, TaskMut}; pub use workingset::WorkingSet; diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 39faa205a..f13bc19c2 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -1,9 +1,8 @@ -use crate::config::ReplicaConfig; use crate::errors::Error; use crate::server::Server; +use crate::storage::{Operation, Storage, TaskMap}; use crate::task::{Status, Task}; use crate::taskdb::TaskDB; -use crate::storage::{KVStorage, Operation, TaskMap, Storage}; use crate::workingset::WorkingSet; use chrono::Utc; use failure::Fallible; @@ -36,13 +35,6 @@ impl Replica { } } - /// Construct a new replica from a configuration object. This is the common way - /// to create a new object. - pub fn from_config(config: ReplicaConfig) -> Fallible { - let storage = Box::new(KVStorage::new(config.taskdb_dir)?); - Ok(Replica::new(storage)) - } - #[cfg(test)] pub fn new_inmemory() -> Replica { Replica::new(Box::new(crate::storage::InMemoryStorage::new())) diff --git a/taskchampion/src/storage/config.rs b/taskchampion/src/storage/config.rs new file mode 100644 index 000000000..5ef964575 --- /dev/null +++ b/taskchampion/src/storage/config.rs @@ -0,0 +1,23 @@ +use super::{InMemoryStorage, KVStorage, Storage}; +use failure::Fallible; +use std::path::PathBuf; + +/// The configuration required for a replica's storage. +pub enum StorageConfig { + /// Store the data on disk. This is the common choice. + OnDisk { + /// Path containing the task DB. + taskdb_dir: PathBuf, + }, + /// Store the data in memory. This is only useful for testing. + InMemory, +} + +impl StorageConfig { + pub fn into_storage(self) -> Fallible> { + Ok(match self { + StorageConfig::OnDisk { taskdb_dir } => Box::new(KVStorage::new(taskdb_dir)?), + StorageConfig::InMemory => Box::new(InMemoryStorage::new()), + }) + } +} diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index 3184831b4..0932afd3f 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -2,11 +2,13 @@ use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; +mod config; mod inmemory; mod kv; mod operation; pub use self::kv::KVStorage; +pub use config::StorageConfig; pub use inmemory::InMemoryStorage; pub use operation::Operation; From ae4cee1ac34a0cb3fb0335d115a120ad452c8bb1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Jan 2021 22:02:42 -0500 Subject: [PATCH 174/548] add docs for the 'server' and 'storage' modules --- taskchampion/src/server/mod.rs | 10 ++++++++++ taskchampion/src/storage/mod.rs | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 21411241c..68b5a713f 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -1,3 +1,13 @@ +/** + +This module defines the client interface to TaskChampion sync servers. +It defines a [trait](crate::server::Server) for servers, and implements both local and remote servers. + +Typical uses of this crate do not interact directly with this module; [`ServerConfig`](crate::ServerConfig) is sufficient. +However, users who wish to implement their own server interfaces can implement the traits defined here and pass the result to [`Replica`](crate::Replica). + +*/ + #[cfg(test)] pub(crate) mod test; diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index 0932afd3f..fec354ed2 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -1,3 +1,12 @@ +/** + +This module defines the backend storage used by [`Replica`](crate::Replica). +It defines a [trait](crate::storage::Storage) for storage implementations, and provides a default on-disk implementation as well as an in-memory implementation for testing. + +Typical uses of this crate do not interact directly with this module; [`StorageConfig`](crate::StorageConfig) is sufficient. +However, users who wish to implement their own storage backends can implement the traits defined here and pass the result to [`Replica`](crate::Replica). + +*/ use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; From 3cccdc7e3253901ef4f13112d5bcf847d070a08f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Jan 2021 22:45:12 -0500 Subject: [PATCH 175/548] v0.3.0 --- Cargo.lock | 6 +++--- cli/Cargo.toml | 2 +- sync-server/Cargo.toml | 2 +- taskchampion/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7087bbabf..154c73a61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2250,7 +2250,7 @@ checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e" [[package]] name = "taskchampion" -version = "0.2.0" +version = "0.3.0" dependencies = [ "chrono", "failure", @@ -2268,7 +2268,7 @@ dependencies = [ [[package]] name = "taskchampion-cli" -version = "0.2.0" +version = "0.3.0" dependencies = [ "assert_cmd", "atty", @@ -2288,7 +2288,7 @@ dependencies = [ [[package]] name = "taskchampion-sync-server" -version = "0.2.0" +version = "0.3.0" dependencies = [ "actix-rt", "actix-web", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 49a7e061e..d2aed0b9d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Dustin J. Mitchell "] edition = "2018" name = "taskchampion-cli" -version = "0.2.0" +version = "0.3.0" [dependencies] dirs = "^3.0.1" diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index bebab6a02..d1662b230 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskchampion-sync-server" -version = "0.2.0" +version = "0.3.0" authors = ["Dustin J. Mitchell "] edition = "2018" diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 1781183d4..e04610ceb 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskchampion" -version = "0.2.0" +version = "0.3.0" authors = ["Dustin J. Mitchell "] description = "Personal task-tracking" homepage = "https://djmitche.github.io/taskchampion/" From 4d9755c43b0554f92153fa971a248436741bd0ca Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 25 Mar 2021 16:33:35 +1100 Subject: [PATCH 176/548] Replace 'failure' crate with anyhow+thiserror Closes #148 --- Cargo.lock | 47 ++++------------ cli/Cargo.toml | 2 +- cli/src/argparse/command.rs | 12 ++--- cli/src/argparse/filter.rs | 4 +- cli/src/invocation/cmd/add.rs | 3 +- cli/src/invocation/cmd/gc.rs | 3 +- cli/src/invocation/cmd/help.rs | 3 +- cli/src/invocation/cmd/info.rs | 3 +- cli/src/invocation/cmd/modify.rs | 3 +- cli/src/invocation/cmd/report.rs | 3 +- cli/src/invocation/cmd/sync.rs | 5 +- cli/src/invocation/cmd/version.rs | 3 +- cli/src/invocation/filter.rs | 5 +- cli/src/invocation/mod.rs | 11 ++-- cli/src/invocation/modify.rs | 3 +- cli/src/invocation/report.rs | 5 +- cli/src/lib.rs | 3 +- cli/src/report.rs | 56 +++++++++---------- cli/src/settings.rs | 5 +- sync-server/Cargo.toml | 2 +- sync-server/src/api/mod.rs | 2 +- sync-server/src/main.rs | 3 +- sync-server/src/server.rs | 17 +++--- sync-server/src/storage/inmemory.rs | 19 ++++--- sync-server/src/storage/kv.rs | 26 +++++---- sync-server/src/storage/mod.rs | 15 +++--- taskchampion/Cargo.toml | 3 +- taskchampion/src/errors.rs | 7 ++- taskchampion/src/replica.rs | 21 ++++---- taskchampion/src/server/config.rs | 3 +- taskchampion/src/server/local.rs | 24 ++++----- taskchampion/src/server/remote/crypto.rs | 11 ++-- taskchampion/src/server/remote/mod.rs | 15 +++--- taskchampion/src/server/test.rs | 5 +- taskchampion/src/server/types.rs | 5 +- taskchampion/src/storage/config.rs | 3 +- taskchampion/src/storage/inmemory.rs | 43 ++++++++------- taskchampion/src/storage/kv.rs | 68 ++++++++++++------------ taskchampion/src/storage/mod.rs | 36 ++++++------- taskchampion/src/task.rs | 33 ++++++------ taskchampion/src/taskdb.rs | 31 ++++++----- 41 files changed, 255 insertions(+), 316 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 154c73a61..ea49e2fd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,6 +294,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "anyhow" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767" + [[package]] name = "arrayref" version = "0.3.6" @@ -821,28 +827,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "flate2" version = "1.0.19" @@ -2230,18 +2214,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "synstructure" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - [[package]] name = "tap" version = "1.0.0" @@ -2252,8 +2224,8 @@ checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e" name = "taskchampion" version = "0.3.0" dependencies = [ + "anyhow", "chrono", - "failure", "kv", "lmdb-rkv", "log", @@ -2261,6 +2233,7 @@ dependencies = [ "serde", "serde_json", "tempdir", + "thiserror", "tindercrypt", "ureq", "uuid", @@ -2270,12 +2243,12 @@ dependencies = [ name = "taskchampion-cli" version = "0.3.0" dependencies = [ + "anyhow", "assert_cmd", "atty", "config", "dirs 3.0.1", "env_logger", - "failure", "log", "nom 6.0.1", "predicates", @@ -2292,9 +2265,9 @@ version = "0.3.0" dependencies = [ "actix-rt", "actix-web", + "anyhow", "clap", "env_logger", - "failure", "futures", "kv", "log", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d2aed0b9d..c68c40934 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -7,7 +7,7 @@ version = "0.3.0" [dependencies] dirs = "^3.0.1" env_logger = "^0.8.2" -failure = "^0.1.8" +anyhow = "1.0" log = "^0.4.11" nom = "^6.0.1" prettytable-rs = "^0.8.0" diff --git a/cli/src/argparse/command.rs b/cli/src/argparse/command.rs index fec430d2c..198a0abf5 100644 --- a/cli/src/argparse/command.rs +++ b/cli/src/argparse/command.rs @@ -1,6 +1,6 @@ use super::args::*; use super::{ArgList, Subcommand}; -use failure::{format_err, Fallible}; +use anyhow::bail; use nom::{combinator::*, sequence::*, Err, IResult}; /// A command is the overall command that the CLI should execute. @@ -29,16 +29,16 @@ impl Command { } /// Parse a command from the given list of strings. - pub fn from_argv(argv: &[&str]) -> Fallible { + pub fn from_argv(argv: &[&str]) -> anyhow::Result { match Command::parse(argv) { Ok((&[], cmd)) => Ok(cmd), - Ok((trailing, _)) => Err(format_err!( + Ok((trailing, _)) => bail!( "command line has trailing arguments: {:?}", trailing - )), + ), Err(Err::Incomplete(_)) => unreachable!(), - Err(Err::Error(e)) => Err(format_err!("command line not recognized: {:?}", e)), - Err(Err::Failure(e)) => Err(format_err!("command line not recognized: {:?}", e)), + Err(Err::Error(e)) => bail!("command line not recognized: {:?}", e), + Err(Err::Failure(e)) => bail!("command line not recognized: {:?}", e), } } } diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 812917845..49273999b 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -1,7 +1,7 @@ use super::args::{arg_matching, id_list, minus_tag, plus_tag, status_colon, TaskId}; use super::ArgList; use crate::usage; -use failure::{bail, Fallible}; +use anyhow::bail; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use taskchampion::Status; @@ -44,7 +44,7 @@ impl Condition { } /// Parse a single condition string - pub(crate) fn parse_str(input: &str) -> Fallible { + pub(crate) fn parse_str(input: &str) -> anyhow::Result { let input = &[input]; Ok(match Condition::parse(input) { Ok((&[], cond)) => cond, diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index 1175b587e..8a4456282 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -1,5 +1,4 @@ use crate::argparse::{DescriptionMod, Modification}; -use failure::Fallible; use taskchampion::{Replica, Status}; use termcolor::WriteColor; @@ -7,7 +6,7 @@ pub(crate) fn execute( w: &mut W, replica: &mut Replica, modification: Modification, -) -> Fallible<()> { +) -> anyhow::Result<()> { let description = match modification.description { DescriptionMod::Set(ref s) => s.clone(), _ => "(no description)".to_owned(), diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs index 75451808e..775b3096f 100644 --- a/cli/src/invocation/cmd/gc.rs +++ b/cli/src/invocation/cmd/gc.rs @@ -1,8 +1,7 @@ -use failure::Fallible; use taskchampion::Replica; use termcolor::WriteColor; -pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Fallible<()> { +pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> anyhow::Result<()> { log::debug!("rebuilding working set"); replica.rebuild_working_set(true)?; writeln!(w, "garbage collected.")?; diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index df7bb5919..878234933 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -1,12 +1,11 @@ use crate::usage::Usage; -use failure::Fallible; use termcolor::WriteColor; pub(crate) fn execute( w: &mut W, command_name: String, summary: bool, -) -> Fallible<()> { +) -> anyhow::Result<()> { let usage = Usage::new(); usage.write_help(w, command_name.as_ref(), summary)?; Ok(()) diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index 603093315..1f90b79f3 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -1,7 +1,6 @@ use crate::argparse::Filter; use crate::invocation::filtered_tasks; use crate::table; -use failure::Fallible; use prettytable::{cell, row, Table}; use taskchampion::Replica; use termcolor::WriteColor; @@ -11,7 +10,7 @@ pub(crate) fn execute( replica: &mut Replica, filter: Filter, debug: bool, -) -> Fallible<()> { +) -> anyhow::Result<()> { let working_set = replica.working_set()?; for task in filtered_tasks(replica, &filter)? { diff --git a/cli/src/invocation/cmd/modify.rs b/cli/src/invocation/cmd/modify.rs index d914da418..06b4ccc0e 100644 --- a/cli/src/invocation/cmd/modify.rs +++ b/cli/src/invocation/cmd/modify.rs @@ -1,6 +1,5 @@ use crate::argparse::{Filter, Modification}; use crate::invocation::{apply_modification, filtered_tasks}; -use failure::Fallible; use taskchampion::Replica; use termcolor::WriteColor; @@ -9,7 +8,7 @@ pub(crate) fn execute( replica: &mut Replica, filter: Filter, modification: Modification, -) -> Fallible<()> { +) -> anyhow::Result<()> { for task in filtered_tasks(replica, &filter)? { let mut task = task.into_mut(replica); diff --git a/cli/src/invocation/cmd/report.rs b/cli/src/invocation/cmd/report.rs index 9076c7b23..bcb258298 100644 --- a/cli/src/invocation/cmd/report.rs +++ b/cli/src/invocation/cmd/report.rs @@ -1,7 +1,6 @@ use crate::argparse::Filter; use crate::invocation::display_report; use config::Config; -use failure::Fallible; use taskchampion::Replica; use termcolor::WriteColor; @@ -11,7 +10,7 @@ pub(crate) fn execute( settings: &Config, report_name: String, filter: Filter, -) -> Fallible<()> { +) -> anyhow::Result<()> { display_report(w, replica, settings, report_name, filter) } diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index 9abc8b657..9acc8e581 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -1,4 +1,3 @@ -use failure::Fallible; use taskchampion::{server::Server, Replica}; use termcolor::WriteColor; @@ -6,8 +5,8 @@ pub(crate) fn execute( w: &mut W, replica: &mut Replica, server: &mut Box, -) -> Fallible<()> { - replica.sync(server)?; +) -> anyhow::Result<()> { + replica.sync(server).unwrap(); writeln!(w, "sync complete.")?; Ok(()) } diff --git a/cli/src/invocation/cmd/version.rs b/cli/src/invocation/cmd/version.rs index db3f2a80b..baef94161 100644 --- a/cli/src/invocation/cmd/version.rs +++ b/cli/src/invocation/cmd/version.rs @@ -1,7 +1,6 @@ -use failure::Fallible; use termcolor::{ColorSpec, WriteColor}; -pub(crate) fn execute(w: &mut W) -> Fallible<()> { +pub(crate) fn execute(w: &mut W) -> anyhow::Result<()> { write!(w, "TaskChampion ")?; w.set_color(ColorSpec::new().set_bold(true))?; writeln!(w, "{}", env!("CARGO_PKG_VERSION"))?; diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index 28e25c238..0cc6e31fe 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -1,5 +1,4 @@ use crate::argparse::{Condition, Filter, TaskId}; -use failure::Fallible; use std::collections::HashSet; use std::convert::TryInto; use taskchampion::{Replica, Status, Tag, Task, Uuid, WorkingSet}; @@ -108,7 +107,7 @@ fn universe_for_filter(filter: &Filter) -> Universe { pub(super) fn filtered_tasks( replica: &mut Replica, filter: &Filter, -) -> Fallible> { +) -> anyhow::Result> { let mut res = vec![]; log::debug!("Applying filter {:?}", filter); @@ -253,7 +252,7 @@ mod test { } #[test] - fn tag_filtering() -> Fallible<()> { + fn tag_filtering() -> anyhow::Result<()> { let mut replica = test_replica(); let yes: Tag = "yes".try_into()?; let no: Tag = "no".try_into()?; diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 32e66a940..fd910a8f0 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -2,7 +2,6 @@ use crate::argparse::{Command, Subcommand}; use config::Config; -use failure::{format_err, Fallible}; use taskchampion::{Replica, Server, ServerConfig, StorageConfig, Uuid}; use termcolor::{ColorChoice, StandardStream}; @@ -20,7 +19,7 @@ use report::display_report; /// Invoke the given Command in the context of the given settings #[allow(clippy::needless_return)] -pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { +pub(crate) fn invoke(command: Command, settings: Config) -> anyhow::Result<()> { log::debug!("command: {:?}", command); log::debug!("settings: {:?}", settings); @@ -101,7 +100,7 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> { // utilities for invoke /// Get the replica for this invocation -fn get_replica(settings: &Config) -> Fallible { +fn get_replica(settings: &Config) -> anyhow::Result { let taskdb_dir = settings.get_str("data_dir")?.into(); log::debug!("Replica data_dir: {:?}", taskdb_dir); let storage_config = StorageConfig::OnDisk { taskdb_dir }; @@ -109,7 +108,7 @@ fn get_replica(settings: &Config) -> Fallible { } /// Get the server for this invocation -fn get_server(settings: &Config) -> Fallible> { +fn get_server(settings: &Config) -> anyhow::Result> { // if server_client_key and server_origin are both set, use // the remote server let config = if let (Ok(client_key), Ok(origin)) = ( @@ -119,7 +118,7 @@ fn get_server(settings: &Config) -> Fallible> { let client_key = Uuid::parse_str(&client_key)?; let encryption_secret = settings .get_str("encryption_secret") - .map_err(|_| format_err!("Could not read `encryption_secret` configuration"))?; + .map_err(|_| anyhow::anyhow!("Could not read `encryption_secret` configuration"))?; log::debug!("Using sync-server with origin {}", origin); log::debug!("Sync client ID: {}", client_key); @@ -137,7 +136,7 @@ fn get_server(settings: &Config) -> Fallible> { } /// Get a WriteColor implementation based on whether the output is a tty. -fn get_writer() -> Fallible { +fn get_writer() -> anyhow::Result { Ok(StandardStream::stdout(if atty::is(atty::Stream::Stdout) { ColorChoice::Auto } else { diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index 74b9b8d00..b67620788 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,5 +1,4 @@ use crate::argparse::{DescriptionMod, Modification}; -use failure::Fallible; use std::convert::TryInto; use taskchampion::TaskMut; use termcolor::WriteColor; @@ -9,7 +8,7 @@ pub(super) fn apply_modification( w: &mut W, task: &mut TaskMut, modification: &Modification, -) -> Fallible<()> { +) -> anyhow::Result<()> { match modification.description { DescriptionMod::Set(ref description) => task.set_description(description.clone())?, DescriptionMod::Prepend(ref description) => { diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 185eaa4b3..0556d3b9d 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -3,7 +3,6 @@ use crate::invocation::filtered_tasks; use crate::report::{Column, Property, Report, SortBy}; use crate::table; use config::Config; -use failure::{format_err, Fallible}; use prettytable::{Row, Table}; use std::cmp::Ordering; use taskchampion::{Replica, Task, WorkingSet}; @@ -83,13 +82,13 @@ pub(super) fn display_report( settings: &Config, report_name: String, filter: Filter, -) -> Fallible<()> { +) -> anyhow::Result<()> { let mut t = Table::new(); let working_set = replica.working_set()?; // Get the report from settings let mut report = Report::from_config(settings.get(&format!("reports.{}", report_name))?) - .map_err(|e| format_err!("report.{}{}", report_name, e))?; + .map_err(|e| anyhow::anyhow!("report.{}{}", report_name, e))?; // include any user-supplied filter conditions report.filter = report.filter.intersect(filter); diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 38546f6cb..29b3f8afc 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -29,7 +29,6 @@ For the public TaskChampion Rust API, see the `taskchampion` crate. */ -use failure::Fallible; use std::os::unix::ffi::OsStringExt; use std::string::FromUtf8Error; @@ -45,7 +44,7 @@ mod usage; /// The main entry point for the command-line interface. This builds an Invocation /// from the particulars of the operating-system interface, and then executes it. -pub fn main() -> Fallible<()> { +pub fn main() -> anyhow::Result<()> { env_logger::init(); // parse the command line into a vector of &str, failing if diff --git a/cli/src/report.rs b/cli/src/report.rs index 12ba0af99..fb60246a0 100644 --- a/cli/src/report.rs +++ b/cli/src/report.rs @@ -1,7 +1,7 @@ //! This module contains the data structures used to define reports. use crate::argparse::{Condition, Filter}; -use failure::{bail, format_err, Fallible}; +use anyhow::{bail}; /// A report specifies a filter as well as a sort order and information about which /// task attributes to display @@ -77,43 +77,43 @@ impl Report { /// Create a Report from a config value. This should be the `report.` value. /// The error message begins with any additional path information, e.g., `.sort[1].sort_by: /// ..`. - pub(crate) fn from_config(cfg: config::Value) -> Fallible { - let mut map = cfg.into_table().map_err(|e| format_err!(": {}", e))?; + pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { + let mut map = cfg.into_table().map_err(|e| anyhow::anyhow!(": {}", e))?; let sort = if let Some(sort_array) = map.remove("sort") { sort_array .into_array() - .map_err(|e| format_err!(".sort: {}", e))? + .map_err(|e| anyhow::anyhow!(".sort: {}", e))? .drain(..) .enumerate() - .map(|(i, v)| Sort::from_config(v).map_err(|e| format_err!(".sort[{}]{}", i, e))) - .collect::>>()? + .map(|(i, v)| Sort::from_config(v).map_err(|e| anyhow::anyhow!(".sort[{}]{}", i, e))) + .collect::>>()? } else { vec![] }; let columns = map .remove("columns") - .ok_or_else(|| format_err!(": 'columns' property is required"))? + .ok_or_else(|| anyhow::anyhow!(": 'columns' property is required"))? .into_array() - .map_err(|e| format_err!(".columns: {}", e))? + .map_err(|e| anyhow::anyhow!(".columns: {}", e))? .drain(..) .enumerate() - .map(|(i, v)| Column::from_config(v).map_err(|e| format_err!(".columns[{}]{}", i, e))) - .collect::>>()?; + .map(|(i, v)| Column::from_config(v).map_err(|e| anyhow::anyhow!(".columns[{}]{}", i, e))) + .collect::>>()?; let conditions = if let Some(conditions) = map.remove("filter") { conditions .into_array() - .map_err(|e| format_err!(".filter: {}", e))? + .map_err(|e| anyhow::anyhow!(".filter: {}", e))? .drain(..) .enumerate() .map(|(i, v)| { v.into_str() .map_err(|e| e.into()) .and_then(|s| Condition::parse_str(&s)) - .map_err(|e| format_err!(".filter[{}]: {}", i, e)) + .map_err(|e| anyhow::anyhow!(".filter[{}]: {}", i, e)) }) - .collect::>>()? + .collect::>>()? } else { vec![] }; @@ -133,18 +133,18 @@ impl Report { } impl Column { - pub(crate) fn from_config(cfg: config::Value) -> Fallible { - let mut map = cfg.into_table().map_err(|e| format_err!(": {}", e))?; + pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { + let mut map = cfg.into_table().map_err(|e| anyhow::anyhow!(": {}", e))?; let label = map .remove("label") - .ok_or_else(|| format_err!(": 'label' property is required"))? + .ok_or_else(|| anyhow::anyhow!(": 'label' property is required"))? .into_str() - .map_err(|e| format_err!(".label: {}", e))?; + .map_err(|e| anyhow::anyhow!(".label: {}", e))?; let property: config::Value = map .remove("property") - .ok_or_else(|| format_err!(": 'property' property is required"))?; + .ok_or_else(|| anyhow::anyhow!(": 'property' property is required"))?; let property = - Property::from_config(property).map_err(|e| format_err!(".property{}", e))?; + Property::from_config(property).map_err(|e| anyhow::anyhow!(".property{}", e))?; if !map.is_empty() { bail!(": unknown properties"); @@ -155,8 +155,8 @@ impl Column { } impl Property { - pub(crate) fn from_config(cfg: config::Value) -> Fallible { - let s = cfg.into_str().map_err(|e| format_err!(": {}", e))?; + pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { + let s = cfg.into_str().map_err(|e| anyhow::anyhow!(": {}", e))?; Ok(match s.as_ref() { "id" => Property::Id, "uuid" => Property::Uuid, @@ -169,18 +169,18 @@ impl Property { } impl Sort { - pub(crate) fn from_config(cfg: config::Value) -> Fallible { - let mut map = cfg.into_table().map_err(|e| format_err!(": {}", e))?; + pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { + let mut map = cfg.into_table().map_err(|e| anyhow::anyhow!(": {}", e))?; let ascending = match map.remove("ascending") { Some(v) => v .into_bool() - .map_err(|e| format_err!(".ascending: {}", e))?, + .map_err(|e| anyhow::anyhow!(".ascending: {}", e))?, None => true, // default }; let sort_by: config::Value = map .remove("sort_by") - .ok_or_else(|| format_err!(": 'sort_by' property is required"))?; - let sort_by = SortBy::from_config(sort_by).map_err(|e| format_err!(".sort_by{}", e))?; + .ok_or_else(|| anyhow::anyhow!(": 'sort_by' property is required"))?; + let sort_by = SortBy::from_config(sort_by).map_err(|e| anyhow::anyhow!(".sort_by{}", e))?; if !map.is_empty() { bail!(": unknown properties"); @@ -191,8 +191,8 @@ impl Sort { } impl SortBy { - pub(crate) fn from_config(cfg: config::Value) -> Fallible { - let s = cfg.into_str().map_err(|e| format_err!(": {}", e))?; + pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { + let s = cfg.into_str().map_err(|e| anyhow::anyhow!(": {}", e))?; Ok(match s.as_ref() { "id" => SortBy::Id, "uuid" => SortBy::Uuid, diff --git a/cli/src/settings.rs b/cli/src/settings.rs index 88d972dc4..27c6a067f 100644 --- a/cli/src/settings.rs +++ b/cli/src/settings.rs @@ -1,5 +1,4 @@ use config::{Config, Environment, File, FileFormat, FileSourceFile, FileSourceString}; -use failure::Fallible; use std::env; use std::path::PathBuf; @@ -34,7 +33,7 @@ reports: "#; /// Get the default settings for this application -pub(crate) fn default_settings() -> Fallible { +pub(crate) fn default_settings() -> anyhow::Result { let mut settings = Config::default(); // set up defaults @@ -62,7 +61,7 @@ pub(crate) fn default_settings() -> Fallible { Ok(settings) } -pub(crate) fn read_settings() -> Fallible { +pub(crate) fn read_settings() -> anyhow::Result { let mut settings = default_settings()?; // load either from the path in TASKCHAMPION_CONFIG, or from CONFIG_DIR/taskchampion diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index d1662b230..233282612 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dependencies] uuid = { version = "^0.8.1", features = ["serde", "v4"] } actix-web = "^3.3.0" -failure = "^0.1.8" +anyhow = "1.0" futures = "^0.3.8" serde = "^1.0.104" kv = {version = "^0.10.0", features = ["msgpack-value"]} diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index 5929f28c1..26aa8eba0 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -29,7 +29,7 @@ pub(crate) fn api_scope() -> Scope { } /// Convert a failure::Error to an Actix ISE -fn failure_to_ise(err: failure::Error) -> impl actix_web::ResponseError { +fn failure_to_ise(err: anyhow::Error) -> impl actix_web::ResponseError { error::InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR) } diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 28481d3e7..296a18aa2 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -4,7 +4,6 @@ use crate::storage::{KVStorage, Storage}; use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder, Scope}; use api::{api_scope, ServerState}; use clap::Arg; -use failure::Fallible; mod api; mod server; @@ -27,7 +26,7 @@ pub(crate) fn app_scope(server_state: ServerState) -> Scope { } #[actix_web::main] -async fn main() -> Fallible<()> { +async fn main() -> anyhow::Result<()> { env_logger::init(); let matches = clap::App::new("taskchampion-sync-server") .version(env!("CARGO_PKG_VERSION")) diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 6c0e183cc..be67639be 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -1,7 +1,6 @@ //! This module implements the core logic of the server: handling transactions, upholding //! invariants, and so on. use crate::storage::{Client, StorageTxn}; -use failure::Fallible; use uuid::Uuid; /// The distinguished value for "no version" @@ -23,7 +22,7 @@ pub(crate) fn get_child_version<'a>( mut txn: Box, client_key: ClientKey, parent_version_id: VersionId, -) -> Fallible> { +) -> anyhow::Result> { Ok(txn .get_version_by_parent(client_key, parent_version_id)? .map(|version| GetVersionResult { @@ -48,7 +47,7 @@ pub(crate) fn add_version<'a>( client: Client, parent_version_id: VersionId, history_segment: HistorySegment, -) -> Fallible { +) -> anyhow::Result { log::debug!( "add_version(client_key: {}, parent_version_id: {})", client_key, @@ -84,7 +83,7 @@ mod test { use crate::storage::{InMemoryStorage, Storage}; #[test] - fn gcv_not_found() -> Fallible<()> { + fn gcv_not_found() -> anyhow::Result<()> { let storage = InMemoryStorage::new(); let txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -94,7 +93,7 @@ mod test { } #[test] - fn gcv_found() -> Fallible<()> { + fn gcv_found() -> anyhow::Result<()> { let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -121,7 +120,7 @@ mod test { } #[test] - fn av_conflict() -> Fallible<()> { + fn av_conflict() -> anyhow::Result<()> { let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -154,7 +153,7 @@ mod test { Ok(()) } - fn test_av_success(latest_version_id_nil: bool) -> Fallible<()> { + fn test_av_success(latest_version_id_nil: bool) -> anyhow::Result<()> { let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -198,12 +197,12 @@ mod test { } #[test] - fn av_success_with_existing_history() -> Fallible<()> { + fn av_success_with_existing_history() -> anyhow::Result<()> { test_av_success(true) } #[test] - fn av_success_nil_latest_version_id() -> Fallible<()> { + fn av_success_nil_latest_version_id() -> anyhow::Result<()> { test_av_success(false) } } diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs index fbd56db65..e37abb07a 100644 --- a/sync-server/src/storage/inmemory.rs +++ b/sync-server/src/storage/inmemory.rs @@ -1,5 +1,4 @@ use super::{Client, Storage, StorageTxn, Uuid, Version}; -use failure::{format_err, Fallible}; use std::collections::HashMap; use std::sync::{Mutex, MutexGuard}; @@ -28,19 +27,19 @@ struct InnerTxn<'a>(MutexGuard<'a, Inner>); /// /// NOTE: this does not implement transaction rollback. impl Storage for InMemoryStorage { - fn txn<'a>(&'a self) -> Fallible> { + fn txn<'a>(&'a self) -> anyhow::Result> { Ok(Box::new(InnerTxn(self.0.lock().expect("poisoned lock")))) } } impl<'a> StorageTxn for InnerTxn<'a> { - fn get_client(&mut self, client_key: Uuid) -> Fallible> { + fn get_client(&mut self, client_key: Uuid) -> anyhow::Result> { Ok(self.0.clients.get(&client_key).cloned()) } - fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> Fallible<()> { + fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { if self.0.clients.get(&client_key).is_some() { - return Err(format_err!("Client {} already exists", client_key)); + return Err(anyhow::anyhow!("Client {} already exists", client_key)); } self.0 .clients @@ -52,12 +51,12 @@ impl<'a> StorageTxn for InnerTxn<'a> { &mut self, client_key: Uuid, latest_version_id: Uuid, - ) -> Fallible<()> { + ) -> anyhow::Result<()> { if let Some(client) = self.0.clients.get_mut(&client_key) { client.latest_version_id = latest_version_id; Ok(()) } else { - Err(format_err!("Client {} does not exist", client_key)) + Err(anyhow::anyhow!("Client {} does not exist", client_key)) } } @@ -65,7 +64,7 @@ impl<'a> StorageTxn for InnerTxn<'a> { &mut self, client_key: Uuid, parent_version_id: Uuid, - ) -> Fallible> { + ) -> anyhow::Result> { Ok(self .0 .versions @@ -79,7 +78,7 @@ impl<'a> StorageTxn for InnerTxn<'a> { version_id: Uuid, parent_version_id: Uuid, history_segment: Vec, - ) -> Fallible<()> { + ) -> anyhow::Result<()> { // TODO: verify it doesn't exist (`.entry`?) let version = Version { version_id, @@ -92,7 +91,7 @@ impl<'a> StorageTxn for InnerTxn<'a> { Ok(()) } - fn commit(&mut self) -> Fallible<()> { + fn commit(&mut self) -> anyhow::Result<()> { Ok(()) } } diff --git a/sync-server/src/storage/kv.rs b/sync-server/src/storage/kv.rs index f3cc178b2..41f441844 100644 --- a/sync-server/src/storage/kv.rs +++ b/sync-server/src/storage/kv.rs @@ -1,5 +1,4 @@ use super::{Client, Storage, StorageTxn, Uuid, Version}; -use failure::Fallible; use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Serde, Store, ValueBuf}; use std::path::Path; @@ -29,7 +28,7 @@ pub(crate) struct KVStorage<'t> { } impl<'t> KVStorage<'t> { - pub fn new>(directory: P) -> Fallible> { + pub fn new>(directory: P) -> anyhow::Result> { let mut config = Config::default(directory); config.bucket("clients", None); config.bucket("versions", None); @@ -50,7 +49,7 @@ impl<'t> KVStorage<'t> { } impl<'t> Storage for KVStorage<'t> { - fn txn<'a>(&'a self) -> Fallible> { + fn txn<'a>(&'a self) -> anyhow::Result> { Ok(Box::new(Txn { storage: self, txn: Some(self.store.write_txn()?), @@ -82,7 +81,7 @@ impl<'t> Txn<'t> { } impl<'t> StorageTxn for Txn<'t> { - fn get_client(&mut self, client_key: Uuid) -> Fallible> { + fn get_client(&mut self, client_key: Uuid) -> anyhow::Result> { let key = client_db_key(client_key); let bucket = self.clients_bucket(); let kvtxn = self.kvtxn(); @@ -97,7 +96,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(Some(client)) } - fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> Fallible<()> { + fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { let key = client_db_key(client_key); let bucket = self.clients_bucket(); let kvtxn = self.kvtxn(); @@ -110,7 +109,7 @@ impl<'t> StorageTxn for Txn<'t> { &mut self, client_key: Uuid, latest_version_id: Uuid, - ) -> Fallible<()> { + ) -> anyhow::Result<()> { // implementation is the same as new_client.. self.new_client(client_key, latest_version_id) } @@ -119,7 +118,7 @@ impl<'t> StorageTxn for Txn<'t> { &mut self, client_key: Uuid, parent_version_id: Uuid, - ) -> Fallible> { + ) -> anyhow::Result> { let key = version_db_key(client_key, parent_version_id); let bucket = self.versions_bucket(); let kvtxn = self.kvtxn(); @@ -139,7 +138,7 @@ impl<'t> StorageTxn for Txn<'t> { version_id: Uuid, parent_version_id: Uuid, history_segment: Vec, - ) -> Fallible<()> { + ) -> anyhow::Result<()> { let key = version_db_key(client_key, parent_version_id); let bucket = self.versions_bucket(); let kvtxn = self.kvtxn(); @@ -152,7 +151,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(()) } - fn commit(&mut self) -> Fallible<()> { + fn commit(&mut self) -> anyhow::Result<()> { if let Some(kvtxn) = self.txn.take() { kvtxn.commit()?; } else { @@ -165,11 +164,10 @@ impl<'t> StorageTxn for Txn<'t> { #[cfg(test)] mod test { use super::*; - use failure::Fallible; use tempdir::TempDir; #[test] - fn test_get_client_empty() -> Fallible<()> { + fn test_get_client_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let storage = KVStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; @@ -179,7 +177,7 @@ mod test { } #[test] - fn test_client_storage() -> Fallible<()> { + fn test_client_storage() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let storage = KVStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; @@ -201,7 +199,7 @@ mod test { } #[test] - fn test_gvbp_empty() -> Fallible<()> { + fn test_gvbp_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let storage = KVStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; @@ -211,7 +209,7 @@ mod test { } #[test] - fn test_add_version_and_gvbp() -> Fallible<()> { + fn test_add_version_and_gvbp() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let storage = KVStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index c02d24fba..e0e13ae93 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -1,4 +1,3 @@ -use failure::Fallible; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -24,24 +23,24 @@ pub(crate) struct Version { pub(crate) trait StorageTxn { /// Get information about the given client - fn get_client(&mut self, client_key: Uuid) -> Fallible>; + fn get_client(&mut self, client_key: Uuid) -> anyhow::Result>; /// Create a new client with the given latest_version_id - fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> Fallible<()>; + fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()>; /// Set the client's latest_version_id fn set_client_latest_version_id( &mut self, client_key: Uuid, latest_version_id: Uuid, - ) -> Fallible<()>; + ) -> anyhow::Result<()>; /// Get a version, indexed by parent version id fn get_version_by_parent( &mut self, client_key: Uuid, parent_version_id: Uuid, - ) -> Fallible>; + ) -> anyhow::Result>; /// Add a version (that must not already exist) fn add_version( @@ -50,16 +49,16 @@ pub(crate) trait StorageTxn { version_id: Uuid, parent_version_id: Uuid, history_segment: Vec, - ) -> Fallible<()>; + ) -> anyhow::Result<()>; /// Commit any changes made in the transaction. It is an error to call this more than /// once. It is safe to skip this call for read-only operations. - fn commit(&mut self) -> Fallible<()>; + fn commit(&mut self) -> anyhow::Result<()>; } /// A trait for objects able to act as storage. Most of the interesting behavior is in the /// [`crate::storage::StorageTxn`] trait. pub(crate) trait Storage: Send + Sync { /// Begin a transaction - fn txn<'a>(&'a self) -> Fallible>; + fn txn<'a>(&'a self) -> anyhow::Result>; } diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index e04610ceb..aa21c40cc 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -15,7 +15,8 @@ uuid = { version = "^0.8.1", features = ["serde", "v4"] } serde = "^1.0.104" serde_json = "^1.0" chrono = { version = "^0.4.10", features = ["serde"] } -failure = {version = "^0.1.5", features = ["derive"] } +anyhow = "1.0" +thiserror = "1.0" kv = {version = "^0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "^0.12.3"} ureq = "^1.5.2" diff --git a/taskchampion/src/errors.rs b/taskchampion/src/errors.rs index 08884376f..801eb5926 100644 --- a/taskchampion/src/errors.rs +++ b/taskchampion/src/errors.rs @@ -1,7 +1,6 @@ -use failure::Fail; - -#[derive(Debug, Fail, Eq, PartialEq, Clone)] +use thiserror::Error; +#[derive(Debug, Error, Eq, PartialEq, Clone)] pub enum Error { - #[fail(display = "Task Database Error: {}", _0)] + #[error("Task Database Error: {}", _0)] DBError(String), } diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index f13bc19c2..f223e55aa 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -5,7 +5,6 @@ use crate::task::{Status, Task}; use crate::taskdb::TaskDB; use crate::workingset::WorkingSet; use chrono::Utc; -use failure::Fallible; use log::trace; use std::collections::HashMap; use uuid::Uuid; @@ -48,7 +47,7 @@ impl Replica { uuid: Uuid, property: S1, value: Option, - ) -> Fallible<()> + ) -> anyhow::Result<()> where S1: Into, S2: Into, @@ -62,12 +61,12 @@ impl Replica { } /// Add the given uuid to the working set, returning its index. - pub(crate) fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { + pub(crate) fn add_to_working_set(&mut self, uuid: Uuid) -> anyhow::Result { self.taskdb.add_to_working_set(uuid) } /// Get all tasks represented as a map keyed by UUID - pub fn all_tasks(&mut self) -> Fallible> { + pub fn all_tasks(&mut self) -> anyhow::Result> { let mut res = HashMap::new(); for (uuid, tm) in self.taskdb.all_tasks()?.drain(..) { res.insert(uuid, Task::new(uuid, tm)); @@ -76,18 +75,18 @@ impl Replica { } /// Get the UUIDs of all tasks - pub fn all_task_uuids(&mut self) -> Fallible> { + pub fn all_task_uuids(&mut self) -> anyhow::Result> { self.taskdb.all_task_uuids() } /// Get the "working set" for this replica. This is a snapshot of the current state, /// and it is up to the caller to decide how long to store this value. - pub fn working_set(&mut self) -> Fallible { + pub fn working_set(&mut self) -> anyhow::Result { Ok(WorkingSet::new(self.taskdb.working_set()?)) } /// Get an existing task by its UUID - pub fn get_task(&mut self, uuid: Uuid) -> Fallible> { + pub fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { Ok(self .taskdb .get_task(uuid)? @@ -95,7 +94,7 @@ impl Replica { } /// Create a new task. The task must not already exist. - pub fn new_task(&mut self, status: Status, description: String) -> Fallible { + pub fn new_task(&mut self, status: Status, description: String) -> anyhow::Result { let uuid = Uuid::new_v4(); self.taskdb.apply(Operation::Create { uuid })?; trace!("task {} created", uuid); @@ -109,7 +108,7 @@ impl Replica { /// Deleted; this is the final purge of the task. This is not a public method as deletion /// should only occur through expiration. #[allow(dead_code)] - fn delete_task(&mut self, uuid: Uuid) -> Fallible<()> { + fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result<()> { // check that it already exists; this is a convenience check, as the task may already exist // when this Create operation is finally sync'd with operations from other replicas if self.taskdb.get_task(uuid)?.is_none() { @@ -123,7 +122,7 @@ impl Replica { /// Synchronize this replica against the given server. The working set is rebuilt after /// this occurs, but without renumbering, so any newly-pending tasks should appear in /// the working set. - pub fn sync(&mut self, server: &mut Box) -> Fallible<()> { + pub fn sync(&mut self, server: &mut Box) -> anyhow::Result<()> { self.taskdb.sync(server)?; self.rebuild_working_set(false) } @@ -132,7 +131,7 @@ impl Replica { /// `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. - pub fn rebuild_working_set(&mut self, renumber: bool) -> Fallible<()> { + pub fn rebuild_working_set(&mut self, renumber: bool) -> anyhow::Result<()> { let pending = String::from(Status::Pending.to_taskmap()); self.taskdb .rebuild_working_set(|t| t.get("status") == Some(&pending), renumber)?; diff --git a/taskchampion/src/server/config.rs b/taskchampion/src/server/config.rs index 3de8243b4..efc444ed8 100644 --- a/taskchampion/src/server/config.rs +++ b/taskchampion/src/server/config.rs @@ -1,6 +1,5 @@ use super::types::Server; use super::{LocalServer, RemoteServer}; -use failure::Fallible; use std::path::PathBuf; use uuid::Uuid; @@ -27,7 +26,7 @@ pub enum ServerConfig { impl ServerConfig { /// Get a server based on this configuration - pub fn into_server(self) -> Fallible> { + pub fn into_server(self) -> anyhow::Result> { Ok(match self { ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), ServerConfig::Remote { diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 9cc4b2b28..f24ee96f6 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -2,7 +2,6 @@ use crate::server::{ AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, }; use crate::utils::Key; -use failure::Fallible; use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; use serde::{Deserialize, Serialize}; @@ -25,7 +24,7 @@ pub struct LocalServer<'t> { impl<'t> LocalServer<'t> { /// A test server has no notion of clients, signatures, encryption, etc. - pub fn new>(directory: P) -> Fallible> { + pub fn new>(directory: P) -> anyhow::Result> { let mut config = Config::default(directory); config.bucket("versions", None); config.bucket("numbers", None); @@ -48,7 +47,7 @@ impl<'t> LocalServer<'t> { }) } - fn get_latest_version_id(&mut self) -> Fallible { + fn get_latest_version_id(&mut self) -> anyhow::Result { let txn = self.store.read_txn()?; let base_version = match txn.get(&self.latest_version_bucket, 0.into()) { Ok(buf) => buf, @@ -60,7 +59,7 @@ impl<'t> LocalServer<'t> { Ok(base_version as VersionId) } - fn set_latest_version_id(&mut self, version_id: VersionId) -> Fallible<()> { + fn set_latest_version_id(&mut self, version_id: VersionId) -> anyhow::Result<()> { let mut txn = self.store.write_txn()?; txn.set( &self.latest_version_bucket, @@ -74,7 +73,7 @@ impl<'t> LocalServer<'t> { fn get_version_by_parent_version_id( &mut self, parent_version_id: VersionId, - ) -> Fallible> { + ) -> anyhow::Result> { let txn = self.store.read_txn()?; let version = match txn.get(&self.versions_bucket, parent_version_id.into()) { @@ -87,7 +86,7 @@ impl<'t> LocalServer<'t> { Ok(Some(version)) } - fn add_version_by_parent_version_id(&mut self, version: Version) -> Fallible<()> { + fn add_version_by_parent_version_id(&mut self, version: Version) -> anyhow::Result<()> { let mut txn = self.store.write_txn()?; txn.set( &self.versions_bucket, @@ -109,7 +108,7 @@ impl<'t> Server for LocalServer<'t> { &mut self, parent_version_id: VersionId, history_segment: HistorySegment, - ) -> Fallible { + ) -> anyhow::Result { // no client lookup // no signature validation @@ -133,7 +132,7 @@ impl<'t> Server for LocalServer<'t> { } /// Get a vector of all versions after `since_version` - fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible { + fn get_child_version(&mut self, parent_version_id: VersionId) -> anyhow::Result { if let Some(version) = self.get_version_by_parent_version_id(parent_version_id)? { Ok(GetVersionResult::Version { version_id: version.version_id, @@ -149,11 +148,10 @@ impl<'t> Server for LocalServer<'t> { #[cfg(test)] mod test { use super::*; - use failure::Fallible; use tempdir::TempDir; #[test] - fn test_empty() -> Fallible<()> { + fn test_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut server = LocalServer::new(&tmp_dir.path())?; let child_version = server.get_child_version(NO_VERSION_ID)?; @@ -162,7 +160,7 @@ mod test { } #[test] - fn test_add_zero_base() -> Fallible<()> { + fn test_add_zero_base() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut server = LocalServer::new(&tmp_dir.path())?; let history = b"1234".to_vec(); @@ -187,7 +185,7 @@ mod test { } #[test] - fn test_add_nonzero_base() -> Fallible<()> { + fn test_add_nonzero_base() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut server = LocalServer::new(&tmp_dir.path())?; let history = b"1234".to_vec(); @@ -215,7 +213,7 @@ mod test { } #[test] - fn test_add_nonzero_base_forbidden() -> Fallible<()> { + fn test_add_nonzero_base_forbidden() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut server = LocalServer::new(&tmp_dir.path())?; let history = b"1234".to_vec(); diff --git a/taskchampion/src/server/remote/crypto.rs b/taskchampion/src/server/remote/crypto.rs index 89dc91398..3751d606f 100644 --- a/taskchampion/src/server/remote/crypto.rs +++ b/taskchampion/src/server/remote/crypto.rs @@ -1,5 +1,4 @@ use crate::server::HistorySegment; -use failure::{format_err, Fallible}; use std::convert::TryFrom; use std::io::Read; use tindercrypt::cryptors::RingCryptor; @@ -27,7 +26,7 @@ pub(super) struct HistoryCleartext { impl HistoryCleartext { /// Seal the payload into its ciphertext - pub(super) fn seal(self, secret: &Secret) -> Fallible { + pub(super) fn seal(self, secret: &Secret) -> anyhow::Result { let cryptor = RingCryptor::new().with_aad(self.parent_version_id.as_bytes()); let ciphertext = cryptor.seal_with_passphrase(secret.as_ref(), &self.history_segment)?; Ok(HistoryCiphertext(ciphertext)) @@ -42,7 +41,7 @@ impl HistoryCiphertext { self, secret: &Secret, parent_version_id: Uuid, - ) -> Fallible { + ) -> anyhow::Result { let cryptor = RingCryptor::new().with_aad(parent_version_id.as_bytes()); let plaintext = cryptor.open(secret.as_ref(), &self.0)?; @@ -54,16 +53,16 @@ impl HistoryCiphertext { } impl TryFrom for HistoryCiphertext { - type Error = failure::Error; + type Error = anyhow::Error; - fn try_from(resp: ureq::Response) -> Result { + fn try_from(resp: ureq::Response) -> Result { if let Some("application/vnd.taskchampion.history-segment") = resp.header("Content-Type") { let mut reader = resp.into_reader(); let mut bytes = vec![]; reader.read_to_end(&mut bytes)?; Ok(Self(bytes)) } else { - Err(format_err!("Response did not have expected content-type")) + Err(anyhow::anyhow!("Response did not have expected content-type")) } } } diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index cd20618ec..ed88d4b15 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -1,5 +1,4 @@ use crate::server::{AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId}; -use failure::{format_err, Fallible}; use std::convert::TryInto; use uuid::Uuid; @@ -31,8 +30,8 @@ impl RemoteServer { } /// Convert a ureq::Response to an Error -fn resp_to_error(resp: ureq::Response) -> failure::Error { - return format_err!( +fn resp_to_error(resp: ureq::Response) -> anyhow::Error { + return anyhow::anyhow!( "error {}: {}", resp.status(), resp.into_string() @@ -41,12 +40,12 @@ fn resp_to_error(resp: ureq::Response) -> failure::Error { } /// Read a UUID-bearing header or fail trying -fn get_uuid_header(resp: &ureq::Response, name: &str) -> Fallible { +fn get_uuid_header(resp: &ureq::Response, name: &str) -> anyhow::Result { let value = resp .header(name) - .ok_or_else(|| format_err!("Response does not have {} header", name))?; + .ok_or_else(|| anyhow::anyhow!("Response does not have {} header", name))?; let value = Uuid::parse_str(value) - .map_err(|e| format_err!("{} header is not a valid UUID: {}", name, e))?; + .map_err(|e| anyhow::anyhow!("{} header is not a valid UUID: {}", name, e))?; Ok(value) } @@ -55,7 +54,7 @@ impl Server for RemoteServer { &mut self, parent_version_id: VersionId, history_segment: HistorySegment, - ) -> Fallible { + ) -> anyhow::Result { let url = format!("{}/client/add-version/{}", self.origin, parent_version_id); let history_cleartext = HistoryCleartext { parent_version_id, @@ -84,7 +83,7 @@ impl Server for RemoteServer { } } - fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible { + fn get_child_version(&mut self, parent_version_id: VersionId) -> anyhow::Result { let url = format!( "{}/client/get-child-version/{}", self.origin, parent_version_id diff --git a/taskchampion/src/server/test.rs b/taskchampion/src/server/test.rs index 3d57147ca..b8e439b47 100644 --- a/taskchampion/src/server/test.rs +++ b/taskchampion/src/server/test.rs @@ -1,7 +1,6 @@ use crate::server::{ AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, }; -use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; @@ -34,7 +33,7 @@ impl Server for TestServer { &mut self, parent_version_id: VersionId, history_segment: HistorySegment, - ) -> Fallible { + ) -> anyhow::Result { // no client lookup // no signature validation @@ -64,7 +63,7 @@ impl Server for TestServer { } /// Get a vector of all versions after `since_version` - fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible { + fn get_child_version(&mut self, parent_version_id: VersionId) -> anyhow::Result { if let Some(version) = self.versions.get(&parent_version_id) { Ok(GetVersionResult::Version { version_id: version.version_id, diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index b0a28a842..4478934d5 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -1,4 +1,3 @@ -use failure::Fallible; use uuid::Uuid; /// Versions are referred to with sha2 hashes. @@ -41,8 +40,8 @@ pub trait Server { &mut self, parent_version_id: VersionId, history_segment: HistorySegment, - ) -> Fallible; + ) -> anyhow::Result; /// Get the version with the given parent VersionId - fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible; + fn get_child_version(&mut self, parent_version_id: VersionId) -> anyhow::Result; } diff --git a/taskchampion/src/storage/config.rs b/taskchampion/src/storage/config.rs index 5ef964575..c87ae007d 100644 --- a/taskchampion/src/storage/config.rs +++ b/taskchampion/src/storage/config.rs @@ -1,5 +1,4 @@ use super::{InMemoryStorage, KVStorage, Storage}; -use failure::Fallible; use std::path::PathBuf; /// The configuration required for a replica's storage. @@ -14,7 +13,7 @@ pub enum StorageConfig { } impl StorageConfig { - pub fn into_storage(self) -> Fallible> { + pub fn into_storage(self) -> anyhow::Result> { Ok(match self { StorageConfig::OnDisk { taskdb_dir } => Box::new(KVStorage::new(taskdb_dir)?), StorageConfig::InMemory => Box::new(InMemoryStorage::new()), diff --git a/taskchampion/src/storage/inmemory.rs b/taskchampion/src/storage/inmemory.rs index e05f60258..e400d61bf 100644 --- a/taskchampion/src/storage/inmemory.rs +++ b/taskchampion/src/storage/inmemory.rs @@ -1,7 +1,6 @@ #![allow(clippy::new_without_default)] use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; -use failure::{bail, Fallible}; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; @@ -41,14 +40,14 @@ impl<'t> Txn<'t> { } impl<'t> StorageTxn for Txn<'t> { - fn get_task(&mut self, uuid: Uuid) -> Fallible> { + fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { match self.data_ref().tasks.get(&uuid) { None => Ok(None), Some(t) => Ok(Some(t.clone())), } } - fn create_task(&mut self, uuid: Uuid) -> Fallible { + fn create_task(&mut self, uuid: Uuid) -> anyhow::Result { if let ent @ Entry::Vacant(_) = self.mut_data_ref().tasks.entry(uuid) { ent.or_insert_with(TaskMap::new); Ok(true) @@ -57,16 +56,16 @@ impl<'t> StorageTxn for Txn<'t> { } } - fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible<()> { + fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> anyhow::Result<()> { self.mut_data_ref().tasks.insert(uuid, task); Ok(()) } - fn delete_task(&mut self, uuid: Uuid) -> Fallible { + fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result { Ok(self.mut_data_ref().tasks.remove(&uuid).is_some()) } - fn all_tasks<'a>(&mut self) -> Fallible> { + fn all_tasks<'a>(&mut self) -> anyhow::Result> { Ok(self .data_ref() .tasks @@ -75,58 +74,58 @@ impl<'t> StorageTxn for Txn<'t> { .collect()) } - fn all_task_uuids<'a>(&mut self) -> Fallible> { + fn all_task_uuids<'a>(&mut self) -> anyhow::Result> { Ok(self.data_ref().tasks.keys().copied().collect()) } - fn base_version(&mut self) -> Fallible { + fn base_version(&mut self) -> anyhow::Result { Ok(self.data_ref().base_version) } - fn set_base_version(&mut self, version: VersionId) -> Fallible<()> { + fn set_base_version(&mut self, version: VersionId) -> anyhow::Result<()> { self.mut_data_ref().base_version = version; Ok(()) } - fn operations(&mut self) -> Fallible> { + fn operations(&mut self) -> anyhow::Result> { Ok(self.data_ref().operations.clone()) } - fn add_operation(&mut self, op: Operation) -> Fallible<()> { + fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { self.mut_data_ref().operations.push(op); Ok(()) } - fn set_operations(&mut self, ops: Vec) -> Fallible<()> { + fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { self.mut_data_ref().operations = ops; Ok(()) } - fn get_working_set(&mut self) -> Fallible>> { + fn get_working_set(&mut self) -> anyhow::Result>> { Ok(self.data_ref().working_set.clone()) } - fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { + fn add_to_working_set(&mut self, uuid: Uuid) -> anyhow::Result { let working_set = &mut self.mut_data_ref().working_set; working_set.push(Some(uuid)); Ok(working_set.len()) } - fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Fallible<()> { + fn set_working_set_item(&mut self, index: usize, uuid: Option) -> anyhow::Result<()> { let working_set = &mut self.mut_data_ref().working_set; if index >= working_set.len() { - bail!("Index {} is not in the working set", index); + anyhow::bail!("Index {} is not in the working set", index); } working_set[index] = uuid; Ok(()) } - fn clear_working_set(&mut self) -> Fallible<()> { + fn clear_working_set(&mut self) -> anyhow::Result<()> { self.mut_data_ref().working_set = vec![None]; Ok(()) } - fn commit(&mut self) -> Fallible<()> { + fn commit(&mut self) -> anyhow::Result<()> { // copy the new_data back into storage to commit the transaction if let Some(data) = self.new_data.take() { self.storage.data = data; @@ -156,7 +155,7 @@ impl InMemoryStorage { } impl Storage for InMemoryStorage { - fn txn<'a>(&'a mut self) -> Fallible> { + fn txn<'a>(&'a mut self) -> anyhow::Result> { Ok(Box::new(Txn { storage: self, new_data: None, @@ -172,7 +171,7 @@ mod test { // elsewhere and not tested here) #[test] - fn get_working_set_empty() -> Fallible<()> { + fn get_working_set_empty() -> anyhow::Result<()> { let mut storage = InMemoryStorage::new(); { @@ -185,7 +184,7 @@ mod test { } #[test] - fn add_to_working_set() -> Fallible<()> { + fn add_to_working_set() -> anyhow::Result<()> { let mut storage = InMemoryStorage::new(); let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -207,7 +206,7 @@ mod test { } #[test] - fn clear_working_set() -> Fallible<()> { + fn clear_working_set() -> anyhow::Result<()> { let mut storage = InMemoryStorage::new(); let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); diff --git a/taskchampion/src/storage/kv.rs b/taskchampion/src/storage/kv.rs index 5a5e06242..a08b6b284 100644 --- a/taskchampion/src/storage/kv.rs +++ b/taskchampion/src/storage/kv.rs @@ -1,6 +1,5 @@ use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use crate::utils::Key; -use failure::{bail, Fallible}; use kv::msgpack::Msgpack; use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; use std::path::Path; @@ -21,7 +20,7 @@ const NEXT_OPERATION: u64 = 2; const NEXT_WORKING_SET_INDEX: u64 = 3; impl<'t> KVStorage<'t> { - pub fn new>(directory: P) -> Fallible> { + pub fn new>(directory: P) -> anyhow::Result> { let mut config = Config::default(directory); config.bucket("tasks", None); config.bucket("numbers", None); @@ -61,7 +60,7 @@ impl<'t> KVStorage<'t> { } impl<'t> Storage for KVStorage<'t> { - fn txn<'a>(&'a mut self) -> Fallible> { + fn txn<'a>(&'a mut self) -> anyhow::Result> { Ok(Box::new(Txn { storage: self, txn: Some(self.store.write_txn()?), @@ -103,7 +102,7 @@ impl<'t> Txn<'t> { } impl<'t> StorageTxn for Txn<'t> { - fn get_task(&mut self, uuid: Uuid) -> Fallible> { + fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { let bucket = self.tasks_bucket(); let buf = match self.kvtxn().get(bucket, uuid.into()) { Ok(buf) => buf, @@ -114,7 +113,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(Some(value)) } - fn create_task(&mut self, uuid: Uuid) -> Fallible { + fn create_task(&mut self, uuid: Uuid) -> anyhow::Result { let bucket = self.tasks_bucket(); let kvtxn = self.kvtxn(); match kvtxn.get(bucket, uuid.into()) { @@ -127,14 +126,14 @@ impl<'t> StorageTxn for Txn<'t> { } } - fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible<()> { + fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> anyhow::Result<()> { let bucket = self.tasks_bucket(); let kvtxn = self.kvtxn(); kvtxn.set(bucket, uuid.into(), Msgpack::to_value_buf(task)?)?; Ok(()) } - fn delete_task(&mut self, uuid: Uuid) -> Fallible { + fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result { let bucket = self.tasks_bucket(); let kvtxn = self.kvtxn(); match kvtxn.del(bucket, uuid.into()) { @@ -144,7 +143,7 @@ impl<'t> StorageTxn for Txn<'t> { } } - fn all_tasks(&mut self) -> Fallible> { + fn all_tasks(&mut self) -> anyhow::Result> { let bucket = self.tasks_bucket(); let kvtxn = self.kvtxn(); let all_tasks: Result, Error> = kvtxn @@ -155,7 +154,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(all_tasks?) } - fn all_task_uuids(&mut self) -> Fallible> { + fn all_task_uuids(&mut self) -> anyhow::Result> { let bucket = self.tasks_bucket(); let kvtxn = self.kvtxn(); Ok(kvtxn @@ -165,7 +164,7 @@ impl<'t> StorageTxn for Txn<'t> { .collect()) } - fn base_version(&mut self) -> Fallible { + fn base_version(&mut self) -> anyhow::Result { let bucket = self.uuids_bucket(); let base_version = match self.kvtxn().get(bucket, BASE_VERSION.into()) { Ok(buf) => buf, @@ -177,7 +176,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(base_version as VersionId) } - fn set_base_version(&mut self, version: VersionId) -> Fallible<()> { + fn set_base_version(&mut self, version: VersionId) -> anyhow::Result<()> { let uuids_bucket = self.uuids_bucket(); let kvtxn = self.kvtxn(); @@ -189,7 +188,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(()) } - fn operations(&mut self) -> Fallible> { + fn operations(&mut self) -> anyhow::Result> { let bucket = self.operations_bucket(); let kvtxn = self.kvtxn(); let all_ops: Result, Error> = kvtxn @@ -204,7 +203,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(all_ops.iter().map(|(_, v)| v.clone()).collect()) } - fn add_operation(&mut self, op: Operation) -> Fallible<()> { + fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { let numbers_bucket = self.numbers_bucket(); let operations_bucket = self.operations_bucket(); let kvtxn = self.kvtxn(); @@ -228,7 +227,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(()) } - fn set_operations(&mut self, ops: Vec) -> Fallible<()> { + fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { let numbers_bucket = self.numbers_bucket(); let operations_bucket = self.operations_bucket(); let kvtxn = self.kvtxn(); @@ -250,7 +249,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(()) } - fn get_working_set(&mut self) -> Fallible>> { + fn get_working_set(&mut self) -> anyhow::Result>> { let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); let kvtxn = self.kvtxn(); @@ -273,7 +272,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(res) } - fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { + fn add_to_working_set(&mut self, uuid: Uuid) -> anyhow::Result { let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); let kvtxn = self.kvtxn(); @@ -297,7 +296,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(next_index as usize) } - fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Fallible<()> { + fn set_working_set_item(&mut self, index: usize, uuid: Option) -> anyhow::Result<()> { let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); let kvtxn = self.kvtxn(); @@ -310,7 +309,7 @@ impl<'t> StorageTxn for Txn<'t> { }; if index >= next_index { - bail!("Index {} is not in the working set", index); + anyhow::bail!("Index {} is not in the working set", index); } if let Some(uuid) = uuid { @@ -326,7 +325,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(()) } - fn clear_working_set(&mut self) -> Fallible<()> { + fn clear_working_set(&mut self) -> anyhow::Result<()> { let working_set_bucket = self.working_set_bucket(); let numbers_bucket = self.numbers_bucket(); let kvtxn = self.kvtxn(); @@ -341,7 +340,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(()) } - fn commit(&mut self) -> Fallible<()> { + fn commit(&mut self) -> anyhow::Result<()> { if let Some(kvtxn) = self.txn.take() { kvtxn.commit()?; } else { @@ -355,11 +354,10 @@ impl<'t> StorageTxn for Txn<'t> { mod test { use super::*; use crate::storage::taskmap_with; - use failure::Fallible; use tempdir::TempDir; #[test] - fn test_create() -> Fallible<()> { + fn test_create() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); @@ -377,7 +375,7 @@ mod test { } #[test] - fn test_create_exists() -> Fallible<()> { + fn test_create_exists() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); @@ -395,7 +393,7 @@ mod test { } #[test] - fn test_get_missing() -> Fallible<()> { + fn test_get_missing() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); @@ -408,7 +406,7 @@ mod test { } #[test] - fn test_set_task() -> Fallible<()> { + fn test_set_task() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); @@ -429,7 +427,7 @@ mod test { } #[test] - fn test_delete_task_missing() -> Fallible<()> { + fn test_delete_task_missing() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); @@ -441,7 +439,7 @@ mod test { } #[test] - fn test_delete_task_exists() -> Fallible<()> { + fn test_delete_task_exists() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); @@ -458,7 +456,7 @@ mod test { } #[test] - fn test_all_tasks_empty() -> Fallible<()> { + fn test_all_tasks_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; { @@ -470,7 +468,7 @@ mod test { } #[test] - fn test_all_tasks_and_uuids() -> Fallible<()> { + fn test_all_tasks_and_uuids() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); @@ -524,7 +522,7 @@ mod test { } #[test] - fn test_base_version_default() -> Fallible<()> { + fn test_base_version_default() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; { @@ -535,7 +533,7 @@ mod test { } #[test] - fn test_base_version_setting() -> Fallible<()> { + fn test_base_version_setting() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let u = Uuid::new_v4(); @@ -552,7 +550,7 @@ mod test { } #[test] - fn test_operations() -> Fallible<()> { + fn test_operations() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); @@ -616,7 +614,7 @@ mod test { } #[test] - fn get_working_set_empty() -> Fallible<()> { + fn get_working_set_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; @@ -630,7 +628,7 @@ mod test { } #[test] - fn add_to_working_set() -> Fallible<()> { + fn add_to_working_set() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); @@ -653,7 +651,7 @@ mod test { } #[test] - fn clear_working_set() -> Fallible<()> { + fn clear_working_set() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; let mut storage = KVStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index fec354ed2..883f5e8b7 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -7,9 +7,9 @@ Typical uses of this crate do not interact directly with this module; [`StorageC However, users who wish to implement their own storage backends can implement the traits defined here and pass the result to [`Replica`](crate::Replica). */ -use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; +use anyhow::Result; mod config; mod inmemory; @@ -55,66 +55,66 @@ pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NO_VERSION_ID; /// It is safe and performant to drop transactions that did not modify any data without committing. pub trait StorageTxn { /// Get an (immutable) task, if it is in the storage - fn get_task(&mut self, uuid: Uuid) -> Fallible>; + fn get_task(&mut self, uuid: Uuid) -> Result>; /// Create an (empty) task, only if it does not already exist. Returns true if /// the task was created (did not already exist). - fn create_task(&mut self, uuid: Uuid) -> Fallible; + fn create_task(&mut self, uuid: Uuid) -> Result; /// Set a task, overwriting any existing task. If the task does not exist, this implicitly /// creates it (use `get_task` to check first, if necessary). - fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Fallible<()>; + fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Result<()>; /// Delete a task, if it exists. Returns true if the task was deleted (already existed) - fn delete_task(&mut self, uuid: Uuid) -> Fallible; + fn delete_task(&mut self, uuid: Uuid) -> Result; /// Get the uuids and bodies of all tasks in the storage, in undefined order. - fn all_tasks(&mut self) -> Fallible>; + fn all_tasks(&mut self) -> Result>; /// Get the uuids of all tasks in the storage, in undefined order. - fn all_task_uuids(&mut self) -> Fallible>; + fn all_task_uuids(&mut self) -> Result>; /// Get the current base_version for this storage -- the last version synced from the server. - fn base_version(&mut self) -> Fallible; + fn base_version(&mut self) -> Result; /// Set the current base_version for this storage. - fn set_base_version(&mut self, version: VersionId) -> Fallible<()>; + fn set_base_version(&mut self, version: VersionId) -> Result<()>; /// Get the current set of outstanding operations (operations that have not been sync'd to the /// server yet) - fn operations(&mut self) -> Fallible>; + fn operations(&mut self) -> Result>; /// Add an operation to the end of the list of operations in the storage. Note that this /// merely *stores* the operation; it is up to the TaskDB to apply it. - fn add_operation(&mut self, op: Operation) -> Fallible<()>; + fn add_operation(&mut self, op: Operation) -> Result<()>; /// Replace the current list of operations with a new list. - fn set_operations(&mut self, ops: Vec) -> Fallible<()>; + fn set_operations(&mut self, ops: Vec) -> Result<()>; /// Get the entire working set, with each task UUID at its appropriate (1-based) index. /// Element 0 is always None. - fn get_working_set(&mut self) -> Fallible>>; + fn get_working_set(&mut self) -> Result>>; /// Add a task to the working set and return its (one-based) index. This index will be one greater /// than the highest used index. - fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible; + fn add_to_working_set(&mut self, uuid: Uuid) -> Result; /// Update the working set task at the given index. This cannot add a new item to the /// working set. - fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Fallible<()>; + fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Result<()>; /// Clear all tasks from the working set in preparation for a garbage-collection operation. /// Note that this is the only way items are removed from the set. - fn clear_working_set(&mut self) -> Fallible<()>; + fn clear_working_set(&mut self) -> Result<()>; /// Commit any changes made in the transaction. It is an error to call this more than /// once. - fn commit(&mut self) -> Fallible<()>; + fn commit(&mut self) -> Result<()>; } /// A trait for objects able to act as task storage. Most of the interesting behavior is in the /// [`crate::storage::StorageTxn`] trait. pub trait Storage { /// Begin a transaction - fn txn<'a>(&'a mut self) -> Fallible>; + fn txn<'a>(&'a mut self) -> Result>; } diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index fcf47f005..afc7fc253 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -1,7 +1,6 @@ use crate::replica::Replica; use crate::storage::TaskMap; use chrono::prelude::*; -use failure::{format_err, Fallible}; use log::trace; use std::convert::{TryFrom, TryInto}; use std::fmt; @@ -95,9 +94,9 @@ pub struct Tag(String); pub const INVALID_TAG_CHARACTERS: &str = "+-*/(<>^! %=~"; impl Tag { - fn from_str(value: &str) -> Result { - fn err(value: &str) -> Result { - Err(format_err!("invalid tag {:?}", value)) + fn from_str(value: &str) -> Result { + fn err(value: &str) -> Result { + anyhow::bail!("invalid tag {:?}", value) } if let Some(c) = value.chars().next() { @@ -119,7 +118,7 @@ impl Tag { } impl TryFrom<&str> for Tag { - type Error = failure::Error; + type Error = anyhow::Error; fn try_from(value: &str) -> Result { Self::from_str(value) @@ -127,7 +126,7 @@ impl TryFrom<&str> for Tag { } impl TryFrom<&String> for Tag { - type Error = failure::Error; + type Error = anyhow::Error; fn try_from(value: &String) -> Result { Self::from_str(&value[..]) @@ -264,7 +263,7 @@ impl<'r> TaskMut<'r> { /// Set the task's status. This also adds the task to the working set if the /// new status puts it in that set. - pub fn set_status(&mut self, status: Status) -> Fallible<()> { + pub fn set_status(&mut self, status: Status) -> anyhow::Result<()> { if status == Status::Pending { let uuid = self.uuid; self.replica.add_to_working_set(uuid)?; @@ -272,17 +271,17 @@ impl<'r> TaskMut<'r> { self.set_string("status", Some(String::from(status.to_taskmap()))) } - pub fn set_description(&mut self, description: String) -> Fallible<()> { + pub fn set_description(&mut self, description: String) -> anyhow::Result<()> { self.set_string("description", Some(description)) } - pub fn set_modified(&mut self, modified: DateTime) -> Fallible<()> { + pub fn set_modified(&mut self, modified: DateTime) -> anyhow::Result<()> { self.set_timestamp("modified", Some(modified)) } /// Start the task by creating "start. Fallible<()> { + pub fn start(&mut self) -> anyhow::Result<()> { if self.is_active() { return Ok(()); } @@ -291,7 +290,7 @@ impl<'r> TaskMut<'r> { } /// Stop the task by adding the current timestamp to all un-resolved "start." keys. - pub fn stop(&mut self) -> Fallible<()> { + pub fn stop(&mut self) -> anyhow::Result<()> { let keys = self .taskmap .iter() @@ -308,18 +307,18 @@ impl<'r> TaskMut<'r> { } /// Add a tag to this task. Does nothing if the tag is already present. - pub fn add_tag(&mut self, tag: &Tag) -> Fallible<()> { + pub fn add_tag(&mut self, tag: &Tag) -> anyhow::Result<()> { self.set_string(format!("tag.{}", tag), Some("".to_owned())) } /// Remove a tag from this task. Does nothing if the tag is not present. - pub fn remove_tag(&mut self, tag: &Tag) -> Fallible<()> { + pub fn remove_tag(&mut self, tag: &Tag) -> anyhow::Result<()> { self.set_string(format!("tag.{}", tag), None) } // -- utility functions - fn lastmod(&mut self) -> Fallible<()> { + fn lastmod(&mut self) -> anyhow::Result<()> { if !self.updated_modified { let now = format!("{}", Utc::now().timestamp()); self.replica @@ -331,7 +330,7 @@ impl<'r> TaskMut<'r> { Ok(()) } - fn set_string>(&mut self, property: S, value: Option) -> Fallible<()> { + fn set_string>(&mut self, property: S, value: Option) -> anyhow::Result<()> { let property = property.into(); self.lastmod()?; self.replica @@ -347,7 +346,7 @@ impl<'r> TaskMut<'r> { Ok(()) } - fn set_timestamp(&mut self, property: &str, value: Option>) -> Fallible<()> { + fn set_timestamp(&mut self, property: &str, value: Option>) -> anyhow::Result<()> { self.lastmod()?; if let Some(value) = value { let ts = format!("{}", value.timestamp()); @@ -364,7 +363,7 @@ impl<'r> TaskMut<'r> { /// Used by tests to ensure that updates are properly written #[cfg(test)] - fn reload(&mut self) -> Fallible<()> { + fn reload(&mut self) -> anyhow::Result<()> { let uuid = self.uuid; let task = self.replica.get_task(uuid)?.unwrap(); self.task.taskmap = task.taskmap; diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index cdf924ade..7bee3b741 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -1,7 +1,6 @@ use crate::errors::Error; use crate::server::{AddVersionResult, GetVersionResult, Server}; use crate::storage::{Operation, Storage, StorageTxn, TaskMap}; -use failure::{format_err, Fallible}; use log::{info, trace, warn}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -34,7 +33,7 @@ impl TaskDB { /// Apply an operation to the TaskDB. Aside from synchronization operations, this is the only way /// to modify the TaskDB. In cases where an operation does not make sense, this function will do /// nothing and return an error (but leave the TaskDB in a consistent state). - pub fn apply(&mut self, op: Operation) -> Fallible<()> { + pub fn apply(&mut self, op: Operation) -> anyhow::Result<()> { // TODO: differentiate error types here? let mut txn = self.storage.txn()?; if let err @ Err(_) = TaskDB::apply_op(txn.as_mut(), &op) { @@ -45,7 +44,7 @@ impl TaskDB { Ok(()) } - fn apply_op(txn: &mut dyn StorageTxn, op: &Operation) -> Fallible<()> { + fn apply_op(txn: &mut dyn StorageTxn, op: &Operation) -> anyhow::Result<()> { match op { Operation::Create { uuid } => { // insert if the task does not already exist @@ -81,25 +80,25 @@ impl TaskDB { } /// Get all tasks. - pub fn all_tasks(&mut self) -> Fallible> { + pub fn all_tasks(&mut self) -> anyhow::Result> { let mut txn = self.storage.txn()?; txn.all_tasks() } /// Get the UUIDs of all tasks - pub fn all_task_uuids(&mut self) -> Fallible> { + pub fn all_task_uuids(&mut self) -> anyhow::Result> { let mut txn = self.storage.txn()?; txn.all_task_uuids() } /// Get the working set - pub fn working_set(&mut self) -> Fallible>> { + pub fn working_set(&mut self) -> anyhow::Result>> { let mut txn = self.storage.txn()?; txn.get_working_set() } /// Get a single task, by uuid. - pub fn get_task(&mut self, uuid: Uuid) -> Fallible> { + pub fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { let mut txn = self.storage.txn()?; txn.get_task(uuid) } @@ -108,7 +107,7 @@ impl TaskDB { /// renumbers the existing working-set tasks to eliminate gaps, and also adds any tasks that /// are not already in the working set but should be. The rebuild occurs in a single /// trasnsaction against the storage backend. - pub fn rebuild_working_set(&mut self, in_working_set: F, renumber: bool) -> Fallible<()> + pub fn rebuild_working_set(&mut self, in_working_set: F, renumber: bool) -> anyhow::Result<()> where F: Fn(&TaskMap) -> bool, { @@ -169,7 +168,7 @@ impl TaskDB { /// Add the given uuid to the working set and return its index; if it is already in the working /// set, its index is returned. This does *not* renumber any existing tasks. - pub fn add_to_working_set(&mut self, uuid: Uuid) -> Fallible { + pub fn add_to_working_set(&mut self, uuid: Uuid) -> anyhow::Result { let mut txn = self.storage.txn()?; // search for an existing entry for this task.. for (i, elt) in txn.get_working_set()?.iter().enumerate() { @@ -185,7 +184,7 @@ impl TaskDB { } /// Sync to the given server, pulling remote changes and pushing local changes. - pub fn sync(&mut self, server: &mut Box) -> Fallible<()> { + pub fn sync(&mut self, server: &mut Box) -> anyhow::Result<()> { let mut txn = self.storage.txn()?; // retry synchronizing until the server accepts our version (this allows for races between @@ -247,9 +246,9 @@ impl TaskDB { ); if let Some(requested) = requested_parent_version_id { if parent_version_id == requested { - return Err(format_err!( + anyhow::bail!( "Server's task history has diverged from this replica" - )); + ); } } requested_parent_version_id = Some(parent_version_id); @@ -261,7 +260,7 @@ impl TaskDB { Ok(()) } - fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> Fallible<()> { + fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Result<()> { // The situation here is that the server has already applied all server operations, and we // have already applied all local operations, so states have diverged by several // operations. We need to figure out what operations to apply locally and on the server in @@ -502,16 +501,16 @@ mod tests { } #[test] - fn rebuild_working_set_renumber() -> Fallible<()> { + fn rebuild_working_set_renumber() -> anyhow::Result<()> { rebuild_working_set(true) } #[test] - fn rebuild_working_set_no_renumber() -> Fallible<()> { + fn rebuild_working_set_no_renumber() -> anyhow::Result<()> { rebuild_working_set(false) } - fn rebuild_working_set(renumber: bool) -> Fallible<()> { + fn rebuild_working_set(renumber: bool) -> anyhow::Result<()> { let mut db = TaskDB::new_inmemory(); let mut uuids = vec![]; uuids.push(Uuid::new_v4()); From 0e7a4c2c33784263f6d43bfa68ae1c91ab40952a Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 25 Mar 2021 16:34:53 +1100 Subject: [PATCH 177/548] Fix minor typo in doc comment --- cli/src/report.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/report.rs b/cli/src/report.rs index fb60246a0..8670281f3 100644 --- a/cli/src/report.rs +++ b/cli/src/report.rs @@ -29,7 +29,7 @@ pub(crate) struct Column { #[derive(Clone, Debug, PartialEq)] #[allow(dead_code)] pub(crate) enum Property { - /// The task's ID, either working-set index or Uuid if no in the working set + /// The task's ID, either working-set index or Uuid if not in the working set Id, /// The task's full UUID From 6d77b9a319a2aadadb6d5df944330769a7cca87b Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 25 Mar 2021 17:10:11 +1100 Subject: [PATCH 178/548] cargo fmt --- cli/src/argparse/command.rs | 5 +---- cli/src/report.rs | 10 +++++++--- taskchampion/src/server/local.rs | 5 ++++- taskchampion/src/server/remote/crypto.rs | 4 +++- taskchampion/src/server/remote/mod.rs | 5 ++++- taskchampion/src/server/test.rs | 5 ++++- taskchampion/src/server/types.rs | 5 ++++- taskchampion/src/task.rs | 12 ++++++++++-- taskchampion/src/taskdb.rs | 10 ++++++---- 9 files changed, 43 insertions(+), 18 deletions(-) diff --git a/cli/src/argparse/command.rs b/cli/src/argparse/command.rs index 198a0abf5..f9c71352c 100644 --- a/cli/src/argparse/command.rs +++ b/cli/src/argparse/command.rs @@ -32,10 +32,7 @@ impl Command { pub fn from_argv(argv: &[&str]) -> anyhow::Result { match Command::parse(argv) { Ok((&[], cmd)) => Ok(cmd), - Ok((trailing, _)) => bail!( - "command line has trailing arguments: {:?}", - trailing - ), + Ok((trailing, _)) => bail!("command line has trailing arguments: {:?}", trailing), Err(Err::Incomplete(_)) => unreachable!(), Err(Err::Error(e)) => bail!("command line not recognized: {:?}", e), Err(Err::Failure(e)) => bail!("command line not recognized: {:?}", e), diff --git a/cli/src/report.rs b/cli/src/report.rs index 8670281f3..e2cdddac4 100644 --- a/cli/src/report.rs +++ b/cli/src/report.rs @@ -1,7 +1,7 @@ //! This module contains the data structures used to define reports. use crate::argparse::{Condition, Filter}; -use anyhow::{bail}; +use anyhow::bail; /// A report specifies a filter as well as a sort order and information about which /// task attributes to display @@ -85,7 +85,9 @@ impl Report { .map_err(|e| anyhow::anyhow!(".sort: {}", e))? .drain(..) .enumerate() - .map(|(i, v)| Sort::from_config(v).map_err(|e| anyhow::anyhow!(".sort[{}]{}", i, e))) + .map(|(i, v)| { + Sort::from_config(v).map_err(|e| anyhow::anyhow!(".sort[{}]{}", i, e)) + }) .collect::>>()? } else { vec![] @@ -98,7 +100,9 @@ impl Report { .map_err(|e| anyhow::anyhow!(".columns: {}", e))? .drain(..) .enumerate() - .map(|(i, v)| Column::from_config(v).map_err(|e| anyhow::anyhow!(".columns[{}]{}", i, e))) + .map(|(i, v)| { + Column::from_config(v).map_err(|e| anyhow::anyhow!(".columns[{}]{}", i, e)) + }) .collect::>>()?; let conditions = if let Some(conditions) = map.remove("filter") { diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index f24ee96f6..84ee18fa8 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -132,7 +132,10 @@ impl<'t> Server for LocalServer<'t> { } /// Get a vector of all versions after `since_version` - fn get_child_version(&mut self, parent_version_id: VersionId) -> anyhow::Result { + fn get_child_version( + &mut self, + parent_version_id: VersionId, + ) -> anyhow::Result { if let Some(version) = self.get_version_by_parent_version_id(parent_version_id)? { Ok(GetVersionResult::Version { version_id: version.version_id, diff --git a/taskchampion/src/server/remote/crypto.rs b/taskchampion/src/server/remote/crypto.rs index 3751d606f..a236ef7be 100644 --- a/taskchampion/src/server/remote/crypto.rs +++ b/taskchampion/src/server/remote/crypto.rs @@ -62,7 +62,9 @@ impl TryFrom for HistoryCiphertext { reader.read_to_end(&mut bytes)?; Ok(Self(bytes)) } else { - Err(anyhow::anyhow!("Response did not have expected content-type")) + Err(anyhow::anyhow!( + "Response did not have expected content-type" + )) } } } diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index ed88d4b15..29ee216f1 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -83,7 +83,10 @@ impl Server for RemoteServer { } } - fn get_child_version(&mut self, parent_version_id: VersionId) -> anyhow::Result { + fn get_child_version( + &mut self, + parent_version_id: VersionId, + ) -> anyhow::Result { let url = format!( "{}/client/get-child-version/{}", self.origin, parent_version_id diff --git a/taskchampion/src/server/test.rs b/taskchampion/src/server/test.rs index b8e439b47..798a54c1e 100644 --- a/taskchampion/src/server/test.rs +++ b/taskchampion/src/server/test.rs @@ -63,7 +63,10 @@ impl Server for TestServer { } /// Get a vector of all versions after `since_version` - fn get_child_version(&mut self, parent_version_id: VersionId) -> anyhow::Result { + fn get_child_version( + &mut self, + parent_version_id: VersionId, + ) -> anyhow::Result { if let Some(version) = self.versions.get(&parent_version_id) { Ok(GetVersionResult::Version { version_id: version.version_id, diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index 4478934d5..466e286a5 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -43,5 +43,8 @@ pub trait Server { ) -> anyhow::Result; /// Get the version with the given parent VersionId - fn get_child_version(&mut self, parent_version_id: VersionId) -> anyhow::Result; + fn get_child_version( + &mut self, + parent_version_id: VersionId, + ) -> anyhow::Result; } diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index afc7fc253..07bb19909 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -330,7 +330,11 @@ impl<'r> TaskMut<'r> { Ok(()) } - fn set_string>(&mut self, property: S, value: Option) -> anyhow::Result<()> { + fn set_string>( + &mut self, + property: S, + value: Option, + ) -> anyhow::Result<()> { let property = property.into(); self.lastmod()?; self.replica @@ -346,7 +350,11 @@ impl<'r> TaskMut<'r> { Ok(()) } - fn set_timestamp(&mut self, property: &str, value: Option>) -> anyhow::Result<()> { + fn set_timestamp( + &mut self, + property: &str, + value: Option>, + ) -> anyhow::Result<()> { self.lastmod()?; if let Some(value) = value { let ts = format!("{}", value.timestamp()); diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 7bee3b741..ed728b8e6 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -107,7 +107,11 @@ impl TaskDB { /// renumbers the existing working-set tasks to eliminate gaps, and also adds any tasks that /// are not already in the working set but should be. The rebuild occurs in a single /// trasnsaction against the storage backend. - pub fn rebuild_working_set(&mut self, in_working_set: F, renumber: bool) -> anyhow::Result<()> + pub fn rebuild_working_set( + &mut self, + in_working_set: F, + renumber: bool, + ) -> anyhow::Result<()> where F: Fn(&TaskMap) -> bool, { @@ -246,9 +250,7 @@ impl TaskDB { ); if let Some(requested) = requested_parent_version_id { if parent_version_id == requested { - anyhow::bail!( - "Server's task history has diverged from this replica" - ); + anyhow::bail!("Server's task history has diverged from this replica"); } } requested_parent_version_id = Some(parent_version_id); From b5b9e63a4ff615808bf59c1f83c0bd9fc30d4a18 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 25 Mar 2021 18:20:47 +1100 Subject: [PATCH 179/548] Disable "unnecessary_wraps" linter as it is almost entirely false-positives Was added in 1.50 and will be reduced in severity in next release as per https://github.com/rust-lang/rust-clippy/pull/6765 --- cli/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 29b3f8afc..e71dcc024 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,4 +1,5 @@ #![deny(clippy::all)] +#![allow(clippy::unnecessary_wraps)] // for Rust 1.50, https://github.com/rust-lang/rust-clippy/pull/6765 /*! This crate implements the command-line interface to TaskChampion. From c04ece0e90786e87f081c01ed36e71d0de0a6622 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 25 Mar 2021 18:21:18 +1100 Subject: [PATCH 180/548] Fix one actual clippy lint: remove an unnecessary clone --- sync-server/src/server.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index be67639be..0fcfb4755 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -132,13 +132,7 @@ mod test { }; assert_eq!( - add_version( - txn, - client_key, - client, - parent_version_id, - history_segment.clone() - )?, + add_version(txn, client_key, client, parent_version_id, history_segment)?, AddVersionResult::ExpectedParentVersion(existing_parent_version_id) ); From 5ee67d65e2a19fe9ec645b0a40097ebd540fc1d4 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 25 Mar 2021 18:29:53 +1100 Subject: [PATCH 181/548] Remove whitespace in comment to avoid cargo fmt doing weird things --- taskchampion/src/storage/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index 883f5e8b7..c5ffb8cc2 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -1,15 +1,13 @@ /** - This module defines the backend storage used by [`Replica`](crate::Replica). It defines a [trait](crate::storage::Storage) for storage implementations, and provides a default on-disk implementation as well as an in-memory implementation for testing. Typical uses of this crate do not interact directly with this module; [`StorageConfig`](crate::StorageConfig) is sufficient. However, users who wish to implement their own storage backends can implement the traits defined here and pass the result to [`Replica`](crate::Replica). - */ +use anyhow::Result; use std::collections::HashMap; use uuid::Uuid; -use anyhow::Result; mod config; mod inmemory; From 5745e9b396f7f4b63ec7b4e7a4f40211c65e0d98 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 26 Mar 2021 09:00:17 -0400 Subject: [PATCH 182/548] use the correct 'package-ecosystem' config --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 02e3e8249..14441fc55 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "rust" + - package-ecosystem: "cargo" directory: "/" # Location of package manifests schedule: interval: "daily" From ae46f82d940138a7ccfbd703840eb44a3f991617 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Mar 2021 22:04:30 +0000 Subject: [PATCH 183/548] Bump serde from 1.0.117 to 1.0.125 Bumps [serde](https://github.com/serde-rs/serde) from 1.0.117 to 1.0.125. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.117...v1.0.125) Signed-off-by: dependabot[bot] --- Cargo.lock | 12 ++++++------ sync-server/Cargo.toml | 2 +- taskchampion/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 154c73a61..8916c0a13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2050,18 +2050,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.117" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.117" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ "proc-macro2", "quote", @@ -2221,9 +2221,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.52" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c1e438504729046a5cfae47f97c30d6d083c7d91d94603efdae3477fc070d4c" +checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" dependencies = [ "proc-macro2", "quote", diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index d1662b230..d24e9b40b 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -11,7 +11,7 @@ uuid = { version = "^0.8.1", features = ["serde", "v4"] } actix-web = "^3.3.0" failure = "^0.1.8" futures = "^0.3.8" -serde = "^1.0.104" +serde = "^1.0.125" kv = {version = "^0.10.0", features = ["msgpack-value"]} clap = "^2.33.0" log = "^0.4.11" diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index e04610ceb..5a8c16094 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" [dependencies] uuid = { version = "^0.8.1", features = ["serde", "v4"] } -serde = "^1.0.104" +serde = "^1.0.125" serde_json = "^1.0" chrono = { version = "^0.4.10", features = ["serde"] } failure = {version = "^0.1.5", features = ["derive"] } From bb821feaa9cfac24a44a5456392b451e262cb86b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 27 Mar 2021 18:47:34 -0400 Subject: [PATCH 184/548] use clippy stable, not preview --- .taskcluster.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.taskcluster.yml b/.taskcluster.yml index 2204a3540..05b58d6c7 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -61,7 +61,7 @@ tasks: cd repo && git config advice.detachedHead false && git checkout ${ref} && - rustup component add clippy-preview && + rustup component add clippy && cargo clippy && cargo fmt -- --check metadata: From e0dbeb27639d52fda1b1b607a69d31e3806f8799 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Mar 2021 22:50:56 +0000 Subject: [PATCH 185/548] Bump log from 0.4.11 to 0.4.14 Bumps [log](https://github.com/rust-lang/log) from 0.4.11 to 0.4.14. - [Release notes](https://github.com/rust-lang/log/releases) - [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/log/compare/0.4.11...0.4.14) Signed-off-by: dependabot[bot] --- Cargo.lock | 6 +++--- cli/Cargo.toml | 2 +- sync-server/Cargo.toml | 2 +- taskchampion/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49457414e..ab4b6db1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1261,11 +1261,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", ] [[package]] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c68c40934..b9959c1f3 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,7 +8,7 @@ version = "0.3.0" dirs = "^3.0.1" env_logger = "^0.8.2" anyhow = "1.0" -log = "^0.4.11" +log = "^0.4.14" nom = "^6.0.1" prettytable-rs = "^0.8.0" textwrap = { version="^0.12.1", features=["terminal_size"] } diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 4923fce84..e30ce6801 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -14,7 +14,7 @@ futures = "^0.3.8" serde = "^1.0.125" kv = {version = "^0.10.0", features = ["msgpack-value"]} clap = "^2.33.0" -log = "^0.4.11" +log = "^0.4.14" env_logger = "^0.8.2" [dev-dependencies] diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 82733ed8d..06751f337 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -20,7 +20,7 @@ thiserror = "1.0" kv = {version = "^0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "^0.12.3"} ureq = "^1.5.2" -log = "^0.4.11" +log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } [dev-dependencies] From 718f16154c6f8de3fe2fe4076634d5a033182345 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Mar 2021 23:05:56 +0000 Subject: [PATCH 186/548] Bump env_logger from 0.8.2 to 0.8.3 Bumps [env_logger](https://github.com/env-logger-rs/env_logger) from 0.8.2 to 0.8.3. - [Release notes](https://github.com/env-logger-rs/env_logger/releases) - [Changelog](https://github.com/env-logger-rs/env_logger/blob/master/CHANGELOG.md) - [Commits](https://github.com/env-logger-rs/env_logger/compare/v0.8.2...v0.8.3) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- cli/Cargo.toml | 2 +- sync-server/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab4b6db1d..d8f5ae2a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -807,9 +807,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" +checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" dependencies = [ "atty", "humantime", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b9959c1f3..cfd501b9b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -6,7 +6,7 @@ version = "0.3.0" [dependencies] dirs = "^3.0.1" -env_logger = "^0.8.2" +env_logger = "^0.8.3" anyhow = "1.0" log = "^0.4.14" nom = "^6.0.1" diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index e30ce6801..2137ae6e4 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -15,7 +15,7 @@ serde = "^1.0.125" kv = {version = "^0.10.0", features = ["msgpack-value"]} clap = "^2.33.0" log = "^0.4.14" -env_logger = "^0.8.2" +env_logger = "^0.8.3" [dev-dependencies] actix-rt = "^1.1.1" From c42cc3bdcb1b3874101491e0745a99416b53133a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 27 Mar 2021 19:12:34 -0400 Subject: [PATCH 187/548] fix new clippy warnings --- cli/src/argparse/mod.rs | 5 +++++ cli/src/invocation/mod.rs | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index ab54326f1..3bdac1a8f 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -1,3 +1,8 @@ +// Nested functions that always return Ok(..) are used as callbacks in a context where a Result is +// expected, so the unnecessary_wraps clippy lint is not useful here. + +#![allow(clippy::unnecessary_wraps)] + /*! This module is responsible for parsing command lines (`Arglist`, an alias for `&[&str]`) into `Command` instances. diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index fd910a8f0..5bce1a16d 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -23,7 +23,7 @@ pub(crate) fn invoke(command: Command, settings: Config) -> anyhow::Result<()> { log::debug!("command: {:?}", command); log::debug!("settings: {:?}", settings); - let mut w = get_writer()?; + let mut w = get_writer(); // This function examines the command and breaks out the necessary bits to call one of the // `execute` functions in a submodule of `cmd`. @@ -136,10 +136,10 @@ fn get_server(settings: &Config) -> anyhow::Result> { } /// Get a WriteColor implementation based on whether the output is a tty. -fn get_writer() -> anyhow::Result { - Ok(StandardStream::stdout(if atty::is(atty::Stream::Stdout) { +fn get_writer() -> StandardStream { + StandardStream::stdout(if atty::is(atty::Stream::Stdout) { ColorChoice::Auto } else { ColorChoice::Never - })) + }) } From 1de0816661fbc6e4e9ef573aad8b246f1fa40c2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Mar 2021 23:05:55 +0000 Subject: [PATCH 188/548] Bump ureq from 1.5.2 to 2.1.0 Bumps [ureq](https://github.com/algesten/ureq) from 1.5.2 to 2.1.0. - [Release notes](https://github.com/algesten/ureq/releases) - [Changelog](https://github.com/algesten/ureq/blob/main/CHANGELOG.md) - [Commits](https://github.com/algesten/ureq/commits/2.1.0) Signed-off-by: dependabot[bot] --- Cargo.lock | 78 ++++++----------------------------------- taskchampion/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8f5ae2a7..e4fe07f8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,7 +47,7 @@ dependencies = [ "actix-service", "actix-threadpool", "actix-utils", - "base64 0.13.0", + "base64", "bitflags", "brotli2", "bytes", @@ -369,7 +369,7 @@ dependencies = [ "actix-http", "actix-rt", "actix-service", - "base64 0.13.0", + "base64", "bytes", "cfg-if 1.0.0", "derive_more", @@ -403,12 +403,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -633,22 +627,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "cookie_store" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" -dependencies = [ - "cookie", - "idna", - "log", - "publicsuffix", - "serde", - "serde_json", - "time 0.2.23", - "url", -] - [[package]] name = "copyless" version = "0.1.5" @@ -818,15 +796,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - [[package]] name = "flate2" version = "1.0.19" @@ -1632,28 +1601,6 @@ version = "2.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da78e04bc0e40f36df43ecc6575e4f4b180e8156c4efd73f13d5619479b05696" -[[package]] -name = "publicsuffix" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b" -dependencies = [ - "error-chain", - "idna", - "lazy_static", - "regex", - "url", -] - -[[package]] -name = "qstring" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" -dependencies = [ - "percent-encoding", -] - [[package]] name = "quick-error" version = "1.2.3" @@ -1949,7 +1896,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" dependencies = [ - "base64 0.13.0", + "base64", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -1972,11 +1919,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" +checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" dependencies = [ - "base64 0.12.3", + "base64", "log", "ring", "sct", @@ -2633,17 +2580,14 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "ureq" -version = "1.5.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a599426c7388ab189dfd0eeb84c8d879490abc73e3e62a0b6a40e286f6427ab7" +checksum = "6fbeb1aabb07378cf0e084971a74f24241273304653184f54cdce113c0d7df1b" dependencies = [ - "base64 0.13.0", + "base64", "chunked_transfer", - "cookie", - "cookie_store", "log", "once_cell", - "qstring", "rustls", "url", "webpki", @@ -2781,9 +2725,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" +checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376" dependencies = [ "webpki", ] diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 06751f337..a8d3d2a3f 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -19,7 +19,7 @@ anyhow = "1.0" thiserror = "1.0" kv = {version = "^0.10.0", features = ["msgpack-value"]} lmdb-rkv = {version = "^0.12.3"} -ureq = "^1.5.2" +ureq = "^2.1.0" log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } From 0f02b38fd8f3e452c5e3e13d72f4c1e691a0c35f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 28 Mar 2021 17:40:39 -0400 Subject: [PATCH 189/548] Modify ureq usage for new version --- taskchampion/src/server/remote/mod.rs | 81 ++++++++++++--------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index 29ee216f1..8916c7a80 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -1,5 +1,6 @@ use crate::server::{AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId}; use std::convert::TryInto; +use std::time::Duration; use uuid::Uuid; mod crypto; @@ -24,21 +25,14 @@ impl RemoteServer { origin, client_key, encryption_secret: encryption_secret.into(), - agent: ureq::agent(), + agent: ureq::AgentBuilder::new() + .timeout_connect(Duration::from_secs(10)) + .timeout_read(Duration::from_secs(60)) + .build(), } } } -/// Convert a ureq::Response to an Error -fn resp_to_error(resp: ureq::Response) -> anyhow::Error { - return anyhow::anyhow!( - "error {}: {}", - resp.status(), - resp.into_string() - .unwrap_or_else(|e| format!("(could not read response: {})", e)) - ); -} - /// Read a UUID-bearing header or fail trying fn get_uuid_header(resp: &ureq::Response, name: &str) -> anyhow::Result { let value = resp @@ -61,25 +55,25 @@ impl Server for RemoteServer { history_segment, }; let history_ciphertext = history_cleartext.seal(&self.encryption_secret)?; - let resp = self + match self .agent .post(&url) - .timeout_connect(10_000) - .timeout_read(60_000) .set( "Content-Type", "application/vnd.taskchampion.history-segment", ) .set("X-Client-Key", &self.client_key.to_string()) - .send_bytes(history_ciphertext.as_ref()); - if resp.ok() { - let version_id = get_uuid_header(&resp, "X-Version-Id")?; - Ok(AddVersionResult::Ok(version_id)) - } else if resp.status() == 409 { - let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; - Ok(AddVersionResult::ExpectedParentVersion(parent_version_id)) - } else { - Err(resp_to_error(resp)) + .send_bytes(history_ciphertext.as_ref()) + { + Ok(resp) => { + let version_id = get_uuid_header(&resp, "X-Version-Id")?; + Ok(AddVersionResult::Ok(version_id)) + } + Err(ureq::Error::Status(status, resp)) if status == 409 => { + let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; + Ok(AddVersionResult::ExpectedParentVersion(parent_version_id)) + } + Err(err) => Err(err.into()), } } @@ -91,30 +85,29 @@ impl Server for RemoteServer { "{}/client/get-child-version/{}", self.origin, parent_version_id ); - let resp = self + match self .agent .get(&url) - .timeout_connect(10_000) - .timeout_read(60_000) .set("X-Client-Key", &self.client_key.to_string()) - .call(); - - if resp.ok() { - let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; - let version_id = get_uuid_header(&resp, "X-Version-Id")?; - let history_ciphertext: HistoryCiphertext = resp.try_into()?; - let history_segment = history_ciphertext - .open(&self.encryption_secret, parent_version_id)? - .history_segment; - Ok(GetVersionResult::Version { - version_id, - parent_version_id, - history_segment, - }) - } else if resp.status() == 404 { - Ok(GetVersionResult::NoSuchVersion) - } else { - Err(resp_to_error(resp)) + .call() + { + Ok(resp) => { + let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; + let version_id = get_uuid_header(&resp, "X-Version-Id")?; + let history_ciphertext: HistoryCiphertext = resp.try_into()?; + let history_segment = history_ciphertext + .open(&self.encryption_secret, parent_version_id)? + .history_segment; + Ok(GetVersionResult::Version { + version_id, + parent_version_id, + history_segment, + }) + } + Err(ureq::Error::Status(status, _)) if status == 404 => { + Ok(GetVersionResult::NoSuchVersion) + } + Err(err) => Err(err.into()), } } } From 959c4f47f7be7c2d28dd13346447476a3fa1003b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Mar 2021 09:49:46 +0000 Subject: [PATCH 190/548] Bump proptest from 0.9.6 to 1.0.0 Bumps [proptest](https://github.com/altsysrq/proptest) from 0.9.6 to 1.0.0. - [Release notes](https://github.com/altsysrq/proptest/releases) - [Changelog](https://github.com/AltSysrq/proptest/blob/master/CHANGELOG.md) - [Commits](https://github.com/altsysrq/proptest/compare/0.9.6...1.0.0) Signed-off-by: dependabot[bot] --- Cargo.lock | 168 ++++++++++++++-------------------------- taskchampion/Cargo.toml | 2 +- 2 files changed, 60 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8f5ae2a7..76c9c646e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,12 +347,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - [[package]] name = "autocfg" version = "1.0.1" @@ -580,15 +574,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - [[package]] name = "cloudabi" version = "0.1.0" @@ -676,7 +661,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" dependencies = [ - "autocfg 1.0.1", + "autocfg", "cfg-if 1.0.0", "lazy_static", ] @@ -1017,6 +1002,17 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + [[package]] name = "gimli" version = "0.23.0" @@ -1118,7 +1114,7 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" dependencies = [ - "autocfg 1.0.1", + "autocfg", "hashbrown", ] @@ -1308,7 +1304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" dependencies = [ "adler", - "autocfg 1.0.1", + "autocfg", ] [[package]] @@ -1399,7 +1395,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-traits", ] @@ -1409,7 +1405,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1", + "autocfg", ] [[package]] @@ -1458,7 +1454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" dependencies = [ "cfg-if 0.1.10", - "cloudabi 0.1.0", + "cloudabi", "instant", "libc", "redox_syscall", @@ -1608,18 +1604,18 @@ dependencies = [ [[package]] name = "proptest" -version = "0.9.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c477819b845fe023d33583ebf10c9f62518c8d79a0960ba5c36d6ac8a55a5b" +checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits", - "quick-error", - "rand 0.6.5", - "rand_chacha 0.1.1", + "quick-error 2.0.0", + "rand 0.8.3", + "rand_chacha 0.3.0", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -1660,6 +1656,12 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-error" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" + [[package]] name = "quote" version = "1.0.7" @@ -1688,46 +1690,28 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi 0.3.9", -] - [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.15", "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_hc", ] [[package]] -name = "rand_chacha" -version = "0.1.1" +name = "rand" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", + "libc", + "rand_chacha 0.3.0", + "rand_core 0.6.2", ] [[package]] @@ -1740,6 +1724,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.2", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1761,16 +1755,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.15", ] [[package]] -name = "rand_hc" -version = "0.1.0" +name = "rand_core" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ - "rand_core 0.3.1", + "getrandom 0.2.2", ] [[package]] @@ -1782,57 +1776,13 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi 0.3.9", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi 0.0.3", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi 0.3.9", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", -] - [[package]] name = "rand_xorshift" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.3.1", + "rand_core 0.6.2", ] [[package]] @@ -1856,7 +1806,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ - "getrandom", + "getrandom 0.1.15", "redox_syscall", "rust-argon2", ] @@ -1904,7 +1854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" dependencies = [ "hostname", - "quick-error", + "quick-error 1.2.3", ] [[package]] @@ -1985,12 +1935,12 @@ dependencies = [ [[package]] name = "rusty-fork" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error", + "quick-error 1.2.3", "tempfile", "wait-timeout", ] diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 06751f337..19719a62e 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -24,5 +24,5 @@ log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } [dev-dependencies] -proptest = "^0.9.4" +proptest = "^1.0.0" tempdir = "^0.3.7" From fdeadfd98186a948ed728b0c30a787ea4fed3606 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 28 Mar 2021 18:40:45 -0400 Subject: [PATCH 191/548] Fix `upper_case_acronyms` lint --- sync-server/src/main.rs | 4 +- sync-server/src/storage/kv.rs | 22 +++++----- sync-server/src/storage/mod.rs | 2 +- taskchampion/src/errors.rs | 2 +- taskchampion/src/replica.rs | 8 ++-- taskchampion/src/storage/config.rs | 4 +- taskchampion/src/storage/kv.rs | 42 +++++++++---------- taskchampion/src/storage/mod.rs | 4 +- taskchampion/src/storage/operation.rs | 10 ++--- taskchampion/src/taskdb.rs | 58 +++++++++++++-------------- 10 files changed, 78 insertions(+), 78 deletions(-) diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 296a18aa2..2ae65ae5c 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,6 +1,6 @@ #![deny(clippy::all)] -use crate::storage::{KVStorage, Storage}; +use crate::storage::{KvStorage, Storage}; use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder, Scope}; use api::{api_scope, ServerState}; use clap::Arg; @@ -56,7 +56,7 @@ async fn main() -> anyhow::Result<()> { let data_dir = matches.value_of("data-dir").unwrap(); let port = matches.value_of("port").unwrap(); - let server_box: Box = Box::new(KVStorage::new(data_dir)?); + let server_box: Box = Box::new(KvStorage::new(data_dir)?); let server_state = ServerState::new(server_box); log::warn!("Serving on port {}", port); diff --git a/sync-server/src/storage/kv.rs b/sync-server/src/storage/kv.rs index 41f441844..89a4d3380 100644 --- a/sync-server/src/storage/kv.rs +++ b/sync-server/src/storage/kv.rs @@ -20,15 +20,15 @@ fn client_db_key(client_key: Uuid) -> ClientDbKey { *client_key.as_bytes() } -/// KVStorage is an on-disk storage backend which uses LMDB via the `kv` crate. -pub(crate) struct KVStorage<'t> { +/// KvStorage is an on-disk storage backend which uses LMDB via the `kv` crate. +pub(crate) struct KvStorage<'t> { store: Store, clients_bucket: Bucket<'t, ClientDbKey, ValueBuf>>, versions_bucket: Bucket<'t, VersionDbKey, ValueBuf>>, } -impl<'t> KVStorage<'t> { - pub fn new>(directory: P) -> anyhow::Result> { +impl<'t> KvStorage<'t> { + pub fn new>(directory: P) -> anyhow::Result> { let mut config = Config::default(directory); config.bucket("clients", None); config.bucket("versions", None); @@ -40,7 +40,7 @@ impl<'t> KVStorage<'t> { let versions_bucket = store.bucket::>>(Some("versions"))?; - Ok(KVStorage { + Ok(KvStorage { store, clients_bucket, versions_bucket, @@ -48,7 +48,7 @@ impl<'t> KVStorage<'t> { } } -impl<'t> Storage for KVStorage<'t> { +impl<'t> Storage for KvStorage<'t> { fn txn<'a>(&'a self) -> anyhow::Result> { Ok(Box::new(Txn { storage: self, @@ -58,7 +58,7 @@ impl<'t> Storage for KVStorage<'t> { } struct Txn<'t> { - storage: &'t KVStorage<'t>, + storage: &'t KvStorage<'t>, txn: Option>, } @@ -169,7 +169,7 @@ mod test { #[test] fn test_get_client_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let storage = KVStorage::new(&tmp_dir.path())?; + let storage = KvStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; let maybe_client = txn.get_client(Uuid::new_v4())?; assert!(maybe_client.is_none()); @@ -179,7 +179,7 @@ mod test { #[test] fn test_client_storage() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let storage = KVStorage::new(&tmp_dir.path())?; + let storage = KvStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -201,7 +201,7 @@ mod test { #[test] fn test_gvbp_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let storage = KVStorage::new(&tmp_dir.path())?; + let storage = KvStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; let maybe_version = txn.get_version_by_parent(Uuid::new_v4(), Uuid::new_v4())?; assert!(maybe_version.is_none()); @@ -211,7 +211,7 @@ mod test { #[test] fn test_add_version_and_gvbp() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let storage = KVStorage::new(&tmp_dir.path())?; + let storage = KvStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index e0e13ae93..1d1cbe139 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -7,7 +7,7 @@ mod inmemory; pub(crate) use inmemory::InMemoryStorage; mod kv; -pub(crate) use self::kv::KVStorage; +pub(crate) use self::kv::KvStorage; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub(crate) struct Client { diff --git a/taskchampion/src/errors.rs b/taskchampion/src/errors.rs index 801eb5926..9e2a712a5 100644 --- a/taskchampion/src/errors.rs +++ b/taskchampion/src/errors.rs @@ -2,5 +2,5 @@ use thiserror::Error; #[derive(Debug, Error, Eq, PartialEq, Clone)] pub enum Error { #[error("Task Database Error: {}", _0)] - DBError(String), + DbError(String), } diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index f223e55aa..ac1baea88 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -2,7 +2,7 @@ use crate::errors::Error; use crate::server::Server; use crate::storage::{Operation, Storage, TaskMap}; use crate::task::{Status, Task}; -use crate::taskdb::TaskDB; +use crate::taskdb::TaskDb; use crate::workingset::WorkingSet; use chrono::Utc; use log::trace; @@ -24,13 +24,13 @@ use uuid::Uuid; /// pending tasks are automatically added to the working set, and the working set is "renumbered" /// during the garbage-collection process. pub struct Replica { - taskdb: TaskDB, + taskdb: TaskDb, } impl Replica { pub fn new(storage: Box) -> Replica { Replica { - taskdb: TaskDB::new(storage), + taskdb: TaskDb::new(storage), } } @@ -112,7 +112,7 @@ impl Replica { // check that it already exists; this is a convenience check, as the task may already exist // when this Create operation is finally sync'd with operations from other replicas if self.taskdb.get_task(uuid)?.is_none() { - return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); + return Err(Error::DbError(format!("Task {} does not exist", uuid)).into()); } self.taskdb.apply(Operation::Delete { uuid })?; trace!("task {} deleted", uuid); diff --git a/taskchampion/src/storage/config.rs b/taskchampion/src/storage/config.rs index c87ae007d..773baa035 100644 --- a/taskchampion/src/storage/config.rs +++ b/taskchampion/src/storage/config.rs @@ -1,4 +1,4 @@ -use super::{InMemoryStorage, KVStorage, Storage}; +use super::{InMemoryStorage, KvStorage, Storage}; use std::path::PathBuf; /// The configuration required for a replica's storage. @@ -15,7 +15,7 @@ pub enum StorageConfig { impl StorageConfig { pub fn into_storage(self) -> anyhow::Result> { Ok(match self { - StorageConfig::OnDisk { taskdb_dir } => Box::new(KVStorage::new(taskdb_dir)?), + StorageConfig::OnDisk { taskdb_dir } => Box::new(KvStorage::new(taskdb_dir)?), StorageConfig::InMemory => Box::new(InMemoryStorage::new()), }) } diff --git a/taskchampion/src/storage/kv.rs b/taskchampion/src/storage/kv.rs index a08b6b284..c33e78e72 100644 --- a/taskchampion/src/storage/kv.rs +++ b/taskchampion/src/storage/kv.rs @@ -5,8 +5,8 @@ use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; use std::path::Path; use uuid::Uuid; -/// KVStorage is an on-disk storage backend which uses LMDB via the `kv` crate. -pub struct KVStorage<'t> { +/// KvStorage is an on-disk storage backend which uses LMDB via the `kv` crate. +pub struct KvStorage<'t> { store: Store, tasks_bucket: Bucket<'t, Key, ValueBuf>>, numbers_bucket: Bucket<'t, Integer, ValueBuf>>, @@ -19,8 +19,8 @@ const BASE_VERSION: u64 = 1; const NEXT_OPERATION: u64 = 2; const NEXT_WORKING_SET_INDEX: u64 = 3; -impl<'t> KVStorage<'t> { - pub fn new>(directory: P) -> anyhow::Result> { +impl<'t> KvStorage<'t> { + pub fn new>(directory: P) -> anyhow::Result> { let mut config = Config::default(directory); config.bucket("tasks", None); config.bucket("numbers", None); @@ -48,7 +48,7 @@ impl<'t> KVStorage<'t> { let working_set_bucket = store.int_bucket::>>(Some("working_set"))?; - Ok(KVStorage { + Ok(KvStorage { store, tasks_bucket, numbers_bucket, @@ -59,7 +59,7 @@ impl<'t> KVStorage<'t> { } } -impl<'t> Storage for KVStorage<'t> { +impl<'t> Storage for KvStorage<'t> { fn txn<'a>(&'a mut self) -> anyhow::Result> { Ok(Box::new(Txn { storage: self, @@ -69,7 +69,7 @@ impl<'t> Storage for KVStorage<'t> { } struct Txn<'t> { - storage: &'t KVStorage<'t>, + storage: &'t KvStorage<'t>, txn: Option>, } @@ -359,7 +359,7 @@ mod test { #[test] fn test_create() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -377,7 +377,7 @@ mod test { #[test] fn test_create_exists() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -395,7 +395,7 @@ mod test { #[test] fn test_get_missing() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -408,7 +408,7 @@ mod test { #[test] fn test_set_task() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -429,7 +429,7 @@ mod test { #[test] fn test_delete_task_missing() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -441,7 +441,7 @@ mod test { #[test] fn test_delete_task_exists() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -458,7 +458,7 @@ mod test { #[test] fn test_all_tasks_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; { let mut txn = storage.txn()?; let tasks = txn.all_tasks()?; @@ -470,7 +470,7 @@ mod test { #[test] fn test_all_tasks_and_uuids() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); { @@ -524,7 +524,7 @@ mod test { #[test] fn test_base_version_default() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; { let mut txn = storage.txn()?; assert_eq!(txn.base_version()?, DEFAULT_BASE_VERSION); @@ -535,7 +535,7 @@ mod test { #[test] fn test_base_version_setting() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let u = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -552,7 +552,7 @@ mod test { #[test] fn test_operations() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); let uuid3 = Uuid::new_v4(); @@ -616,7 +616,7 @@ mod test { #[test] fn get_working_set_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; { let mut txn = storage.txn()?; @@ -630,7 +630,7 @@ mod test { #[test] fn add_to_working_set() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -653,7 +653,7 @@ mod test { #[test] fn clear_working_set() -> anyhow::Result<()> { let tmp_dir = TempDir::new("test")?; - let mut storage = KVStorage::new(&tmp_dir.path())?; + let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index c5ffb8cc2..b263fd56f 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -14,7 +14,7 @@ mod inmemory; mod kv; mod operation; -pub use self::kv::KVStorage; +pub use self::kv::KvStorage; pub use config::StorageConfig; pub use inmemory::InMemoryStorage; @@ -83,7 +83,7 @@ pub trait StorageTxn { fn operations(&mut self) -> Result>; /// Add an operation to the end of the list of operations in the storage. Note that this - /// merely *stores* the operation; it is up to the TaskDB to apply it. + /// merely *stores* the operation; it is up to the TaskDb to apply it. fn add_operation(&mut self, op: Operation) -> Result<()>; /// Replace the current list of operations with a new list. diff --git a/taskchampion/src/storage/operation.rs b/taskchampion/src/storage/operation.rs index fd88e17f9..ef52f2cb4 100644 --- a/taskchampion/src/storage/operation.rs +++ b/taskchampion/src/storage/operation.rs @@ -126,7 +126,7 @@ impl Operation { mod test { use super::*; use crate::storage::InMemoryStorage; - use crate::taskdb::TaskDB; + use crate::taskdb::TaskDb; use chrono::{Duration, Utc}; use proptest::prelude::*; @@ -145,7 +145,7 @@ mod test { // check that the two operation sequences have the same effect, enforcing the invariant of // the transform function. - let mut db1 = TaskDB::new_inmemory(); + let mut db1 = TaskDb::new_inmemory(); if let Some(ref o) = setup { db1.apply(o.clone()).unwrap(); } @@ -154,7 +154,7 @@ mod test { db1.apply(o).unwrap(); } - let mut db2 = TaskDB::new_inmemory(); + let mut db2 = TaskDb::new_inmemory(); if let Some(ref o) = setup { db2.apply(o.clone()).unwrap(); } @@ -307,8 +307,8 @@ mod test { fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); - let mut db1 = TaskDB::new(Box::new(InMemoryStorage::new())); - let mut db2 = TaskDB::new(Box::new(InMemoryStorage::new())); + let mut db1 = TaskDb::new(Box::new(InMemoryStorage::new())); + let mut db2 = TaskDb::new(Box::new(InMemoryStorage::new())); // Ensure that any expected tasks already exist if let Operation::Update{ ref uuid, .. } = o1 { diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index ed728b8e6..f4850f487 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -7,10 +7,10 @@ use std::collections::HashSet; use std::str; use uuid::Uuid; -/// A TaskDB is the backend for a replica. It manages the storage, operations, synchronization, +/// A TaskDb is the backend for a replica. It manages the storage, operations, synchronization, /// and so on, and all the invariants that come with it. It leaves the meaning of particular task /// properties to the replica and task implementations. -pub struct TaskDB { +pub struct TaskDb { storage: Box, } @@ -19,24 +19,24 @@ struct Version { operations: Vec, } -impl TaskDB { - /// Create a new TaskDB with the given backend storage - pub fn new(storage: Box) -> TaskDB { - TaskDB { storage } +impl TaskDb { + /// Create a new TaskDb with the given backend storage + pub fn new(storage: Box) -> TaskDb { + TaskDb { storage } } #[cfg(test)] - pub fn new_inmemory() -> TaskDB { - TaskDB::new(Box::new(crate::storage::InMemoryStorage::new())) + pub fn new_inmemory() -> TaskDb { + TaskDb::new(Box::new(crate::storage::InMemoryStorage::new())) } - /// Apply an operation to the TaskDB. Aside from synchronization operations, this is the only way - /// to modify the TaskDB. In cases where an operation does not make sense, this function will do - /// nothing and return an error (but leave the TaskDB in a consistent state). + /// Apply an operation to the TaskDb. Aside from synchronization operations, this is the only way + /// to modify the TaskDb. In cases where an operation does not make sense, this function will do + /// nothing and return an error (but leave the TaskDb in a consistent state). pub fn apply(&mut self, op: Operation) -> anyhow::Result<()> { // TODO: differentiate error types here? let mut txn = self.storage.txn()?; - if let err @ Err(_) = TaskDB::apply_op(txn.as_mut(), &op) { + if let err @ Err(_) = TaskDb::apply_op(txn.as_mut(), &op) { return err; } txn.add_operation(op)?; @@ -49,12 +49,12 @@ impl TaskDB { Operation::Create { uuid } => { // insert if the task does not already exist if !txn.create_task(*uuid)? { - return Err(Error::DBError(format!("Task {} already exists", uuid)).into()); + return Err(Error::DbError(format!("Task {} already exists", uuid)).into()); } } Operation::Delete { ref uuid } => { if !txn.delete_task(*uuid)? { - return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); + return Err(Error::DbError(format!("Task {} does not exist", uuid)).into()); } } Operation::Update { @@ -71,7 +71,7 @@ impl TaskDB { }; txn.set_task(*uuid, task)?; } else { - return Err(Error::DBError(format!("Task {} does not exist", uuid)).into()); + return Err(Error::DbError(format!("Task {} does not exist", uuid)).into()); } } } @@ -213,7 +213,7 @@ impl TaskDB { // apply this verison and update base_version in storage info!("applying version {:?} from server", version_id); - TaskDB::apply_version(txn.as_mut(), version)?; + TaskDb::apply_version(txn.as_mut(), version)?; txn.set_base_version(version_id)?; base_version_id = version_id; } else { @@ -313,7 +313,7 @@ impl TaskDB { } } if let Some(o) = svr_op { - if let Err(e) = TaskDB::apply_op(txn, &o) { + if let Err(e) = TaskDb::apply_op(txn, &o) { warn!("Invalid operation when syncing: {} (ignored)", e); } } @@ -367,7 +367,7 @@ mod tests { #[test] fn test_apply_create() { - let mut db = TaskDB::new_inmemory(); + let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; db.apply(op.clone()).unwrap(); @@ -378,7 +378,7 @@ mod tests { #[test] fn test_apply_create_exists() { - let mut db = TaskDB::new_inmemory(); + let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; db.apply(op.clone()).unwrap(); @@ -393,7 +393,7 @@ mod tests { #[test] fn test_apply_create_update() { - let mut db = TaskDB::new_inmemory(); + let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; db.apply(op1.clone()).unwrap(); @@ -414,7 +414,7 @@ mod tests { #[test] fn test_apply_create_update_delete_prop() { - let mut db = TaskDB::new_inmemory(); + let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; db.apply(op1.clone()).unwrap(); @@ -456,7 +456,7 @@ mod tests { #[test] fn test_apply_update_does_not_exist() { - let mut db = TaskDB::new_inmemory(); + let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Update { uuid, @@ -475,7 +475,7 @@ mod tests { #[test] fn test_apply_create_delete() { - let mut db = TaskDB::new_inmemory(); + let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; db.apply(op1.clone()).unwrap(); @@ -489,7 +489,7 @@ mod tests { #[test] fn test_apply_delete_not_present() { - let mut db = TaskDB::new_inmemory(); + let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); let op1 = Operation::Delete { uuid }; @@ -513,7 +513,7 @@ mod tests { } fn rebuild_working_set(renumber: bool) -> anyhow::Result<()> { - let mut db = TaskDB::new_inmemory(); + let mut db = TaskDb::new_inmemory(); let mut uuids = vec![]; uuids.push(Uuid::new_v4()); println!("uuids[0]: {:?} - pending, not in working set", uuids[0]); @@ -526,7 +526,7 @@ mod tests { uuids.push(Uuid::new_v4()); println!("uuids[4]: {:?} - pending, in working set", uuids[4]); - // add everything to the TaskDB + // add everything to the TaskDb for uuid in &uuids { db.apply(Operation::Create { uuid: *uuid })?; } @@ -598,8 +598,8 @@ mod tests { Ok(()) } - fn newdb() -> TaskDB { - TaskDB::new(Box::new(InMemoryStorage::new())) + fn newdb() -> TaskDb { + TaskDb::new(Box::new(InMemoryStorage::new())) } #[test] @@ -751,7 +751,7 @@ mod tests { #[test] // check that various sequences of operations on mulitple db's do not get the db's into an // incompatible state. The main concern here is that there might be a sequence of create - // and delete operations that results in a task existing in one TaskDB but not existing in + // and delete operations that results in a task existing in one TaskDb but not existing in // another. So, the generated sequences focus on a single task UUID. fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { let mut server: Box = Box::new(TestServer::new()); From bbceed41f942440c8f67d89b6a2da18790eac1e8 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 29 Mar 2021 19:25:12 -0400 Subject: [PATCH 192/548] fix needless_question_mark --- cli/src/invocation/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 5bce1a16d..feff61c27 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -132,7 +132,7 @@ fn get_server(settings: &Config) -> anyhow::Result> { log::debug!("Using local sync-server at `{:?}`", server_dir); ServerConfig::Local { server_dir } }; - Ok(config.into_server()?) + config.into_server() } /// Get a WriteColor implementation based on whether the output is a tty. From ff58711b2d7b53cad780f2eece450f5246e556b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Mar 2021 23:34:08 +0000 Subject: [PATCH 193/548] Bump predicates from 1.0.5 to 1.0.7 Bumps [predicates](https://github.com/assert-rs/predicates-rs) from 1.0.5 to 1.0.7. - [Release notes](https://github.com/assert-rs/predicates-rs/releases) - [Changelog](https://github.com/assert-rs/predicates-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/assert-rs/predicates-rs/compare/v1.0.5...v1.0.7) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- cli/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76c9c646e..a50925d8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1540,9 +1540,9 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "predicates" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bfead12e90dccead362d62bb2c90a5f6fc4584963645bc7f71a735e0b0735a" +checksum = "eeb433456c1a57cc93554dea3ce40b4c19c4057e41c55d4a0f3d84ea71c325aa" dependencies = [ "difference", "float-cmp", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index cfd501b9b..50d72db61 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -25,5 +25,5 @@ path = "../taskchampion" [dev-dependencies] assert_cmd = "^1.0.1" -predicates = "^1.0.5" +predicates = "^1.0.7" tempdir = "^0.3.7" From 357bbcad0138c958b7706845cfd766a1810ef537 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Mar 2021 23:34:12 +0000 Subject: [PATCH 194/548] Bump uuid from 0.8.1 to 0.8.2 Bumps [uuid](https://github.com/uuid-rs/uuid) from 0.8.1 to 0.8.2. - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/0.8.1...0.8.2) Signed-off-by: dependabot[bot] --- Cargo.lock | 6 +++--- sync-server/Cargo.toml | 2 +- taskchampion/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76c9c646e..9a4fa5631 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2614,11 +2614,11 @@ dependencies = [ [[package]] name = "uuid" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "rand 0.7.3", + "getrandom 0.2.2", "serde", ] diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 2137ae6e4..266ea739e 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -uuid = { version = "^0.8.1", features = ["serde", "v4"] } +uuid = { version = "^0.8.2", features = ["serde", "v4"] } actix-web = "^3.3.0" anyhow = "1.0" futures = "^0.3.8" diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 19719a62e..f561afde2 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" edition = "2018" [dependencies] -uuid = { version = "^0.8.1", features = ["serde", "v4"] } +uuid = { version = "^0.8.2", features = ["serde", "v4"] } serde = "^1.0.125" serde_json = "^1.0" chrono = { version = "^0.4.10", features = ["serde"] } From def67c81b6b5f9a94d4a6dffd938574274623403 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Mar 2021 23:45:28 +0000 Subject: [PATCH 195/548] Bump assert_cmd from 1.0.2 to 1.0.3 Bumps [assert_cmd](https://github.com/assert-rs/assert_cmd) from 1.0.2 to 1.0.3. - [Release notes](https://github.com/assert-rs/assert_cmd/releases) - [Changelog](https://github.com/assert-rs/assert_cmd/blob/master/CHANGELOG.md) - [Commits](https://github.com/assert-rs/assert_cmd/compare/v1.0.2...v1.0.3) Signed-off-by: dependabot[bot] --- Cargo.lock | 5 +++-- cli/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f3a94248..16238f3f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,10 +314,11 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "assert_cmd" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc1679af9a1ab4bea16f228b05d18f8363f8327b1fa8db00d2760cfafc6b61e" +checksum = "f2475b58cd94eb4f70159f4fd8844ba3b807532fe3131b3373fae060bbe30396" dependencies = [ + "bstr", "doc-comment", "predicates", "predicates-core", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 50d72db61..bdee16c53 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -24,6 +24,6 @@ version = "^0.10.1" path = "../taskchampion" [dev-dependencies] -assert_cmd = "^1.0.1" +assert_cmd = "^1.0.3" predicates = "^1.0.7" tempdir = "^0.3.7" From 695785116d0eda7b2a9ec185cef3a0ce868bc867 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Mar 2021 07:43:11 +0000 Subject: [PATCH 196/548] Bump actix-rt from 1.1.1 to 2.2.0 Bumps [actix-rt](https://github.com/actix/actix-net) from 1.1.1 to 2.2.0. - [Release notes](https://github.com/actix/actix-net/releases) - [Commits](https://github.com/actix/actix-net/compare/rt-1.1.1...rt-v2.2.0) Signed-off-by: dependabot[bot] --- Cargo.lock | 114 ++++++++++++++++++++++++++++++++--------- sync-server/Cargo.toml | 2 +- 2 files changed, 92 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c06f918d..a16fadd4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,7 @@ dependencies = [ "futures-sink", "log", "pin-project 0.4.27", - "tokio", + "tokio 0.2.23", "tokio-util", ] @@ -23,7 +23,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" dependencies = [ "actix-codec", - "actix-rt", + "actix-rt 1.1.1", "actix-service", "actix-utils", "derive_more", @@ -43,7 +43,7 @@ checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" dependencies = [ "actix-codec", "actix-connect", - "actix-rt", + "actix-rt 1.1.1", "actix-service", "actix-threadpool", "actix-utils", @@ -92,6 +92,16 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb2b608f0accc2f5bcf3dd872194ce13d94ee45b571487035864cf966b04ef" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "actix-router" version = "0.2.5" @@ -111,13 +121,24 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" dependencies = [ - "actix-macros", + "actix-macros 0.1.2", "actix-threadpool", "copyless", "futures-channel", "futures-util", "smallvec", - "tokio", + "tokio 0.2.23", +] + +[[package]] +name = "actix-rt" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d7cd957c9ed92288a7c3c96af81fa5291f65247a76a34dac7b6af74e52ba0" +dependencies = [ + "actix-macros 0.2.0", + "futures-core", + "tokio 1.4.0", ] [[package]] @@ -127,13 +148,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" dependencies = [ "actix-codec", - "actix-rt", + "actix-rt 1.1.1", "actix-service", "actix-utils", "futures-channel", "futures-util", "log", - "mio", + "mio 0.6.22", "mio-uds", "num_cpus", "slab", @@ -156,8 +177,8 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" dependencies = [ - "actix-macros", - "actix-rt", + "actix-macros 0.1.2", + "actix-rt 1.1.1", "actix-server", "actix-service", "log", @@ -198,7 +219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" dependencies = [ "actix-codec", - "actix-rt", + "actix-rt 1.1.1", "actix-service", "bitflags", "bytes", @@ -219,9 +240,9 @@ checksum = "3a89a7b133e734f6d1e555502d450408ae04105826aef7e3605019747d3ac732" dependencies = [ "actix-codec", "actix-http", - "actix-macros", + "actix-macros 0.1.2", "actix-router", - "actix-rt", + "actix-rt 1.1.1", "actix-server", "actix-service", "actix-testing", @@ -362,7 +383,7 @@ checksum = "e9056f5e27b0d56bedd82f78eceaba0bcddcbbcbbefae3cd0a53994b28c96ff5" dependencies = [ "actix-codec", "actix-http", - "actix-rt", + "actix-rt 1.1.1", "actix-service", "base64", "bytes", @@ -1003,7 +1024,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio", + "tokio 0.2.23", "tokio-util", "tracing", "tracing-futures", @@ -1183,9 +1204,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.80" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" [[package]] name = "linked-hash-map" @@ -1290,12 +1311,25 @@ dependencies = [ "kernel32-sys", "libc", "log", - "miow", + "miow 0.2.2", "net2", "slab", "winapi 0.2.8", ] +[[package]] +name = "mio" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +dependencies = [ + "libc", + "log", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + [[package]] name = "mio-uds" version = "0.6.8" @@ -1304,7 +1338,7 @@ checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" dependencies = [ "iovec", "libc", - "mio", + "mio 0.6.22", ] [[package]] @@ -1319,6 +1353,15 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "net2" version = "0.2.36" @@ -1359,6 +1402,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -2161,7 +2213,7 @@ dependencies = [ name = "taskchampion-sync-server" version = "0.3.0" dependencies = [ - "actix-rt", + "actix-rt 2.2.0", "actix-web", "anyhow", "clap", @@ -2373,7 +2425,7 @@ dependencies = [ "lazy_static", "libc", "memchr", - "mio", + "mio 0.6.22", "mio-uds", "pin-project-lite 0.1.11", "signal-hook-registry", @@ -2381,6 +2433,22 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tokio" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" +dependencies = [ + "autocfg", + "libc", + "mio 0.7.11", + "once_cell", + "parking_lot", + "pin-project-lite 0.2.0", + "signal-hook-registry", + "winapi 0.3.9", +] + [[package]] name = "tokio-util" version = "0.3.1" @@ -2392,7 +2460,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite 0.1.11", - "tokio", + "tokio 0.2.23", ] [[package]] @@ -2457,7 +2525,7 @@ dependencies = [ "rand 0.7.3", "smallvec", "thiserror", - "tokio", + "tokio 0.2.23", "url", ] @@ -2477,7 +2545,7 @@ dependencies = [ "resolv-conf", "smallvec", "thiserror", - "tokio", + "tokio 0.2.23", "trust-dns-proto", ] diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 266ea739e..48f25d869 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -18,5 +18,5 @@ log = "^0.4.14" env_logger = "^0.8.3" [dev-dependencies] -actix-rt = "^1.1.1" +actix-rt = "^2.2.0" tempdir = "^0.3.7" From c89b646fdcb2a3660606302f198ca7a11c563000 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Mar 2021 07:43:17 +0000 Subject: [PATCH 197/548] Bump thiserror from 1.0.22 to 1.0.24 Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.22 to 1.0.24. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.22...1.0.24) Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c06f918d..46be2511c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2249,18 +2249,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" dependencies = [ "proc-macro2", "quote", From af39be175c8b13b360a9c557a48e2602df56f903 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Mar 2021 07:43:23 +0000 Subject: [PATCH 198/548] Bump lmdb-rkv from 0.12.3 to 0.14.0 Bumps [lmdb-rkv](https://github.com/mozilla/lmdb-rs) from 0.12.3 to 0.14.0. - [Release notes](https://github.com/mozilla/lmdb-rs/releases) - [Commits](https://github.com/mozilla/lmdb-rs/compare/0.12.3...0.14.0) Signed-off-by: dependabot[bot] --- Cargo.lock | 29 ++++++++++++++++++++++++++--- taskchampion/Cargo.toml | 2 +- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c06f918d..b107cb228 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1149,7 +1149,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" dependencies = [ - "lmdb-rkv", + "lmdb-rkv 0.12.3", "rmp-serde", "serde", "thiserror", @@ -1202,7 +1202,19 @@ dependencies = [ "bitflags", "byteorder", "libc", - "lmdb-rkv-sys", + "lmdb-rkv-sys 0.9.6", +] + +[[package]] +name = "lmdb-rkv" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447a296f7aca299cfbb50f4e4f3d49451549af655fb7215d7f8c0c3d64bad42b" +dependencies = [ + "bitflags", + "byteorder", + "libc", + "lmdb-rkv-sys 0.11.0", ] [[package]] @@ -1216,6 +1228,17 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lmdb-rkv-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b27470ac25167b3afdfb6af8fcd3bc1be67de50ffbdaf4073378cfded6ae24a5" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "lock_api" version = "0.4.2" @@ -2125,7 +2148,7 @@ dependencies = [ "anyhow", "chrono", "kv", - "lmdb-rkv", + "lmdb-rkv 0.14.0", "log", "proptest", "serde", diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 3198e816d..c2f539bcd 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -18,7 +18,7 @@ chrono = { version = "^0.4.10", features = ["serde"] } anyhow = "1.0" thiserror = "1.0" kv = {version = "^0.10.0", features = ["msgpack-value"]} -lmdb-rkv = {version = "^0.12.3"} +lmdb-rkv = {version = "^0.14.0"} ureq = "^2.1.0" log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } From cc7f522e2a618c5b8a3e8bb93c37336138acc27d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Mar 2021 07:43:37 +0000 Subject: [PATCH 199/548] Bump nom from 6.0.1 to 6.1.2 Bumps [nom](https://github.com/Geal/nom) from 6.0.1 to 6.1.2. - [Release notes](https://github.com/Geal/nom/releases) - [Changelog](https://github.com/Geal/nom/blob/master/CHANGELOG.md) - [Commits](https://github.com/Geal/nom/compare/6.0.1...6.1.2) Signed-off-by: dependabot[bot] --- Cargo.lock | 11 ++++++----- cli/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c06f918d..68926e41c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -843,9 +843,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "funty" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" [[package]] name = "futures" @@ -1343,11 +1343,12 @@ dependencies = [ [[package]] name = "nom" -version = "6.0.1" +version = "6.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88034cfd6b4a0d54dd14f4a507eceee36c0b70e5a02236c4e4df571102be17f0" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" dependencies = [ "bitvec", + "funty", "lexical-core", "memchr", "version_check", @@ -2148,7 +2149,7 @@ dependencies = [ "dirs 3.0.1", "env_logger", "log", - "nom 6.0.1", + "nom 6.1.2", "predicates", "prettytable-rs", "taskchampion", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index bdee16c53..fe490698f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,7 +9,7 @@ dirs = "^3.0.1" env_logger = "^0.8.3" anyhow = "1.0" log = "^0.4.14" -nom = "^6.0.1" +nom = "^6.1.2" prettytable-rs = "^0.8.0" textwrap = { version="^0.12.1", features=["terminal_size"] } termcolor = "^1.1.2" From 82fc8dcd97f61254eeb8e16d2b3b12bdb86ebece Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Mar 2021 12:09:05 +0000 Subject: [PATCH 200/548] Bump config from 0.10.1 to 0.11.0 Bumps [config](https://github.com/mehcode/config-rs) from 0.10.1 to 0.11.0. - [Release notes](https://github.com/mehcode/config-rs/releases) - [Changelog](https://github.com/mehcode/config-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/mehcode/config-rs/commits/0.11.0) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- cli/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee023b427..edb9b226e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "config" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ "lazy_static", "nom 5.1.2", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index fe490698f..5d49dd318 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -18,7 +18,7 @@ atty = "^0.2.14" [dependencies.config] default-features = false features = ["yaml"] -version = "^0.10.1" +version = "^0.11.0" [dependencies.taskchampion] path = "../taskchampion" From a21c85a8729928b969afe51782fe72c81ddcfc77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 07:30:05 +0000 Subject: [PATCH 201/548] Bump anyhow from 1.0.39 to 1.0.40 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.39 to 1.0.40. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.39...1.0.40) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edb9b226e..318d3c0b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" [[package]] name = "arrayref" From 5b28993be0c2e5c7d8895fcb9594ee0378a55745 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 07:30:16 +0000 Subject: [PATCH 202/548] Bump actix-web from 3.3.0 to 3.3.2 Bumps [actix-web](https://github.com/actix/actix-web) from 3.3.0 to 3.3.2. - [Release notes](https://github.com/actix/actix-web/releases) - [Changelog](https://github.com/actix/actix-web/blob/master/CHANGES.md) - [Commits](https://github.com/actix/actix-web/compare/web-v3.3.0...web-v3.3.2) Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- sync-server/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edb9b226e..bb288c220 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "3.3.0" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a89a7b133e734f6d1e555502d450408ae04105826aef7e3605019747d3ac732" +checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" dependencies = [ "actix-codec", "actix-http", @@ -377,9 +377,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "awc" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9056f5e27b0d56bedd82f78eceaba0bcddcbbcbbefae3cd0a53994b28c96ff5" +checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691" dependencies = [ "actix-codec", "actix-http", diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 48f25d869..6ea4ed4ff 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" [dependencies] uuid = { version = "^0.8.2", features = ["serde", "v4"] } -actix-web = "^3.3.0" +actix-web = "^3.3.2" anyhow = "1.0" futures = "^0.3.8" serde = "^1.0.125" From e69efe79d0f501dc445a433ddbdc490aec0a59ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 07:30:25 +0000 Subject: [PATCH 203/548] Bump textwrap from 0.12.1 to 0.13.4 Bumps [textwrap](https://github.com/mgeisler/textwrap) from 0.12.1 to 0.13.4. - [Release notes](https://github.com/mgeisler/textwrap/releases) - [Changelog](https://github.com/mgeisler/textwrap/blob/master/CHANGELOG.md) - [Commits](https://github.com/mgeisler/textwrap/compare/0.12.1...0.13.4) Signed-off-by: dependabot[bot] --- Cargo.lock | 13 ++++++++++--- cli/Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edb9b226e..a0c7c201e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2089,6 +2089,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "socket2" version = "0.3.17" @@ -2230,7 +2236,7 @@ dependencies = [ "taskchampion", "tempdir", "termcolor", - "textwrap 0.12.1", + "textwrap 0.13.4", ] [[package]] @@ -2315,10 +2321,11 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.12.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835" dependencies = [ + "smawk", "terminal_size", "unicode-width", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5d49dd318..e7820e368 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,7 +11,7 @@ anyhow = "1.0" log = "^0.4.14" nom = "^6.1.2" prettytable-rs = "^0.8.0" -textwrap = { version="^0.12.1", features=["terminal_size"] } +textwrap = { version="^0.13.4", features=["terminal_size"] } termcolor = "^1.1.2" atty = "^0.2.14" From 35ae3420b8e9ce134dda9884ca50bdb61698946a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 07:30:38 +0000 Subject: [PATCH 204/548] Bump serde_json from 1.0.59 to 1.0.64 Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.59 to 1.0.64. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.59...v1.0.64) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edb9b226e..874228352 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2028,9 +2028,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.59" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "itoa", "ryu", From c2349cf708fc5e758bb313a7d090f61f7b30e5bc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 3 Apr 2021 19:43:45 -0400 Subject: [PATCH 205/548] Update links to point to new org --- README.md | 6 ++---- taskchampion/Cargo.toml | 4 ++-- taskchampion/src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c0fbcfd13..6a0715ccb 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ TaskChampion is an open-source personal task-tracking application. Use it to keep track of what you need to do, with a quick command-line interface and flexible sorting and filtering. It is modeled on [TaskWarrior](https://taskwarrior.org), but not a drop-in replacement for that application. +See the [documentation](https://taskchampion.github.io/taskchampion/) for more! + ## Status TC is still under development. @@ -31,7 +33,3 @@ There are three crates here: * [taskchampion-cli](./cli) - the command-line binary * [taskchampion-sync-server](./sync-server) - the server against which `task sync` operates -## See Also - - * [Documentation](https://djmitche.github.io/taskchampion/) (NOTE: temporary url) - * [Progress on the first version](https://github.com/djmitche/taskwarrior-rust/projects/1) diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index c2f539bcd..e0c04769d 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -3,9 +3,9 @@ name = "taskchampion" version = "0.3.0" authors = ["Dustin J. Mitchell "] description = "Personal task-tracking" -homepage = "https://djmitche.github.io/taskchampion/" +homepage = "https://taskchampion.github.io/taskchampion/" documentation = "https://docs.rs/crate/taskchampion" -repository = "https://github.com/djmitche/taskchampion" +repository = "https://github.com/taskchampion/taskchampion" readme = "../README.md" license = "MIT" edition = "2018" diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 9a1cd4635..da61235d9 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -26,7 +26,7 @@ Users can define their own server impelementations. # See Also -See the [TaskChampion Book](http://djmitche.github.com/taskchampion) +See the [TaskChampion Book](http://taskchampion.github.com/taskchampion) for more information about the design and usage of the tool. */ From e4e40b3e5e1f5a8031b71f9a684cd3bf93ad4739 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 3 Apr 2021 19:51:25 -0400 Subject: [PATCH 206/548] Add actions These will be more easily maintained by others than a Taskcluster configuration. --- .github/workflows/publish-docs.yml | 27 ++++++++ .github/workflows/rust-tests.yml | 79 ++++++++++++++++++++++++ .taskcluster.yml | 98 ------------------------------ 3 files changed, 106 insertions(+), 98 deletions(-) create mode 100644 .github/workflows/publish-docs.yml create mode 100644 .github/workflows/rust-tests.yml delete mode 100644 .taskcluster.yml diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml new file mode 100644 index 000000000..933386615 --- /dev/null +++ b/.github/workflows/publish-docs.yml @@ -0,0 +1,27 @@ +name: taskchampion + +on: + push: + branches: + - main + +jobs: + mdbook-deploy: + runs-on: ubuntu-latest + needs: mdbook + + steps: + - uses: actions/checkout@v1 + + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: 'latest' + + - run: mdbook build docs + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/book diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml new file mode 100644 index 000000000..8d9eba149 --- /dev/null +++ b/.github/workflows/rust-tests.yml @@ -0,0 +1,79 @@ +name: taskchampion + +on: + push: + branches: + - main + pull_request: + types: [opened, reopened, synchronize] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - uses: actions-rs/cargo@v1.0.1 + with: + command: check + + - name: test + run: cargo test + + clippy: + runs-on: ubuntu-latest + needs: test + + steps: + - uses: actions/checkout@v1 + + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + + - run: rustup component add clippy + + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features + + mdbook: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: 'latest' + + - run: mdbook test docs + - run: mdbook build docs diff --git a/.taskcluster.yml b/.taskcluster.yml deleted file mode 100644 index 05b58d6c7..000000000 --- a/.taskcluster.yml +++ /dev/null @@ -1,98 +0,0 @@ -version: 1 -reporting: checks-v1 -policy: - pullRequests: public -tasks: - $if: 'tasks_for in ["github-push", "github-pull-request"]' - then: - $let: - run: - $if: 'tasks_for == "github-push"' - then: {$eval: 'event.ref == "refs/heads/main"'} - else: {$eval: 'event.action in ["opened", "reopened", "synchronize"]'} - repo_url: - $if: 'tasks_for == "github-push"' - then: ${event.repository.clone_url} - else: ${event.pull_request.head.repo.clone_url} - ref: - $if: 'tasks_for == "github-push"' - then: ${event.after} - else: ${event.pull_request.head.sha} - in: - - $if: run - then: - provisionerId: 'proj-misc' - workerType: 'ci' - deadline: {$fromNow: '1 hour'} - expires: {$fromNow: '1 day'} - payload: - maxRunTime: 3600 - image: rust:latest - command: - - /bin/bash - - '-c' - - >- - rustup component add rustfmt && - git clone ${repo_url} repo && - cd repo && - git config advice.detachedHead false && - git checkout ${ref} && - cargo test - metadata: - name: taskchampion-tests - description: Run tests for taskchampion - owner: dustin@v.igoro.us - source: ${repo_url} - - $if: run - then: - provisionerId: 'proj-misc' - workerType: 'ci' - deadline: {$fromNow: '1 hour'} - expires: {$fromNow: '1 day'} - payload: - maxRunTime: 3600 - image: rust:latest - command: - - /bin/bash - - '-c' - - >- - rustup component add rustfmt && - git clone ${repo_url} repo && - cd repo && - git config advice.detachedHead false && - git checkout ${ref} && - rustup component add clippy && - cargo clippy && - cargo fmt -- --check - metadata: - name: taskchampion-clippy - description: Run clippy and rustfmt for taskchampion - owner: dustin@v.igoro.us - source: ${repo_url} - - $if: run - then: - provisionerId: 'proj-misc' - workerType: 'ci' - deadline: {$fromNow: '1 hour'} - expires: {$fromNow: '1 day'} - payload: - maxRunTime: 3600 - image: rust:latest - command: - - /bin/bash - - '-c' - - >- - git clone ${repo_url} repo && - cd repo && - git config advice.detachedHead false && - git checkout ${ref} && - cd docs && - curl -L --compressed https://github.com/rust-lang/mdBook/releases/download/v0.4.4/mdbook-v0.4.4-x86_64-unknown-linux-gnu.tar.gz | gunzip -c | tar -xf - && - chmod +x mdbook && - ./mdbook test && - ./mdbook build - metadata: - name: taskchampion-book - description: Verify that the docs build with mdbook - owner: dustin@v.igoro.us - source: ${repo_url} From d20884281b28a3698a610b0f3d90d423bbae0b9e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 3 Apr 2021 20:16:12 -0400 Subject: [PATCH 207/548] add security audit --- .github/workflows/audit.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/audit.yml diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 000000000..28b5a01f2 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,20 @@ +name: Security audit + +on: + schedule: + - cron: '0 0 0 * *' + push: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + pull_request: + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/audit-check@issue-104 + with: + token: ${{ secrets.GITHUB_TOKEN }} + From 1b8672ffc93297ac3e3230ff467f13987b905298 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 11 Apr 2021 19:55:35 +0000 Subject: [PATCH 208/548] fix audit cron --- .github/workflows/audit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 28b5a01f2..eb7bef20c 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -2,7 +2,7 @@ name: Security audit on: schedule: - - cron: '0 0 0 * *' + - cron: '0 0 * * *' push: paths: - '**/Cargo.toml' From 8d15ce896f84aae6d940886a30acb6ee5949a4bc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 12 Apr 2021 11:16:24 -0400 Subject: [PATCH 209/548] try cargo audit at main --- .github/workflows/audit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index eb7bef20c..ed77c771e 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/audit-check@issue-104 + - uses: actions-rs/audit-check@main with: token: ${{ secrets.GITHUB_TOKEN }} From 10536f024d4fcd6742ccb59e4885306187f3e5fa Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 15 Apr 2021 16:45:22 -0400 Subject: [PATCH 210/548] use audit action from the README --- .github/workflows/audit.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index ed77c771e..778016544 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -14,7 +14,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/audit-check@main + - uses: actions-rs/audit-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - From 6c0f7a736ff6cfc12d1d8cb02dd0e84f4ec0e872 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 16 Apr 2021 09:21:59 -0400 Subject: [PATCH 211/548] Use `dirs-next` instead of `dirs` --- Cargo.lock | 50 +++++++++++++++++++++++++++++++-------------- cli/Cargo.toml | 2 +- cli/src/settings.rs | 4 ++-- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb6a8cde1..4f5ede44f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -721,27 +721,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", - "redox_users", + "redox_users 0.3.5", "winapi 0.3.9", ] [[package]] -name = "dirs" -version = "3.0.1" +name = "dirs-next" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "dirs-sys", + "cfg-if 1.0.0", + "dirs-sys-next", ] [[package]] -name = "dirs-sys" -version = "0.3.5" +name = "dirs-sys-next" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.0", "winapi 0.3.9", ] @@ -1503,7 +1504,7 @@ dependencies = [ "cloudabi", "instant", "libc", - "redox_syscall", + "redox_syscall 0.1.57", "smallvec", "winapi 0.3.9", ] @@ -1824,6 +1825,15 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_syscall" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.3.5" @@ -1831,10 +1841,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ "getrandom 0.1.15", - "redox_syscall", + "redox_syscall 0.1.57", "rust-argon2", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.2", + "redox_syscall 0.2.6", +] + [[package]] name = "regex" version = "1.4.2" @@ -2103,7 +2123,7 @@ checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", + "redox_syscall 0.1.57", "winapi 0.3.9", ] @@ -2227,7 +2247,7 @@ dependencies = [ "assert_cmd", "atty", "config", - "dirs 3.0.1", + "dirs-next", "env_logger", "log", "nom 6.1.2", @@ -2275,7 +2295,7 @@ dependencies = [ "cfg-if 0.1.10", "libc", "rand 0.7.3", - "redox_syscall", + "redox_syscall 0.1.57", "remove_dir_all", "winapi 0.3.9", ] @@ -2287,7 +2307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", - "dirs 1.0.5", + "dirs", "winapi 0.3.9", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e7820e368..bc881bc0f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -5,7 +5,7 @@ name = "taskchampion-cli" version = "0.3.0" [dependencies] -dirs = "^3.0.1" +dirs-next = "^2.0.0" env_logger = "^0.8.3" anyhow = "1.0" log = "^0.4.14" diff --git a/cli/src/settings.rs b/cli/src/settings.rs index 27c6a067f..de8c137f2 100644 --- a/cli/src/settings.rs +++ b/cli/src/settings.rs @@ -37,7 +37,7 @@ pub(crate) fn default_settings() -> anyhow::Result { let mut settings = Config::default(); // set up defaults - if let Some(dir) = dirs::data_local_dir() { + if let Some(dir) = dirs_next::data_local_dir() { let mut tc_dir = dir.clone(); tc_dir.push("taskchampion"); settings.set_default( @@ -71,7 +71,7 @@ pub(crate) fn read_settings() -> anyhow::Result { let config_file: File = config_file.into(); settings.merge(config_file.required(true))?; env::remove_var("TASKCHAMPION_CONFIG"); - } else if let Some(mut dir) = dirs::config_dir() { + } else if let Some(mut dir) = dirs_next::config_dir() { dir.push("taskchampion"); log::debug!("Loading configuration from {:?} (optional)", dir); let config_file: File = dir.into(); From 27c20074e3e1a54b30dd0f74a34e413f9cb4fcae Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 16 Apr 2021 19:22:08 -0400 Subject: [PATCH 212/548] run 'git audit fix' --- Cargo.lock | 598 +++++++++++++++++++++++++---------------------------- 1 file changed, 277 insertions(+), 321 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f5ede44f..9cfffd28d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,12 +7,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570" dependencies = [ "bitflags", - "bytes", + "bytes 0.5.6", "futures-core", "futures-sink", "log", - "pin-project 0.4.27", - "tokio 0.2.23", + "pin-project 0.4.28", + "tokio 0.2.25", "tokio-util", ] @@ -50,7 +50,7 @@ dependencies = [ "base64", "bitflags", "brotli2", - "bytes", + "bytes 0.5.6", "cookie", "copyless", "derive_more", @@ -71,7 +71,7 @@ dependencies = [ "log", "mime", "percent-encoding", - "pin-project 1.0.2", + "pin-project 1.0.7", "rand 0.7.3", "regex", "serde", @@ -79,14 +79,14 @@ dependencies = [ "serde_urlencoded", "sha-1", "slab", - "time 0.2.23", + "time 0.2.26", ] [[package]] name = "actix-macros" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a60f9ba7c4e6df97f3aacb14bb5c0cd7d98a49dcbaed0d7f292912ad9a6a3ed2" +checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655" dependencies = [ "quote", "syn", @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "actix-router" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd1f7dbda1645bf7da33554db60891755f6c01c1b2169e2f4c492098d30c235" +checksum = "2ad299af73649e1fc893e333ccf86f377751eb95ff875d095131574c6f43452c" dependencies = [ "bytestring", "http", @@ -121,13 +121,13 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" dependencies = [ - "actix-macros 0.1.2", + "actix-macros 0.1.3", "actix-threadpool", "copyless", "futures-channel", "futures-util", "smallvec", - "tokio 0.2.23", + "tokio 0.2.25", ] [[package]] @@ -138,7 +138,7 @@ checksum = "bc7d7cd957c9ed92288a7c3c96af81fa5291f65247a76a34dac7b6af74e52ba0" dependencies = [ "actix-macros 0.2.0", "futures-core", - "tokio 1.4.0", + "tokio 1.5.0", ] [[package]] @@ -154,7 +154,7 @@ dependencies = [ "futures-channel", "futures-util", "log", - "mio 0.6.22", + "mio 0.6.23", "mio-uds", "num_cpus", "slab", @@ -168,7 +168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" dependencies = [ "futures-util", - "pin-project 0.4.27", + "pin-project 0.4.28", ] [[package]] @@ -177,7 +177,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" dependencies = [ - "actix-macros 0.1.2", + "actix-macros 0.1.3", "actix-rt 1.1.1", "actix-server", "actix-service", @@ -222,13 +222,13 @@ dependencies = [ "actix-rt 1.1.1", "actix-service", "bitflags", - "bytes", + "bytes 0.5.6", "either", "futures-channel", "futures-sink", "futures-util", "log", - "pin-project 0.4.27", + "pin-project 0.4.28", "slab", ] @@ -240,7 +240,7 @@ checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" dependencies = [ "actix-codec", "actix-http", - "actix-macros 0.1.2", + "actix-macros 0.1.3", "actix-router", "actix-rt 1.1.1", "actix-server", @@ -251,7 +251,7 @@ dependencies = [ "actix-utils", "actix-web-codegen", "awc", - "bytes", + "bytes 0.5.6", "derive_more", "encoding_rs", "futures-channel", @@ -260,13 +260,13 @@ dependencies = [ "fxhash", "log", "mime", - "pin-project 1.0.2", + "pin-project 1.0.7", "regex", "serde", "serde_json", "serde_urlencoded", "socket2", - "time 0.2.23", + "time 0.2.26", "tinyvec", "url", ] @@ -282,20 +282,11 @@ dependencies = [ "syn", ] -[[package]] -name = "addr2line" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" -dependencies = [ - "gimli", -] - [[package]] name = "adler" -version = "0.2.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" @@ -349,9 +340,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.42" +version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +checksum = "589652ce7ccb335d1e7ecb3be145425702b290dbcb7029bbeaae263fc1d87b48" dependencies = [ "proc-macro2", "quote", @@ -386,7 +377,7 @@ dependencies = [ "actix-rt 1.1.1", "actix-service", "base64", - "bytes", + "bytes 0.5.6", "cfg-if 1.0.0", "derive_more", "futures-core", @@ -399,20 +390,6 @@ dependencies = [ "serde_urlencoded", ] -[[package]] -name = "backtrace" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598" -dependencies = [ - "addr2line", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base-x" version = "0.2.8" @@ -436,9 +413,9 @@ dependencies = [ [[package]] name = "bit-vec" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" @@ -448,9 +425,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitvec" -version = "0.19.4" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" dependencies = [ "funty", "radium", @@ -500,9 +477,9 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf" +checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" dependencies = [ "lazy_static", "memchr", @@ -512,15 +489,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.4.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" @@ -529,19 +506,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] -name = "bytestring" -version = "0.1.5" +name = "bytes" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + +[[package]] +name = "bytestring" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" dependencies = [ - "bytes", + "bytes 1.0.1", ] [[package]] name = "cc" -version = "1.0.65" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" [[package]] name = "cfg-if" @@ -565,15 +548,15 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time 0.1.44", + "time 0.1.43", "winapi 0.3.9", ] [[package]] name = "chunked_transfer" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7477065d45a8fe57167bf3cf8bcd3729b54cfcb81cca49bda2d038ea89ae82ca" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" [[package]] name = "clap" @@ -590,15 +573,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "cloudabi" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" -dependencies = [ - "bitflags", -] - [[package]] name = "config" version = "0.11.0" @@ -613,9 +587,9 @@ dependencies = [ [[package]] name = "const_fn" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" +checksum = "076a6803b0dacd6a88cfe64deba628b01533ff5ef265687e6938280c1afd0a28" [[package]] name = "constant_time_eq" @@ -624,13 +598,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] -name = "cookie" -version = "0.14.3" +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ "percent-encoding", - "time 0.2.23", + "time 0.2.26", "version_check", ] @@ -657,9 +637,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ "autocfg", "cfg-if 1.0.0", @@ -668,9 +648,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d58633299b24b515ac72a3f869f8b91306a3cec616a602843a383acd6f9e97" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", @@ -690,10 +670,11 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.11" +version = "0.99.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +checksum = "f82b1b72f1263f214c0f823371768776c4f5841b942c9883aa8e5ec584fd0ba6" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn", @@ -772,9 +753,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.26" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" dependencies = [ "cfg-if 1.0.0", ] @@ -806,9 +787,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ "cfg-if 1.0.0", "crc32fast", @@ -833,9 +814,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ "matches", "percent-encoding", @@ -871,9 +852,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" [[package]] name = "futures" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" +checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" dependencies = [ "futures-channel", "futures-core", @@ -886,9 +867,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" dependencies = [ "futures-core", "futures-sink", @@ -896,15 +877,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" +checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" [[package]] name = "futures-executor" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" +checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" dependencies = [ "futures-core", "futures-task", @@ -913,15 +894,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" +checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" [[package]] name = "futures-macro" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" +checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -931,24 +912,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" +checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" [[package]] name = "futures-task" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" -dependencies = [ - "once_cell", -] +checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" [[package]] name = "futures-util" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" +checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" dependencies = [ "futures-channel", "futures-core", @@ -957,7 +935,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project 1.0.2", + "pin-project-lite 0.2.6", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -985,11 +963,11 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] @@ -1002,22 +980,16 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.10.2+wasi-snapshot-preview1", ] -[[package]] -name = "gimli" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" - [[package]] name = "h2" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" dependencies = [ - "bytes", + "bytes 0.5.6", "fnv", "futures-core", "futures-sink", @@ -1025,7 +997,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 0.2.23", + "tokio 0.2.25", "tokio-util", "tracing", "tracing-futures", @@ -1039,18 +1011,18 @@ checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" [[package]] name = "heck" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" dependencies = [ "unicode-segmentation", ] [[package]] name = "hermit-abi" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] @@ -1068,32 +1040,32 @@ dependencies = [ [[package]] name = "http" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" dependencies = [ - "bytes", + "bytes 1.0.1", "fnv", "itoa", ] [[package]] name = "httparse" -version = "1.3.4" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437" [[package]] name = "humantime" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "idna" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", @@ -1102,9 +1074,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.6.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg", "hashbrown", @@ -1142,15 +1114,15 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "js-sys" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" +checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" dependencies = [ "wasm-bindgen", ] @@ -1192,28 +1164,28 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lexical-core" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" +checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374" dependencies = [ "arrayvec", "bitflags", - "cfg-if 0.1.10", + "cfg-if 1.0.0", "ryu", "static_assertions", ] [[package]] name = "libc" -version = "0.2.91" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" [[package]] name = "linked-hash-map" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lmdb-rkv" @@ -1263,9 +1235,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" dependencies = [ "scopeguard", ] @@ -1314,9 +1286,9 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "miniz_oxide" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", "autocfg", @@ -1324,9 +1296,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.6.22" +version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" dependencies = [ "cfg-if 0.1.10", "fuchsia-zircon", @@ -1362,7 +1334,7 @@ checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" dependencies = [ "iovec", "libc", - "mio 0.6.22", + "mio 0.6.23", ] [[package]] @@ -1388,9 +1360,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.36" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" dependencies = [ "cfg-if 0.1.10", "libc", @@ -1465,17 +1437,11 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" - [[package]] name = "once_cell" -version = "1.5.2" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "opaque-debug" @@ -1496,15 +1462,14 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ - "cfg-if 0.1.10", - "cloudabi", + "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.1.57", + "redox_syscall 0.2.6", "smallvec", "winapi 0.3.9", ] @@ -1517,27 +1482,27 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +checksum = "918192b5c59119d51e0cd221f4d49dde9112824ba717369e903c97d076083d0f" dependencies = [ - "pin-project-internal 0.4.27", + "pin-project-internal 0.4.28", ] [[package]] name = "pin-project" -version = "1.0.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" +checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" dependencies = [ - "pin-project-internal 1.0.2", + "pin-project-internal 1.0.7", ] [[package]] name = "pin-project-internal" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +checksum = "3be26700300be6d9d23264c73211d8190e755b6b5ca7a1b28230025511b52a5e" dependencies = [ "proc-macro2", "quote", @@ -1546,9 +1511,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" +checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" dependencies = [ "proc-macro2", "quote", @@ -1557,15 +1522,15 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" [[package]] name = "pin-utils" @@ -1600,15 +1565,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" [[package]] name = "predicates-tree" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" +checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" dependencies = [ "predicates-core", "treeline", @@ -1636,15 +1601,15 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ "unicode-xid", ] @@ -1671,9 +1636,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.18.1" +version = "2.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da78e04bc0e40f36df43ecc6575e4f4b180e8156c4efd73f13d5619479b05696" +checksum = "1b7f4a129bb3754c25a4e04032a90173c68f85168f77118ac4cb4936e7f06f92" [[package]] name = "quick-error" @@ -1689,9 +1654,9 @@ checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" [[package]] name = "quote" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] @@ -1721,11 +1686,11 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom 0.1.15", + "getrandom 0.1.16", "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc", + "rand_hc 0.2.0", ] [[package]] @@ -1737,6 +1702,7 @@ dependencies = [ "libc", "rand_chacha 0.3.0", "rand_core 0.6.2", + "rand_hc 0.3.0", ] [[package]] @@ -1780,7 +1746,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom 0.1.15", + "getrandom 0.1.16", ] [[package]] @@ -1801,6 +1767,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core 0.6.2", +] + [[package]] name = "rand_xorshift" version = "0.3.0" @@ -1840,7 +1815,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ - "getrandom 0.1.15", + "getrandom 0.1.16", "redox_syscall 0.1.57", "rust-argon2", ] @@ -1857,14 +1832,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] [[package]] @@ -1878,9 +1852,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.21" +version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" [[package]] name = "remove_dir_all" @@ -1903,9 +1877,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.18" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70017ed5c555d79ee3538fc63ca09c70ad8f317dcadc1adc2c496b60c22bb24f" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", "libc", @@ -1918,9 +1892,9 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" +checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" dependencies = [ "byteorder", "num-traits", @@ -1949,12 +1923,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rustc-demangle" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" - [[package]] name = "rustc_version" version = "0.2.3" @@ -2003,9 +1971,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sct" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ "ring", "untrusted", @@ -2071,9 +2039,9 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" +checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" dependencies = [ "block-buffer", "cfg-if 1.0.0", @@ -2090,9 +2058,9 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" [[package]] name = "signal-hook-registry" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" dependencies = [ "libc", ] @@ -2105,9 +2073,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "smawk" @@ -2117,13 +2085,12 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "socket2" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.1.57", "winapi 0.3.9", ] @@ -2135,9 +2102,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "standback" -version = "0.2.13" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" dependencies = [ "version_check", ] @@ -2205,9 +2172,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" +checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" dependencies = [ "proc-macro2", "quote", @@ -2216,9 +2183,9 @@ dependencies = [ [[package]] name = "tap" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "taskchampion" @@ -2288,14 +2255,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "libc", - "rand 0.7.3", - "redox_syscall 0.1.57", + "rand 0.8.3", + "redox_syscall 0.2.6", "remove_dir_all", "winapi 0.3.9", ] @@ -2322,9 +2289,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1" +checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" dependencies = [ "libc", "winapi 0.3.9", @@ -2370,15 +2337,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -dependencies = [ - "lazy_static", -] - [[package]] name = "threadpool" version = "1.8.1" @@ -2390,20 +2348,19 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "wasi 0.10.0+wasi-snapshot-preview1", "winapi 0.3.9", ] [[package]] name = "time" -version = "0.2.23" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b" +checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372" dependencies = [ "const_fn", "libc", @@ -2451,9 +2408,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" dependencies = [ "tinyvec_macros", ] @@ -2466,19 +2423,19 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "0.2.23" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" dependencies = [ - "bytes", + "bytes 0.5.6", "futures-core", "iovec", "lazy_static", "libc", "memchr", - "mio 0.6.22", + "mio 0.6.23", "mio-uds", - "pin-project-lite 0.1.11", + "pin-project-lite 0.1.12", "signal-hook-registry", "slab", "winapi 0.3.9", @@ -2486,16 +2443,16 @@ dependencies = [ [[package]] name = "tokio" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" +checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ "autocfg", "libc", "mio 0.7.11", "once_cell", "parking_lot", - "pin-project-lite 0.2.0", + "pin-project-lite 0.2.6", "signal-hook-registry", "winapi 0.3.9", ] @@ -2506,32 +2463,32 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" dependencies = [ - "bytes", + "bytes 0.5.6", "futures-core", "futures-sink", "log", - "pin-project-lite 0.1.11", - "tokio 0.2.23", + "pin-project-lite 0.1.12", + "tokio 0.2.25", ] [[package]] name = "toml" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] [[package]] name = "tracing" -version = "0.1.22" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" +checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.0", + "pin-project-lite 0.2.6", "tracing-core", ] @@ -2546,11 +2503,11 @@ dependencies = [ [[package]] name = "tracing-futures" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 0.4.27", + "pin-project 1.0.7", "tracing", ] @@ -2562,12 +2519,12 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" [[package]] name = "trust-dns-proto" -version = "0.19.6" +version = "0.19.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53861fcb288a166aae4c508ae558ed18b53838db728d4d310aad08270a7d4c2b" +checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9" dependencies = [ "async-trait", - "backtrace", + "cfg-if 1.0.0", "enum-as-inner", "futures", "idna", @@ -2576,17 +2533,16 @@ dependencies = [ "rand 0.7.3", "smallvec", "thiserror", - "tokio 0.2.23", + "tokio 0.2.25", "url", ] [[package]] name = "trust-dns-resolver" -version = "0.19.6" +version = "0.19.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6759e8efc40465547b0dfce9500d733c65f969a4cbbfbe3ccf68daaa46ef179e" +checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb" dependencies = [ - "backtrace", "cfg-if 0.1.10", "futures", "ipconfig", @@ -2596,30 +2552,30 @@ dependencies = [ "resolv-conf", "smallvec", "thiserror", - "tokio 0.2.23", + "tokio 0.2.25", "trust-dns-proto", ] [[package]] name = "typenum" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" dependencies = [ "matches", ] [[package]] name = "unicode-normalization" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" +checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" dependencies = [ "tinyvec", ] @@ -2666,9 +2622,9 @@ dependencies = [ [[package]] name = "url" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" +checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" dependencies = [ "form_urlencoded", "idna", @@ -2694,9 +2650,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "wait-timeout" @@ -2715,25 +2671,25 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.68" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" +checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.68" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" +checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" dependencies = [ "bumpalo", "lazy_static", @@ -2746,9 +2702,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.68" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" +checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2756,9 +2712,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.68" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" +checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" dependencies = [ "proc-macro2", "quote", @@ -2769,15 +2725,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.68" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" +checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" [[package]] name = "web-sys" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" +checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" dependencies = [ "js-sys", "wasm-bindgen", @@ -2785,9 +2741,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" dependencies = [ "ring", "untrusted", @@ -2795,9 +2751,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ "webpki", ] @@ -2878,9 +2834,9 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" [[package]] name = "yaml-rust" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] From c8d6619d71d6ea4104589dbc4d3b6527dbc63768 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 16 Apr 2021 19:29:27 -0400 Subject: [PATCH 213/548] Replace tempfile with tempdir --- Cargo.lock | 59 ++------------------------------ cli/Cargo.toml | 2 +- cli/src/invocation/cmd/sync.rs | 4 +-- cli/src/invocation/test.rs | 2 +- sync-server/Cargo.toml | 2 +- sync-server/src/storage/kv.rs | 10 +++--- taskchampion/Cargo.toml | 2 +- taskchampion/src/server/local.rs | 10 +++--- taskchampion/src/storage/kv.rs | 30 ++++++++-------- 9 files changed, 34 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f5ede44f..9f48a47b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,12 +841,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -1702,19 +1696,6 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi 0.3.9", -] - [[package]] name = "rand" version = "0.7.3" @@ -1759,21 +1740,6 @@ dependencies = [ "rand_core 0.6.2", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.5.1" @@ -1810,15 +1776,6 @@ dependencies = [ "rand_core 0.6.2", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.1.57" @@ -2232,7 +2189,7 @@ dependencies = [ "proptest", "serde", "serde_json", - "tempdir", + "tempfile", "thiserror", "tindercrypt", "ureq", @@ -2254,7 +2211,7 @@ dependencies = [ "predicates", "prettytable-rs", "taskchampion", - "tempdir", + "tempfile", "termcolor", "textwrap 0.13.4", ] @@ -2272,20 +2229,10 @@ dependencies = [ "kv", "log", "serde", - "tempdir", + "tempfile", "uuid", ] -[[package]] -name = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -dependencies = [ - "rand 0.4.6", - "remove_dir_all", -] - [[package]] name = "tempfile" version = "3.1.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index bc881bc0f..570d106da 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -26,4 +26,4 @@ path = "../taskchampion" [dev-dependencies] assert_cmd = "^1.0.3" predicates = "^1.0.7" -tempdir = "^0.3.7" +tempfile = "3" diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index 9acc8e581..9a1bb18d2 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -15,13 +15,13 @@ pub(crate) fn execute( mod test { use super::*; use crate::invocation::test::*; - use tempdir::TempDir; + use tempfile::TempDir; #[test] fn test_add() { let mut w = test_writer(); let mut replica = test_replica(); - let server_dir = TempDir::new("test").unwrap(); + let server_dir = TempDir::new().unwrap(); let mut server = test_server(&server_dir); // Note that the details of the actual sync are tested thoroughly in the taskchampion crate diff --git a/cli/src/invocation/test.rs b/cli/src/invocation/test.rs index 5dcac753c..72f11e137 100644 --- a/cli/src/invocation/test.rs +++ b/cli/src/invocation/test.rs @@ -1,6 +1,6 @@ use std::io; use taskchampion::{storage, Replica, Server, ServerConfig}; -use tempdir::TempDir; +use tempfile::TempDir; pub(super) fn test_replica() -> Replica { let storage = storage::InMemoryStorage::new(); diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 6ea4ed4ff..2a42298be 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -19,4 +19,4 @@ env_logger = "^0.8.3" [dev-dependencies] actix-rt = "^2.2.0" -tempdir = "^0.3.7" +tempfile = "3" diff --git a/sync-server/src/storage/kv.rs b/sync-server/src/storage/kv.rs index 89a4d3380..bd5fa3502 100644 --- a/sync-server/src/storage/kv.rs +++ b/sync-server/src/storage/kv.rs @@ -164,11 +164,11 @@ impl<'t> StorageTxn for Txn<'t> { #[cfg(test)] mod test { use super::*; - use tempdir::TempDir; + use tempfile::TempDir; #[test] fn test_get_client_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let storage = KvStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; let maybe_client = txn.get_client(Uuid::new_v4())?; @@ -178,7 +178,7 @@ mod test { #[test] fn test_client_storage() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let storage = KvStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; @@ -200,7 +200,7 @@ mod test { #[test] fn test_gvbp_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let storage = KvStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; let maybe_version = txn.get_version_by_parent(Uuid::new_v4(), Uuid::new_v4())?; @@ -210,7 +210,7 @@ mod test { #[test] fn test_add_version_and_gvbp() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let storage = KvStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index e0c04769d..2505d1ef9 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -25,4 +25,4 @@ tindercrypt = { version = "^0.2.2", default-features = false } [dev-dependencies] proptest = "^1.0.0" -tempdir = "^0.3.7" +tempfile = "3" diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 84ee18fa8..bdd3c5dbd 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -151,11 +151,11 @@ impl<'t> Server for LocalServer<'t> { #[cfg(test)] mod test { use super::*; - use tempdir::TempDir; + use tempfile::TempDir; #[test] fn test_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut server = LocalServer::new(&tmp_dir.path())?; let child_version = server.get_child_version(NO_VERSION_ID)?; assert_eq!(child_version, GetVersionResult::NoSuchVersion); @@ -164,7 +164,7 @@ mod test { #[test] fn test_add_zero_base() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut server = LocalServer::new(&tmp_dir.path())?; let history = b"1234".to_vec(); match server.add_version(NO_VERSION_ID, history.clone())? { @@ -189,7 +189,7 @@ mod test { #[test] fn test_add_nonzero_base() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut server = LocalServer::new(&tmp_dir.path())?; let history = b"1234".to_vec(); let parent_version_id = Uuid::new_v4() as VersionId; @@ -217,7 +217,7 @@ mod test { #[test] fn test_add_nonzero_base_forbidden() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut server = LocalServer::new(&tmp_dir.path())?; let history = b"1234".to_vec(); let parent_version_id = Uuid::new_v4() as VersionId; diff --git a/taskchampion/src/storage/kv.rs b/taskchampion/src/storage/kv.rs index c33e78e72..e65e31dfa 100644 --- a/taskchampion/src/storage/kv.rs +++ b/taskchampion/src/storage/kv.rs @@ -354,11 +354,11 @@ impl<'t> StorageTxn for Txn<'t> { mod test { use super::*; use crate::storage::taskmap_with; - use tempdir::TempDir; + use tempfile::TempDir; #[test] fn test_create() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { @@ -376,7 +376,7 @@ mod test { #[test] fn test_create_exists() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { @@ -394,7 +394,7 @@ mod test { #[test] fn test_get_missing() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { @@ -407,7 +407,7 @@ mod test { #[test] fn test_set_task() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { @@ -428,7 +428,7 @@ mod test { #[test] fn test_delete_task_missing() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { @@ -440,7 +440,7 @@ mod test { #[test] fn test_delete_task_exists() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { @@ -457,7 +457,7 @@ mod test { #[test] fn test_all_tasks_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; { let mut txn = storage.txn()?; @@ -469,7 +469,7 @@ mod test { #[test] fn test_all_tasks_and_uuids() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -523,7 +523,7 @@ mod test { #[test] fn test_base_version_default() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; { let mut txn = storage.txn()?; @@ -534,7 +534,7 @@ mod test { #[test] fn test_base_version_setting() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let u = Uuid::new_v4(); { @@ -551,7 +551,7 @@ mod test { #[test] fn test_operations() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -615,7 +615,7 @@ mod test { #[test] fn get_working_set_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; { @@ -629,7 +629,7 @@ mod test { #[test] fn add_to_working_set() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -652,7 +652,7 @@ mod test { #[test] fn clear_working_set() -> anyhow::Result<()> { - let tmp_dir = TempDir::new("test")?; + let tmp_dir = TempDir::new()?; let mut storage = KvStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); From e40724b38195500f7f343b6d87c186973134db04 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 22 Apr 2021 11:48:05 +1000 Subject: [PATCH 214/548] Start of SQLite backed storage #131 --- Cargo.lock | 63 ++++ taskchampion/Cargo.toml | 1 + taskchampion/src/storage/mod.rs | 1 + taskchampion/src/storage/sqlite.rs | 495 +++++++++++++++++++++++++++++ 4 files changed, 560 insertions(+) create mode 100644 taskchampion/src/storage/sqlite.rs diff --git a/Cargo.lock b/Cargo.lock index 3d695cbfe..bdd220039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + [[package]] name = "aho-corasick" version = "0.7.15" @@ -785,6 +791,18 @@ dependencies = [ "termcolor", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "flate2" version = "1.0.20" @@ -1002,6 +1020,18 @@ name = "hashbrown" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" +dependencies = [ + "hashbrown", +] [[package]] name = "heck" @@ -1175,6 +1205,17 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" +[[package]] +name = "libsqlite3-sys" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19cb1effde5f834799ac5e5ef0e40d45027cd74f271b1de786ba8abb30e2164d" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -1868,6 +1909,21 @@ dependencies = [ "serde", ] +[[package]] +name = "rusqlite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc783b7ddae608338003bac1fa00b6786a75a9675fbd8e87243ecfdea3f6ed2" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -2154,6 +2210,7 @@ dependencies = [ "lmdb-rkv 0.14.0", "log", "proptest", + "rusqlite", "serde", "serde_json", "tempfile", @@ -2589,6 +2646,12 @@ dependencies = [ "serde", ] +[[package]] +name = "vcpkg" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 2505d1ef9..e5069cea6 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -22,6 +22,7 @@ lmdb-rkv = {version = "^0.14.0"} ureq = "^2.1.0" log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } +rusqlite = { version = "0.25", features = ["bundled"] } [dev-dependencies] proptest = "^1.0.0" diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index b263fd56f..b4993b039 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -12,6 +12,7 @@ use uuid::Uuid; mod config; mod inmemory; mod kv; +mod sqlite; mod operation; pub use self::kv::KvStorage; diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs new file mode 100644 index 000000000..3992ae091 --- /dev/null +++ b/taskchampion/src/storage/sqlite.rs @@ -0,0 +1,495 @@ +use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; +use crate::utils::Key; +use anyhow::Context; +use rusqlite::Connection; +use serde::serde_if_integer128; +use std::path::Path; +use uuid::Uuid; + +#[derive(Debug, thiserror::Error)] +enum SqliteError { + #[error("SQLite transaction already committted")] + TransactionAlreadyCommitted, +} + +/// SqliteStorage is an on-disk storage backed by SQLite3. +pub struct SqliteStorage { + con: Connection, +} + +impl SqliteStorage { + pub fn new>(directory: P) -> anyhow::Result { + let db_file = directory.as_ref().join("taskchampion.sqlite3"); + let con = Connection::open(db_file)?; + con.execute( + "CREATE TABLE IF NOT EXISTS tasks (uuid STRING PRIMARY KEY, data STRING)", + [], + ) + .context("Creating table")?; + Ok(SqliteStorage { con }) + } +} + +struct Txn<'t> { + txn: Option>, +} + +impl<'t> Txn<'t> { + fn get_txn(&self) -> Result<&rusqlite::Transaction<'t>, SqliteError> { + self.txn + .as_ref() + .ok_or_else(|| SqliteError::TransactionAlreadyCommitted) + } +} + +impl Storage for SqliteStorage { + fn txn<'a>(&'a mut self) -> anyhow::Result> { + let txn = self.con.transaction()?; + Ok(Box::new(Txn { txn: Some(txn) })) + } +} + +impl<'t> StorageTxn for Txn<'t> { + fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { + let t = self.get_txn()?; + let result: Result = t.query_row( + "SELECT data FROM tasks WHERE uuid = ? LIMIT 1", + [&uuid.to_string()], + |r| r.get(0), + ); + + match result { + Ok(ref r) => { + let tm = serde_json::from_str(&r)?; + Ok(Some(tm)) + } + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), + Err(e) => Err(anyhow::Error::from(e)), + } + } + + fn create_task(&mut self, uuid: Uuid) -> anyhow::Result { + let t = self.get_txn()?; + let count: usize = t.query_row( + "SELECT count(uuid) FROM tasks WHERE uuid = ?", + [&uuid.to_string()], + |x| x.get(0), + )?; + if count > 0 { + return Ok(false); + } + + let data = TaskMap::default(); + let data_str = serde_json::to_string(&data)?; + t.execute( + "INSERT INTO TASKS (uuid, data) VALUES (?, ?)", + [&uuid.to_string(), &data_str], + ) + .context("Create task query")?; + Ok(true) + } + + fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> anyhow::Result<()> { + let t = self.get_txn()?; + let data_str = serde_json::to_string(&task)?; + t.execute( + "INSERT OR REPLACE INTO tasks (uuid, data) VALUES (?, ?)", + [&uuid.to_string(), &data_str], + ) + .context("Update task query")?; + Ok(()) + } + + fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result { + let t = self.get_txn()?; + let changed = t + .execute("DELETE FROM tasks WHERE uuid = ?", [&uuid.to_string()]) + .context("Delete task query")?; + Ok(changed > 0) + } + + fn all_tasks(&mut self) -> anyhow::Result> { + todo!() + } + + fn all_task_uuids(&mut self) -> anyhow::Result> { + todo!() + } + + fn base_version(&mut self) -> anyhow::Result { + todo!() + } + + fn set_base_version(&mut self, version: VersionId) -> anyhow::Result<()> { + todo!() + } + + fn operations(&mut self) -> anyhow::Result> { + todo!() + } + + fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { + todo!() + } + + fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { + todo!() + } + + fn get_working_set(&mut self) -> anyhow::Result>> { + todo!() + } + + fn add_to_working_set(&mut self, uuid: Uuid) -> anyhow::Result { + todo!() + } + + fn set_working_set_item(&mut self, index: usize, uuid: Option) -> anyhow::Result<()> { + todo!() + } + + fn clear_working_set(&mut self) -> anyhow::Result<()> { + todo!() + } + + fn commit(&mut self) -> anyhow::Result<()> { + let t = self + .txn + .take() + .ok_or(SqliteError::TransactionAlreadyCommitted)?; + t.commit().context("Committing transaction")?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::storage::taskmap_with; + use tempfile::TempDir; + + #[test] + fn test_create() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid)?); + txn.commit()?; + } + { + let mut txn = storage.txn()?; + let task = txn.get_task(uuid)?; + assert_eq!(task, Some(taskmap_with(vec![]))); + } + Ok(()) + } + + #[test] + fn test_create_exists() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid)?); + txn.commit()?; + } + { + let mut txn = storage.txn()?; + assert!(!txn.create_task(uuid)?); + txn.commit()?; + } + Ok(()) + } + + #[test] + fn test_get_missing() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + let task = txn.get_task(uuid)?; + assert_eq!(task, None); + } + Ok(()) + } + + #[test] + fn test_set_task() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + txn.set_task(uuid, taskmap_with(vec![("k".to_string(), "v".to_string())]))?; + txn.commit()?; + } + { + let mut txn = storage.txn()?; + let task = txn.get_task(uuid)?; + assert_eq!( + task, + Some(taskmap_with(vec![("k".to_string(), "v".to_string())])) + ); + } + Ok(()) + } + + #[test] + fn test_delete_task_missing() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(!txn.delete_task(uuid)?); + } + Ok(()) + } + + #[test] + fn test_delete_task_exists() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid)?); + txn.commit()?; + } + { + let mut txn = storage.txn()?; + assert!(txn.delete_task(uuid)?); + } + Ok(()) + } + + #[test] + fn test_all_tasks_empty() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + { + let mut txn = storage.txn()?; + let tasks = txn.all_tasks()?; + assert_eq!(tasks, vec![]); + } + Ok(()) + } + + #[test] + fn test_all_tasks_and_uuids() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid1.clone())?); + txn.set_task( + uuid1.clone(), + taskmap_with(vec![("num".to_string(), "1".to_string())]), + )?; + assert!(txn.create_task(uuid2.clone())?); + txn.set_task( + uuid2.clone(), + taskmap_with(vec![("num".to_string(), "2".to_string())]), + )?; + txn.commit()?; + } + { + let mut txn = storage.txn()?; + let mut tasks = txn.all_tasks()?; + + // order is nondeterministic, so sort by uuid + tasks.sort_by(|a, b| a.0.cmp(&b.0)); + + let mut exp = vec![ + ( + uuid1.clone(), + taskmap_with(vec![("num".to_string(), "1".to_string())]), + ), + ( + uuid2.clone(), + taskmap_with(vec![("num".to_string(), "2".to_string())]), + ), + ]; + exp.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!(tasks, exp); + } + { + let mut txn = storage.txn()?; + let mut uuids = txn.all_task_uuids()?; + uuids.sort(); + + let mut exp = vec![uuid1.clone(), uuid2.clone()]; + exp.sort(); + + assert_eq!(uuids, exp); + } + Ok(()) + } + + #[test] + fn test_base_version_default() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + { + let mut txn = storage.txn()?; + assert_eq!(txn.base_version()?, DEFAULT_BASE_VERSION); + } + Ok(()) + } + + #[test] + fn test_base_version_setting() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let u = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + txn.set_base_version(u)?; + txn.commit()?; + } + { + let mut txn = storage.txn()?; + assert_eq!(txn.base_version()?, u); + } + Ok(()) + } + + #[test] + fn test_operations() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + let uuid3 = Uuid::new_v4(); + + // create some operations + { + let mut txn = storage.txn()?; + txn.add_operation(Operation::Create { uuid: uuid1 })?; + txn.add_operation(Operation::Create { uuid: uuid2 })?; + txn.commit()?; + } + + // read them back + { + let mut txn = storage.txn()?; + let ops = txn.operations()?; + assert_eq!( + ops, + vec![ + Operation::Create { uuid: uuid1 }, + Operation::Create { uuid: uuid2 }, + ] + ); + } + + // set them to a different bunch + { + let mut txn = storage.txn()?; + txn.set_operations(vec![ + Operation::Delete { uuid: uuid2 }, + Operation::Delete { uuid: uuid1 }, + ])?; + txn.commit()?; + } + + // create some more operations (to test adding operations after clearing) + { + let mut txn = storage.txn()?; + txn.add_operation(Operation::Create { uuid: uuid3 })?; + txn.add_operation(Operation::Delete { uuid: uuid3 })?; + txn.commit()?; + } + + // read them back + { + let mut txn = storage.txn()?; + let ops = txn.operations()?; + assert_eq!( + ops, + vec![ + Operation::Delete { uuid: uuid2 }, + Operation::Delete { uuid: uuid1 }, + Operation::Create { uuid: uuid3 }, + Operation::Delete { uuid: uuid3 }, + ] + ); + } + Ok(()) + } + + #[test] + fn get_working_set_empty() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None]); + } + + Ok(()) + } + + #[test] + fn add_to_working_set() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1)?; + txn.add_to_working_set(uuid2)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, Some(uuid1), Some(uuid2)]); + } + + Ok(()) + } + + #[test] + fn clear_working_set() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1)?; + txn.add_to_working_set(uuid2)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + txn.clear_working_set()?; + txn.add_to_working_set(uuid2)?; + txn.add_to_working_set(uuid1)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, Some(uuid2), Some(uuid1)]); + } + + Ok(()) + } +} From 0f3729d4c86d212ad827c1e9d22ea5232b721dc4 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 22 Apr 2021 12:54:34 +1000 Subject: [PATCH 215/548] sqlite: Get all rows Needs some improvement to error handling --- taskchampion/src/storage/sqlite.rs | 40 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 3992ae091..e38481ddc 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -1,7 +1,7 @@ use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use crate::utils::Key; use anyhow::Context; -use rusqlite::Connection; +use rusqlite::{Connection, OptionalExtension}; use serde::serde_if_integer128; use std::path::Path; use uuid::Uuid; @@ -10,6 +10,8 @@ use uuid::Uuid; enum SqliteError { #[error("SQLite transaction already committted")] TransactionAlreadyCommitted, + #[error("Invalid UUID string from database: {0}")] + InvalidUuidString(String), } /// SqliteStorage is an on-disk storage backed by SQLite3. @@ -52,19 +54,17 @@ impl Storage for SqliteStorage { impl<'t> StorageTxn for Txn<'t> { fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { let t = self.get_txn()?; - let result: Result = t.query_row( - "SELECT data FROM tasks WHERE uuid = ? LIMIT 1", - [&uuid.to_string()], - |r| r.get(0), - ); + let result: Option = t + .query_row( + "SELECT data FROM tasks WHERE uuid = ? LIMIT 1", + [&uuid.to_string()], + |r| r.get(0), + ) + .optional()?; match result { - Ok(ref r) => { - let tm = serde_json::from_str(&r)?; - Ok(Some(tm)) - } - Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), - Err(e) => Err(anyhow::Error::from(e)), + None => Ok(None), + Some(r) => Ok(serde_json::from_str(&r)?), } } @@ -109,7 +109,21 @@ impl<'t> StorageTxn for Txn<'t> { } fn all_tasks(&mut self) -> anyhow::Result> { - todo!() + let t = self.get_txn()?; + let mut q = t.prepare("SELECT uuid, data FROM tasks")?; + let rows = q + .query_map([], |r| { + let uuid_str: String = r.get(0)?; + let data_str: String = r.get(1)?; + let uuid = Uuid::parse_str(&uuid_str).unwrap(); // FIXME: Remove unwrap + let data = serde_json::from_str(&data_str).unwrap(); // FIXME: Remove unwrap + Ok((uuid, data)) + })?; + let mut ret = vec![]; + for r in rows { + ret.push(r?); + } + Ok(ret) } fn all_task_uuids(&mut self) -> anyhow::Result> { From 4bd6c40daf5d47d5215d7f1f8b13af264bea8333 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 22 Apr 2021 13:31:15 +1000 Subject: [PATCH 216/548] Use uuid feature of rusqlite --- taskchampion/Cargo.toml | 2 +- taskchampion/src/storage/sqlite.rs | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index e5069cea6..1f8b52828 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -22,7 +22,7 @@ lmdb-rkv = {version = "^0.14.0"} ureq = "^2.1.0" log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } -rusqlite = { version = "0.25", features = ["bundled"] } +rusqlite = { version = "0.25", features = ["bundled", "uuid"] } [dev-dependencies] proptest = "^1.0.0" diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index e38481ddc..e989aa393 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -1,7 +1,7 @@ use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use crate::utils::Key; use anyhow::Context; -use rusqlite::{Connection, OptionalExtension}; +use rusqlite::{Connection, OptionalExtension, params}; use serde::serde_if_integer128; use std::path::Path; use uuid::Uuid; @@ -57,7 +57,7 @@ impl<'t> StorageTxn for Txn<'t> { let result: Option = t .query_row( "SELECT data FROM tasks WHERE uuid = ? LIMIT 1", - [&uuid.to_string()], + [&uuid], |r| r.get(0), ) .optional()?; @@ -72,7 +72,7 @@ impl<'t> StorageTxn for Txn<'t> { let t = self.get_txn()?; let count: usize = t.query_row( "SELECT count(uuid) FROM tasks WHERE uuid = ?", - [&uuid.to_string()], + [&uuid], |x| x.get(0), )?; if count > 0 { @@ -83,7 +83,7 @@ impl<'t> StorageTxn for Txn<'t> { let data_str = serde_json::to_string(&data)?; t.execute( "INSERT INTO TASKS (uuid, data) VALUES (?, ?)", - [&uuid.to_string(), &data_str], + params![&uuid, &data_str], ) .context("Create task query")?; Ok(true) @@ -94,7 +94,7 @@ impl<'t> StorageTxn for Txn<'t> { let data_str = serde_json::to_string(&task)?; t.execute( "INSERT OR REPLACE INTO tasks (uuid, data) VALUES (?, ?)", - [&uuid.to_string(), &data_str], + params![&uuid, &data_str], ) .context("Update task query")?; Ok(()) @@ -103,7 +103,7 @@ impl<'t> StorageTxn for Txn<'t> { fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result { let t = self.get_txn()?; let changed = t - .execute("DELETE FROM tasks WHERE uuid = ?", [&uuid.to_string()]) + .execute("DELETE FROM tasks WHERE uuid = ?", [&uuid]) .context("Delete task query")?; Ok(changed > 0) } @@ -113,9 +113,8 @@ impl<'t> StorageTxn for Txn<'t> { let mut q = t.prepare("SELECT uuid, data FROM tasks")?; let rows = q .query_map([], |r| { - let uuid_str: String = r.get(0)?; + let uuid: Uuid = r.get(0)?; let data_str: String = r.get(1)?; - let uuid = Uuid::parse_str(&uuid_str).unwrap(); // FIXME: Remove unwrap let data = serde_json::from_str(&data_str).unwrap(); // FIXME: Remove unwrap Ok((uuid, data)) })?; From a9b93e7c20beeb4d2cc1ff6d16b3667d0abc6bac Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 22 Apr 2021 13:33:54 +1000 Subject: [PATCH 217/548] Use names for row.get(...) --- taskchampion/src/storage/sqlite.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index e989aa393..dfafba7b9 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -58,7 +58,7 @@ impl<'t> StorageTxn for Txn<'t> { .query_row( "SELECT data FROM tasks WHERE uuid = ? LIMIT 1", [&uuid], - |r| r.get(0), + |r| r.get("data"), ) .optional()?; @@ -113,8 +113,8 @@ impl<'t> StorageTxn for Txn<'t> { let mut q = t.prepare("SELECT uuid, data FROM tasks")?; let rows = q .query_map([], |r| { - let uuid: Uuid = r.get(0)?; - let data_str: String = r.get(1)?; + let uuid: Uuid = r.get("uuid")?; + let data_str: String = r.get("data")?; let data = serde_json::from_str(&data_str).unwrap(); // FIXME: Remove unwrap Ok((uuid, data)) })?; From 11d0172bf805d3918d04933ee3c7bb934634eef6 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 22 Apr 2021 14:15:50 +1000 Subject: [PATCH 218/548] fmt --- taskchampion/src/storage/mod.rs | 2 +- taskchampion/src/storage/sqlite.rs | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index b4993b039..a4c430b94 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -12,8 +12,8 @@ use uuid::Uuid; mod config; mod inmemory; mod kv; -mod sqlite; mod operation; +mod sqlite; pub use self::kv::KvStorage; pub use config::StorageConfig; diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index dfafba7b9..2dc2e7dbd 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -1,7 +1,7 @@ use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use crate::utils::Key; use anyhow::Context; -use rusqlite::{Connection, OptionalExtension, params}; +use rusqlite::{params, Connection, OptionalExtension}; use serde::serde_if_integer128; use std::path::Path; use uuid::Uuid; @@ -111,13 +111,12 @@ impl<'t> StorageTxn for Txn<'t> { fn all_tasks(&mut self) -> anyhow::Result> { let t = self.get_txn()?; let mut q = t.prepare("SELECT uuid, data FROM tasks")?; - let rows = q - .query_map([], |r| { - let uuid: Uuid = r.get("uuid")?; - let data_str: String = r.get("data")?; - let data = serde_json::from_str(&data_str).unwrap(); // FIXME: Remove unwrap - Ok((uuid, data)) - })?; + let rows = q.query_map([], |r| { + let uuid: Uuid = r.get("uuid")?; + let data_str: String = r.get("data")?; + let data: TaskMap = serde_json::from_str(&data_str).unwrap(); // FIXME: Remove unwrap + Ok((uuid, data)) + })?; let mut ret = vec![]; for r in rows { ret.push(r?); From 305e6e2edee695094c76087b9a94cb8e4dc19400 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 22 Apr 2021 14:16:07 +1000 Subject: [PATCH 219/548] Appease clippy --- taskchampion/src/storage/sqlite.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 2dc2e7dbd..e31076898 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -40,7 +40,7 @@ impl<'t> Txn<'t> { fn get_txn(&self) -> Result<&rusqlite::Transaction<'t>, SqliteError> { self.txn .as_ref() - .ok_or_else(|| SqliteError::TransactionAlreadyCommitted) + .ok_or(SqliteError::TransactionAlreadyCommitted) } } From 8aa6b50969672689e219bb20d7321e8f95e1f93b Mon Sep 17 00:00:00 2001 From: Andrew Savchenko Date: Tue, 27 Apr 2021 15:40:11 +0930 Subject: [PATCH 220/548] Add POLICY.md - https://github.com/taskchampion/taskchampion/issues/210 - https://github.com/taskchampion/taskchampion/milestone/1 --- POLICY.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 POLICY.md diff --git a/POLICY.md b/POLICY.md new file mode 100644 index 000000000..f135e1630 --- /dev/null +++ b/POLICY.md @@ -0,0 +1,54 @@ +# Compatibility & deprecation + +Until TaskChampion reaches [v1.0.0](https://github.com/taskchampion/taskchampion/milestone/7), nothing is set in stone. That being said, we aim for the following: + +1. Major versions represent significant change and may be incompatible with previous major release. +2. Minor versions are always backwards compatible and might add some new functionality. +3. Patch versions should not introduce any new functionality and do what name implies — fix bugs. + +As there are no major releases yet, we do not support any older versions. Users are encouraged to use the latest release. + +## ABI policy + +1. We target stable `rustc`. + +## API policy + +1. Deprecated features return a warning at least 1 minor version prior to being removed. + + Example: + + > If support of `--bar` is to be dropped in v2.0.0, we shall announce it in v1.9.0 at latest. + +2. We aim to issue a notice of newly added functionality when appropriate. + + Example: + + > "NOTICE: Since v1.1.0 you can use `--foo` in conjunction with `--bar`. Foobar!" + +3. TaskChampion always uses UTF-8. + +## Exit codes + +- `0` No errors, normal exit. +- `1` Generic error. +- `2` Never used to avoid conflicts with Bash. +- `3` Unable to execute with the given parameters. +- `4` I/O error. +- `5` Database error, irrespective of the backend used. + +## Command-line interface + +Considered to be part of the API policy. + +# Security + +To report a vulnerability, please contact [dustin@cs.uchicago.edu](dustin@cs.uchicago.edu), you may use GPG public-key `KEY-ID` which is available [here](#). Initial response is expected within ~48h. + +We kinldy ask to follow the responsible disclosure model and refrain from sharing information until: +1. Vulnerabilities are patched in TaskChampion + 60 days to coordinate with distributions. +2. 90 days since the vulnerability is disclosed to us. + +We recognise the legitimacy of public interest and accept that security researchers can publish information after 90-days deadline unilaterally. + +We will assist with obtaining CVE and acknowledge the vulnerabilites reported. From c81d61dfa62285ca7dcc82f7fce0f1d67846f95a Mon Sep 17 00:00:00 2001 From: Andrew Savchenko Date: Tue, 27 Apr 2021 15:41:29 +0930 Subject: [PATCH 221/548] Removed SECURITY.md in favour of POLICY.md --- SECURITY.md | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 3f9c867c2..000000000 --- a/SECURITY.md +++ /dev/null @@ -1,12 +0,0 @@ -# Security Policy - -## Supported Versions - -This software is currently pre-release, so no versions are formally supported. - -Once 1.0 has been released, only the most recent version will be supported. - -## Reporting a Vulnerability - -To report a vulnerability in this application, contact me directly at `dustin@cs.uchicago.edu`. -You can expect an initial response within a day or two, and a regular email conversational cadence thereafter. From 215fda0539ab7a938a9a2ac7b1bc935d59ea3187 Mon Sep 17 00:00:00 2001 From: Andrew Savchenko Date: Tue, 27 Apr 2021 16:00:22 +0930 Subject: [PATCH 222/548] [ABI] Add notice about backend upgrade --- POLICY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/POLICY.md b/POLICY.md index f135e1630..b540da5b1 100644 --- a/POLICY.md +++ b/POLICY.md @@ -11,6 +11,7 @@ As there are no major releases yet, we do not support any older versions. Users ## ABI policy 1. We target stable `rustc`. +2. TaskChampion will never upgrade any backend to a non-compatible version without explicit user's request. ## API policy From 1959f0c63a8a6102bcb2a9670264adadfcd66cd3 Mon Sep 17 00:00:00 2001 From: dbr Date: Tue, 27 Apr 2021 19:35:45 +1000 Subject: [PATCH 223/548] Implement all_task_uuids and base version methods --- taskchampion/Cargo.toml | 2 +- taskchampion/src/storage/sqlite.rs | 49 +++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 1f8b52828..e5a1ab801 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -22,7 +22,7 @@ lmdb-rkv = {version = "^0.14.0"} ureq = "^2.1.0" log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } -rusqlite = { version = "0.25", features = ["bundled", "uuid"] } +rusqlite = { version = "0.25", features = ["bundled", "uuid", "serde_json"] } [dev-dependencies] proptest = "^1.0.0" diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index e31076898..6aaf7b85a 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -23,11 +23,15 @@ impl SqliteStorage { pub fn new>(directory: P) -> anyhow::Result { let db_file = directory.as_ref().join("taskchampion.sqlite3"); let con = Connection::open(db_file)?; - con.execute( - "CREATE TABLE IF NOT EXISTS tasks (uuid STRING PRIMARY KEY, data STRING)", - [], - ) - .context("Creating table")?; + + let queries = vec![ + "CREATE TABLE IF NOT EXISTS tasks (uuid STRING PRIMARY KEY, data STRING);", + "CREATE TABLE IF NOT EXISTS sync_meta (key STRING PRIMARY KEY, value STRING);", + ]; + for q in queries { + con.execute(q, []).context("Creating table")?; + } + Ok(SqliteStorage { con }) } } @@ -110,6 +114,7 @@ impl<'t> StorageTxn for Txn<'t> { fn all_tasks(&mut self) -> anyhow::Result> { let t = self.get_txn()?; + let mut q = t.prepare("SELECT uuid, data FROM tasks")?; let rows = q.query_map([], |r| { let uuid: Uuid = r.get("uuid")?; @@ -117,6 +122,7 @@ impl<'t> StorageTxn for Txn<'t> { let data: TaskMap = serde_json::from_str(&data_str).unwrap(); // FIXME: Remove unwrap Ok((uuid, data)) })?; + let mut ret = vec![]; for r in rows { ret.push(r?); @@ -125,15 +131,42 @@ impl<'t> StorageTxn for Txn<'t> { } fn all_task_uuids(&mut self) -> anyhow::Result> { - todo!() + let t = self.get_txn()?; + + let mut q = t.prepare("SELECT uuid FROM tasks")?; + let rows = q.query_map([], |r| { + let uuid: Uuid = r.get("uuid")?; + Ok(uuid) + })?; + + let mut ret = vec![]; + for r in rows { + ret.push(r?); + } + Ok(ret) } fn base_version(&mut self) -> anyhow::Result { - todo!() + let t = self.get_txn()?; + + let mut version = t + .query_row( + "SELECT value FROM sync_meta WHERE key = 'base_version'", + [], + |r| r.get("value"), + ) + .optional()?; + Ok(version.unwrap_or(DEFAULT_BASE_VERSION)) } fn set_base_version(&mut self, version: VersionId) -> anyhow::Result<()> { - todo!() + let t = self.get_txn()?; + t.execute( + "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)", + params!["base_version", &version], + ) + .context("Set base version")?; + Ok(()) } fn operations(&mut self) -> anyhow::Result> { From 2b7e121e7ee1d515d89dda4569922428ff2bfcac Mon Sep 17 00:00:00 2001 From: dbr Date: Wed, 28 Apr 2021 12:35:50 +1000 Subject: [PATCH 224/548] Implement operations storage for SQLite --- taskchampion/src/storage/sqlite.rs | 38 +++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 6aaf7b85a..d5bcc3dd9 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -26,6 +26,7 @@ impl SqliteStorage { let queries = vec![ "CREATE TABLE IF NOT EXISTS tasks (uuid STRING PRIMARY KEY, data STRING);", + "CREATE TABLE IF NOT EXISTS operations (id INTEGER PRIMARY KEY AUTOINCREMENT, data STRING);", "CREATE TABLE IF NOT EXISTS sync_meta (key STRING PRIMARY KEY, value STRING);", ]; for q in queries { @@ -170,15 +171,46 @@ impl<'t> StorageTxn for Txn<'t> { } fn operations(&mut self) -> anyhow::Result> { - todo!() + let t = self.get_txn()?; + + let mut q = t.prepare("SELECT data FROM operations ORDER BY id ASC")?; + let rows = q.query_map([], |r| { + let data_str: String = r.get("data")?; + let data: Operation = serde_json::from_str(&data_str).unwrap(); // FIXME: Remove unwrap + Ok(data) + })?; + + let mut ret = vec![]; + for r in rows { + ret.push(r?); + } + Ok(ret) } fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { - todo!() + let t = self.get_txn()?; + + let data_str = serde_json::to_string(&op)?; + t.execute( + "INSERT INTO operations (data) VALUES (?)", + params![&data_str], + ) + .context("Add operation query")?; + Ok(()) } fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { - todo!() + let t = self.get_txn()?; + t.execute( + "DELETE FROM operations", + [], + ) + .context("Clear all existing operations")?; + + for o in ops { + self.add_operation(o)?; + } + Ok(()) } fn get_working_set(&mut self) -> anyhow::Result>> { From 62f3dab04920934f774bad5977747b245b45c2c1 Mon Sep 17 00:00:00 2001 From: Andrew Savchenko Date: Wed, 28 Apr 2021 12:20:34 +0930 Subject: [PATCH 225/548] Update Policy - https://github.com/taskchampion/taskchampion/pull/212#discussion_r621458637 --- POLICY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/POLICY.md b/POLICY.md index b540da5b1..c70e66fac 100644 --- a/POLICY.md +++ b/POLICY.md @@ -44,7 +44,7 @@ Considered to be part of the API policy. # Security -To report a vulnerability, please contact [dustin@cs.uchicago.edu](dustin@cs.uchicago.edu), you may use GPG public-key `KEY-ID` which is available [here](#). Initial response is expected within ~48h. +To report a vulnerability, please contact [dustin@cs.uchicago.edu](dustin@cs.uchicago.edu), you may use GPG public-key `D8097934A92E4B4210368102FF8B7AC6154E3226` which is available [here](https://keybase.io/djmitche/pgp_keys.asc?fingerprint=d8097934a92e4b4210368102ff8b7ac6154e3226). Initial response is expected within ~48h. We kinldy ask to follow the responsible disclosure model and refrain from sharing information until: 1. Vulnerabilities are patched in TaskChampion + 60 days to coordinate with distributions. From 990c540563f2b47b5fbae0948d4a61c3735e8d5d Mon Sep 17 00:00:00 2001 From: Andrew Savchenko Date: Wed, 28 Apr 2021 13:37:11 +0930 Subject: [PATCH 226/548] Update Policy - https://github.com/taskchampion/taskchampion/pull/212#discussion_r621457029 --- POLICY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/POLICY.md b/POLICY.md index c70e66fac..60daae884 100644 --- a/POLICY.md +++ b/POLICY.md @@ -11,7 +11,7 @@ As there are no major releases yet, we do not support any older versions. Users ## ABI policy 1. We target stable `rustc`. -2. TaskChampion will never upgrade any backend to a non-compatible version without explicit user's request. +2. TaskChampion will never upgrade any storage to a non-compatible version without explicit user's request. ## API policy From db9d5483e83fe1d4c76842b29cb8418c39cc8932 Mon Sep 17 00:00:00 2001 From: Andrew Savchenko Date: Wed, 28 Apr 2021 13:43:06 +0930 Subject: [PATCH 227/548] Update Policy - Closes https://github.com/taskchampion/taskchampion/pull/212 --- POLICY.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/POLICY.md b/POLICY.md index 60daae884..9f9af590b 100644 --- a/POLICY.md +++ b/POLICY.md @@ -29,18 +29,18 @@ As there are no major releases yet, we do not support any older versions. Users 3. TaskChampion always uses UTF-8. -## Exit codes +## Command-line interface + +Considered to be part of the API policy. + +## CLI exit codes - `0` No errors, normal exit. - `1` Generic error. - `2` Never used to avoid conflicts with Bash. - `3` Unable to execute with the given parameters. - `4` I/O error. -- `5` Database error, irrespective of the backend used. - -## Command-line interface - -Considered to be part of the API policy. +- `5` Database error. # Security From 8f0f8bf31bb46083b282d9bfd304a82584bfce3f Mon Sep 17 00:00:00 2001 From: Andrew Savchenko Date: Wed, 28 Apr 2021 13:56:29 +0930 Subject: [PATCH 228/548] Add CGI assets - Closes https://github.com/taskchampion/taskchampion/issues/207 --- docs/assets/cgi/LICENSE.md | 2 ++ .../cgi/icon_rounded/icon_rounded_1024.png | Bin 0 -> 566832 bytes .../cgi/icon_rounded/icon_rounded_128.png | Bin 0 -> 16993 bytes .../cgi/icon_rounded/icon_rounded_16.png | Bin 0 -> 1183 bytes .../cgi/icon_rounded/icon_rounded_256.png | Bin 0 -> 52056 bytes .../cgi/icon_rounded/icon_rounded_32.png | Bin 0 -> 2337 bytes .../cgi/icon_rounded/icon_rounded_512.png | Bin 0 -> 169678 bytes .../cgi/icon_rounded/icon_rounded_64.png | Bin 0 -> 5819 bytes .../cgi/icon_square/icon_square_1024.png | Bin 0 -> 536011 bytes .../assets/cgi/icon_square/icon_square_128.png | Bin 0 -> 14641 bytes docs/assets/cgi/icon_square/icon_square_16.png | Bin 0 -> 1066 bytes .../assets/cgi/icon_square/icon_square_256.png | Bin 0 -> 46171 bytes docs/assets/cgi/icon_square/icon_square_32.png | Bin 0 -> 2011 bytes .../assets/cgi/icon_square/icon_square_512.png | Bin 0 -> 155209 bytes docs/assets/cgi/icon_square/icon_square_64.png | Bin 0 -> 5036 bytes docs/assets/cgi/logo/logo_1024.png | Bin 0 -> 826502 bytes docs/assets/cgi/logo/logo_128.png | Bin 0 -> 21775 bytes docs/assets/cgi/logo/logo_16.png | Bin 0 -> 1373 bytes docs/assets/cgi/logo/logo_256.png | Bin 0 -> 68818 bytes docs/assets/cgi/logo/logo_32.png | Bin 0 -> 2820 bytes docs/assets/cgi/logo/logo_512.png | Bin 0 -> 234149 bytes docs/assets/cgi/logo/logo_64.png | Bin 0 -> 7359 bytes 22 files changed, 2 insertions(+) create mode 100644 docs/assets/cgi/LICENSE.md create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_1024.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_128.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_16.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_256.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_32.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_512.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_64.png create mode 100755 docs/assets/cgi/icon_square/icon_square_1024.png create mode 100755 docs/assets/cgi/icon_square/icon_square_128.png create mode 100755 docs/assets/cgi/icon_square/icon_square_16.png create mode 100755 docs/assets/cgi/icon_square/icon_square_256.png create mode 100755 docs/assets/cgi/icon_square/icon_square_32.png create mode 100755 docs/assets/cgi/icon_square/icon_square_512.png create mode 100755 docs/assets/cgi/icon_square/icon_square_64.png create mode 100755 docs/assets/cgi/logo/logo_1024.png create mode 100755 docs/assets/cgi/logo/logo_128.png create mode 100755 docs/assets/cgi/logo/logo_16.png create mode 100755 docs/assets/cgi/logo/logo_256.png create mode 100755 docs/assets/cgi/logo/logo_32.png create mode 100755 docs/assets/cgi/logo/logo_512.png create mode 100755 docs/assets/cgi/logo/logo_64.png diff --git a/docs/assets/cgi/LICENSE.md b/docs/assets/cgi/LICENSE.md new file mode 100644 index 000000000..1d4dbe059 --- /dev/null +++ b/docs/assets/cgi/LICENSE.md @@ -0,0 +1,2 @@ +Copyright (C) Andrew Savchenko - All Rights Reserved +All files within this folder are proprietary and reserved for the use by TaskChampion project. diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_1024.png b/docs/assets/cgi/icon_rounded/icon_rounded_1024.png new file mode 100755 index 0000000000000000000000000000000000000000..d4a4a9e13a759f242f2fbe470ac198abbc465343 GIT binary patch literal 566832 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�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≷? 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

5xuPip24S-Od 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≫FGJqFiJSWzqOHHRV8l%U$R#@bOzx-)w$K$= z`K~eKwOf5cf8*bp&Q3wR2wHcH-{y$V^Yuc!=qebyK!tN0I5SAo3tNZn>@EMixP$l! zPq}3UTN=1R_XUPRZyhYmOXB_qWyyaX#lC3vr$6|f-pt``_oeY(sDY4C9|j`;Lkab9 z(EtZQmq!_RZ&M}Mh|}H$tA9?5c$mO|+iwAI>I-hI*rm9zE9_0`{ApB$+PBKYv*1iA z1;1m~WbA#jPe5^EDnlVUd$FPEV-f1s5WNaim9R9;O+wRE?;}XWf8#2A$ost)_?-~Z zpuYNrcb3%3fXg41oM1sKW0G3Gq-)O6XcnD3a2xI&B&JPF<~(zuo}y3SJZNj}rD2g%@%`tRpYwdY2NI?1CbyGBY7EX|fpMA_F^HdO>W?zEoC_Z!m17R{=dQ&Hg9UaM|E7I%`0! zxUS5*qvSz^OsY9_ISoU0m*6K+F@q&zp(gGfO9!AuE>dr~M&=^zFXCeEl<1p39gig= z>pu6aIrS@rL6q@ywN{kZH#XFhuJ82n!_92++Vph+=@HV-NDPz^zvH}=;gL&MH^sRnc3^QoyKQY9LrXd&}FU4N`QTpA( zCImX93qkvJTx^Ip^a2e*Koul0TvXwp-g+|Xvh#$k^T-W6+m3#w>Tt=@@J z;;$Lxbu!|2PytNglrWoJ1Pk+MsyzMQCumd#?@&FK%cDA5b1-M3O7SKUzSp_BaQ z*ozgMaj*kw$$b}vSz!&#McqFi>CMInTmWP_#{_MEsOT7QgirnO9~(WrqPQfu`CH|q z(sq|$Klat)>wFPMQ;LdpOwEVIT8p&Q&DI#3-q!D2kf$cst=Gac3Dcb?a1itAT!wq0 zyGbxC^Skp$*Z)mjSD$ji)6;OocVSY}n*}FL-m|@BGV($D&RBjSj<@TNvj8c4+v&wV zh+o=avXo1hxkT{rjFPu7T|0IoP6L_sr&#LBu6U0V$Y&~uKr|VRyEF-AP*Jz>S z@!eN3YBr~CB7#uU81al`=mBRDOSWIz-XBbS3bviw(*4_l;J1yvEneE4{A~uRzqj|8<5AUB z4>R4=gCc47j|`y)eD$jD$BY5E@zE zUQ(r%PiDrxT>eQ|O?^ou+@v&vvVo@}uy90v+Bo4yk!*i4>=23z+p5UKX_Fpn<0 z_I7&Q5pzZtNiMSE>Ws3VrycVdFPt zO^=}f;+6g*mqV4aArA|8Lp}%j1r?nHzGp}FR3T!CHg`x>I{MNxby7-~y&a+%D%5wj zteGAdDW;JkuAL1_>*N&KupHG2iz%S?e9xdQzxqBe(AZTw4A^VP7EX$SWc8cf!-*bp z22D<c7s+uR4fYJXqLU zKph_xZF&FG^r&47HM4f(SMHO!fIPB&5-{?ZX&tNJ^4H~|K+r|Y)jF7^a#mVW7l{PRE(C>m^13k$B>9Y?pW~S!wt2a zvfF2GaV_j#17m~uj}z_G?%XwzKTlO|c)rZ#WOlN^NJJ6bA? zvbC}(6A;Vir18$@J}KR#o=PX{wxX7rDOCf!&I4!>J3d+w5gqRAJjridvr=-r2g4U! zaJFa{D?0lN^SDsG`!8KU#r=!m7Hw~3b*(yPBisLp}6mHQ}-s!tF(_wQ-M`e z)dJBF4-Fu8OI&tH!T*-5Eo*&#HlHu02U~|rJ+?c7SOx(^1sC#4*LST$EzD)OQS($- zjLrU4Lovg*0YTLiY-ytF%wYM4gz^!tw|D7DqLV7_{1I|&HX9|Bwh6~R}KA%*?Y~FUByy2JBP41i*wBUu2nTOhIx*j-=m)Rr=p!{2?l2b1?W)Vw2kK zl6DMgRiTw2WGMD~|Cs(8uR%uMwO>SB*lI)F7b=+kdMx4biumzffcTw_^v(+z1NuIG zCZkw;8o>EY*&XsUO!23#J1x;{zaJEg5~@DZgdTI;TYY4FZ(%-!Pqv||E>mK>H?{O* zB9YOrQhN!wj$%!-i_{OCU#p@)cN_$tT7TIsct%kE{hO?Fu){B(Tg*~g&W~9-XZjlC z>KAPG$un|2fZvee`IyB^;Ut zM|pty?!zI_d zXE`d+eFT;vyJXr#skV%--V~M%x5zvvw#dN>WE>p^lyIZkJwFW9niSF{0eEbp$Up19 z-bEo~Xz1cbTQ7E6l_~2+S{Pn~6Zu*!&PpJE@^FgIRQo~{Bzi`*XBtb&0(wr%MzCSY z^c?*l-RG8u1-opoU4XdDAW*y@&uFKOt!mApkBc=$3*|r>M`wf5j1W1t$iVD&0JraM zqn29_NX#<@_$~w$_Lh8o->8!H|bZb zFA*1vw^Oze2E9Yd9GbOlznS%$bBeXJfAPh#?$A>GDd;L;15}Wu+Fqjc7nCugK; z%SCs1qnNu0yIPm+<#Vxt56Mk z=A-5T!RZ{kg#?|N$`J<)?OLyM@+ACA?}g57^KXDj@HpE#$z;`!td2dI_}Ly;ZS4CG ziLq}JpF6Ph`XH1W#&a?+{kiJ+21Ug-w=BjYGQu9yD|?MVe&0*5O<_Y`ZuU5=6v-(^ z(|F~41cYz_m0%mpz%lD>XYr-i%&bpnVcuU+`lPl9yU!37yC>@&zvGoWKT8^7!7QB? z>dxKBmcxwB(@F4NZ6C29Dsb54O~mNlsGH648#>9zb}CjE*-eUYP%P(Lj~(ebroUH7 zVo831eeJq0>bojSWrskKUwaRWAhN;b8t9|^DvF|?O`6J2L|X3_1?+#T0vUnpLQVr{ zX$iy+Od{a;2shw{Ji!UL&Bst>1!7o#>Lmi*n|evmzHV!3Aq%j!uYb`9WT}&dq7+^T z=j@SK5+GEfL4dZFANnZ(%4Pb!gj%wJLV*G0k!`fktx#}!=+EB+kwEP&PPqFB4j#0l z;HQMBp-+JXhCxC1TLNt1pXgKR<%6Biuq;u{rq2jpTAFXy%L~;xtbdp3mk(|}XP{-oEj$Z~ux_;lw^U)0#}Leqk~Ivi&9prIogdw^w+2=rIl-7u zOqfYG#2;o1Xh~_qob8PTWByMvl;QEzy#JZ&sgIKJ?6e&f3?h?aiu5^Ygfm0=3ou`) zhp@;NPF%R=Fx+n#!BuPZCFODF%W~@05Z%oFJ?o5mF5G~l#9&9#H(bF^T|Uh`Jr}A5 z10QHrvlNr#kjz0xFc)txxkarKT9w6m8=(bKYZ$A2v_kUa*2UY_ws@%9GBH9Q+3Rv# z?PmSDA@p-bnw|72FJE+!faayfu%=E7uSm2w=Y)z zyiyeP3Q|!4l^B6|YH%c-jujH`IG=>(o zf|u?j65?tI!un`unPZVNPu?MXM@f_`j~evGa6fC558`71&2M?X3*q5LGz7f;{guPEpD2Dj~Sg3haLuUc83n#O<+ z!C1^I#GVDWqrvbLCPxZg;()<5zn;fN8=4-=O0VCGocs?P_uEW8D!GzxsD-XPzQ47O z6s9lyHni;R>T_#wMG|UqC+`%TfzCUNn$+xXY1_y%MdZTwA{{hy<5^inOTDxQQ=GB2B5o+Ae^MY`1+NVv zA=(Xti#dY^$ zi4$!WaG49I_$MGs2!N)8$;g~LD;z;vUj!Gdx&EU=XYnn6m_fJngN>Q7#^(7@d{b9p zC#)}?Ph1iE;Z*U%@`IO+pO?u!FeerL*nir7(Kq|d&SgbSF*YrUR2$anHR`w@FJ-1Q zeYYYhYvk1nxyWOoYAomVGxf(3g6m(&Xv(qQ^gxy+)%1Z@N{eG;b-?4h~# z<*2r!LQ<6n6PAn!5E@YQXy+dMaa6LY&S@rZ7^s?^$d!Fk?s zoUTopL!7)_nA!8}52QU4iUG`dscoUtnvLUL^lS%NJv2V+uJLUL#iB}t5aX-<7x-4eaoNV1UJ)#pTgu}Euv!lL3|!oq z2rI@5V0b35R12WY1UA6V2i^kY{VHtUHz)IfFgB7m0UVoloP?DDDt{7#rKT*o$o1Dw zLOmfWe78am5*6F(sBfSLgVGLl@u(z@YhugZ;5v5_;qj$U`HKGKz3$~EtYQ+)J%dA| z8tkM$z0K1=)fnVD#|Q1*4W|>1k>0p~%zV0jLB}L?kOY4ziV+0YugX{qgZ%W)e=!lc zs&*^d{hSWU();-~M`BoE<_$(9NG+~CkgcX1$#qtI#Z!sVxnB5&Cm}S z{$!-kEM%_rh;Q;Xr&AJlO1EY<0>zO1;G;8jhDB7`Sak4`%@vtPt>8)3E`2_Al-dVA z6X6R_8<84vql7`2iT6lmBC|grs5_EC3@CpjJ|VOM-EZ0Y~i^0`vhVMz-kL?45_Ni5dx?#n4igIR8l)QB}hgJIuHSy#>DP*=5(b zA7$Ap!?BV(ee^!*P@AJ^_)^Z8+pKguM35 zg7@?;s8on#?s8ox0VWT#o(&$dZV-9kw06Dx-+{h$*^qMEd_fR$a?>*jA382&`sF+J z$2gh~bMP0#(z!XmB6@X}#zLGz*<$=l^aVXxqvhdy6-H<5?^(8L>q0leNLRSl z)aTS0Dd^LUN9e$n*s15Rhy%F~b2v0)-BU;W5p?|(O!MKWZIWhV)+R(?KsXy6!vBmo{-dK90QBAosK~SlJtFoU+fuM!)@k7dw z@D)@6F7ulO!G%KkQ&xcP8$J9vG$9|Tkw&p=9+OuNTwIA-bl%Rka6_sbaedNDbWuhn z>?qSj;4io33+M$o*P3mV&^8k_%;q4X`N@azxzK^G8oKR(LRs>X*rYUX-qoE%E5puM_fJ5|HPqhjDMA_ zugr-4II=#2D^={j`ZkrBWsadvpS#i^N@1%x%PN?jy|n1E{`fFx^tym(A;2f3)_|h@ zHI0{W@8H<#3Z9VGfB?my4=zAT*^D2mJ;20y?QKGJ|0ok;O6o6gv3!tVfO?<0rHiLL zeHkL|$0_?EoXW_Wh4nrmf^o2Qs}kfEE9~q`8nu0JhE&7Fb9h1zu2OzFJM+pS+Y^(? zO7TUqhL8Fg3Q>OjVT||1g$&5ZScy(9mE4zc$oTR21{7qFJh(md$9h|8Iu#G_Mgu1s0C0%0^={B3DT^-7}j>yz)kr!wpuQh%*Y<#W}Gg8K{0j|`vD;~j2JW1g?+%$u)q`ZNsflejC5$E|!kgaUi zIVE=nLu2jPGo;ilC0NH;m{&phGl(k|BM`xSrepVSBulFk4I&Ua5CpLkiCCvA`Ol!K z$^k<-FRP@0F`53KCS@Z=W{UEp@lSl|O<+IzoC@n;5~GYndw;a*64HcFkz!@XFL=B; zZ}O&TXAVesxMpOb`O+IwwI@XzaVTPIv>|*UV^v2cXP`vTz4`xW`pSkVzc1QnhM^m! z8$?PvL?i}OP`XnZq`Py55+wvll~lUBV`u~<1nKVXu9-W(|GoFce1UoPIeVS8*Ir9( zGDl#3oH04Trf3%KSxG?YvPsgF#+u1dx9lEZPjpyF2U6vpmxXFlWxpsv#76I2E1iNW zV5nlY%BL>c>(taDV(Umd%4tWt&`Pq9wI%6xv`pRv33~F)e zi?wugK?4o!eF-{N{KPJj#=?P_W5@3%!3&!__LCb2Szj-sLDb%F-xLDaNN0Cn6^ zFC6GXLRKO3{5@l>YGHara6l=mHdW}~|&sp;VJ?@|9&7d~#%f*pgh{#9&aG`8o0G{on;cX-W}&yazg4NkUMIAw=HxZ#*kqcC;TX!WaUe-1-b zKf6S2bmKvXG9c6^OYDzkyXTI6ELBBc>foNkkmJqwlCL8#N(T9bmTb%jx<*8LL!3b# zdU`;>b*chkAtxBJ7F1ti^D@)^V^x5TRU0HA#f$=z|EcE2$>8b0Pht8N#mFvO0-k`k z#%qH_R^Lczg37RCr&3qyPial==^vF}ezj5H%ZYugiO{m7GVMAH~tWxg6b z)o{BuU=g`A_|oP@h&SkX12`L_grXgd-m}Xog)0muB9hVU(aNZ265xfjL<^LNmtB(p zK8ZW+`~5AX;{L}}v7P8HiIuDgfn~IjB6?vm;B$5c{|GzUq9EnCgSH;M;y&mGx6N?< zv9(I#!ZiRN*Lns)ld6y!dhkE7Lb^|)6{@Taf>o1PA0+2xIra8jH=4I9yZY)xA1B$< zvYtNNuBGE8e;yYSfz#6g=be)XA^= zbJV6zk80eJE`3N5%$ANl>RMQt;ff}p`q(k_fZp6NCtXHAtKIMG|1%x{86LMj$BD@;1f##|oZ)ISM$ zQz0-~v?KZHDDIf7VM}7_^!T%S!n;p>4%F>J4=#{H@hkY8F%K_uz$+F zx_wIY>G69p(&J}GubJEMp&G#c5$wQ|`jQyal|mQv`%o@HRS-zl`d)ZaWsMpI$fmVN zqde=@8ogFO3Xgdwnfwah>Hk6TRFCzy!fY$+XaKd^D_qPIh0g(g)TR4n6E;&%#ZTm` z^O6J|e`KN8@3uNHpL=_QmvW;Y!R4pvgXjfYcNl#ksK3O}OUw%64z$f&?fD)Tq%084 z5-v&_XoC6M^_yWyWK6T*^Lktd1wa!b%m%+$7?He|2s+%5j)+=F$6Jp=hqm6p2MeEJ zqh0z~joa?oQlJ_xkVhFFyo$A5N%nJcXR>?n2r}yG4V%Nag)5pU zjSet2J7nmJ?Tr}r{XxK})%0lYZS^MHdKuLE<8Skf^p=?|JE*-p{Q7c>W=vLj&Ti;Y z0d?2{r33%De{D2}&~qyer#r>8FCpGUk9y78jenoR8rp-VE$F=3EBf9nT&nK=fw|tw ze?Zw@qWr#~SRDVa7hf!vkvwugojr#l!+Y$#QrgAX-rzuV51IyDIbJ|zJKV7g-^B`I zNd`E|lg`<8Pzv9v26_jxTODg?VENE^ItNvMA-!E`wImu39EOgkR0|276 zDbZFZd&BpSx+EDwwHxai%&<&I(t5B13Q1A7B2$zi5hPg385OVDwN1)az(jy4YS^6* z**^u+0D$(2<|{iJmyAJK1$;WI&4XOw(UM_P-xsr7nRve^!S_fvA@8G4Py&L;C+{fY zRm8ps*7$fG&@d|K7AjN$lwsqE`J=52E?0trvG>tq{hPu-5u4myfh6bV3`Pn+s}n?s z*8jP#fTn^Q-YaX6osdBBT0h;TqgIQpr zdTy3DUPe>#$D$2m=+VyiKOU^lP)(Paj=HM^DD5ddI>W}hrP=?@H;I$W;$Bk5POZ;) zsK6zWI)~D|`>2g#7~v>J&htCsjuWVb3g*4zCF6nEr@sDgohLMgJl;i5Fb@1C8!4k6 zL7ig8E|gQozgtk@r&!;A4+#nB&fTC%xRP_TlJ6v*X~t~S3;Pc%wNtq6Z>-9^s<>Cs{I_IIF*@55 ziO%-SeGW%UhxItnA%Hhj=u>Udnf_2MHl(Qz{-ceS4!gcUi^P)_Kq}%Zn)T8Vs*7#J z={RjU@JELF3 zMl@?K4I(|jR+kjcsS}7nSJNkE$LA)eMY){TR>X2@09QRv;iTcdi(po%s5*58%8l}HPu7!kY5RSS*y(z7#MebLjB$1As4T+SUL zPqwVwjX7d3gDg*E?)KCmZExV|^Il3OYkxAR3+F{;i_r& z8-vkbW;BK5uY`V9$GHU6@<3H_)rJXYzmv5`1YTcZ_oUA;c8s*0{%893AZDuxem~qJ zu)Gn_j&9lG|6%YIi{~btu}i)b&>egw-1a#LL#+YWMiWxQ16CZajxzyvr~lY4uU6-~ zb_6%!c-YW;0yAF32j7kS4-^S7{#_h%1~Jq%5niHtnpzX{G|eJsUIY@>!N5z*02 zwyMt1HkoN(M{qvzH#3%0+3JaB^h4|O*qTci+UH64n6D=d)$n{A=30(w{`n2rH-lpL zyssZZ{>O?*?|LK!%*vE+tuKey?vqcYw*(xZq#vA_ZwpEuyiI35-0(4q;@GsFnBsxq z0iZXHI zg+KaJQp%IO(L@m)KhY7K82M+>SOAf2(uN9_CMKj}XkX8Qg(o&5AKvi}Qv?dQruWW| zxlmMf<1`%x&XJ9XNU9BJZW}QDJlVK?w)M{Dm-U8-TrSjxips^Jb39_!d3bT^wKI0!=Q%j78ErV2cbD z<&bT(D6Q#6>VBUa5pC@h*kAzuw*bo9rE-y4<~Lc*kx%1a$$l;!GqMo4SbvL-K&a6J zO`pPFpzT`4eQubrYK(xAC9P9pxEs$?Oe&ix4K5oaGSd?f70Fgb%b?m^Jh6-|2?S@? zdCuQ%E#3xm%tJbL%?bfpKDJF88~|0YgkiZKQU-ds1k3Y$m?vc-f1M-#@M`Xqn1uwh z?JAFA9Oiy+ZHzJk@tf0_w5PA!{7?~ixSD}sqtF2DzwI4juo1I0@A_Z&Q+qj2dqKA^ zh)c!(?~vo;%$3tQSoSw$RMQkJRbRSABzao&VamW2_O|n-RKOs=2{WZQ^13@cOI`iP*8{gxG@;|h;LLo{r zEk9aTzQxuxUO_Na)2Q*~MO;d=v@zQ&V3!lgZi+_CgqAvqB{uSJ=B}o##JKMVq6iG= zi)@mEuc>QaCNO?ar%2&uQw*feQjA(lG9D6&c7yT)@2wfOBF$OM`vB zn0nYfl|0n<%QQLknA#oy&|ki6bd4KyoArq~NC#X}h!IJK=EEMZwz(Fmw{e4<-b;i^ zU!nvoJu}&W+fxZ9&;mX3zQ1gsT{KuFx&Us z)NH{->-aKnzsrss{OL3NSI5>4y~4s&kzXzy#LfPWOjtuK_-H~_6aK#zAedh6cSTo) z{TCG8HDe_@#nTC?T8$c;ee&wQ0P=f+JJz!mjnN&?L-V=Uf7=fhz3V;b?f#Z2F2O7!)TcolkH&&F(ZcZ zwctwPbTYW5d5m711_7xC}HPkFx(@=6{iw9Lvj*`Xrfyt zW9LzTUm3N0;#)+ieK(sDY=%{_0gv=Cl-$*cpaMffWTv8VBkhQgyVIS?%BQg(^+Mde z6Rq>yJSXRqFee#Lkq!Uq=iY6j$f`yHEdpW>tdl`OnUJrhxoVlzs9y%j!a3dHcLLV3 zff6tIAa%E7k4H!Bk)@sm;H7{AtrjFP#-%x0iMHQ|zi-*iPayUsv(uQ@^uIu61bSJw z?M>DQ(i`cradGPZ--4H@pWim#Ox4|W+I-QD2PSm{bSsF5ZUm|7jKU&Xx_jx;2MdKP z`}$ZrWKnsQJXHO$Ct-s=ifiti21-BXz-44V>YlK#xq@2pR@e#g93MxG4-x^x^=V~?~%@2CcHScPy|%{`@q+e^rSn=g|1te#-%T$BcmroUL-?f)}d;wtGa~$#GgjU_+NdX$GeT( z$BRCX1-Jpu&1QoK5cH=Xc+=bEWD@myqdy2x`KDU}f;W^}X6hAe_@@u(J>ZSxqRAI& zs#_#FGIXK(zsS&OJ0>pRF%vC1UU2%rX(itT$riBPVJ&cS2%J@lV?RpT5CZ5L?*nzPP2__w`(egA3@ZVmRCiCPu$CYip$CrK8Xnwx!U`{ zRY3T7PNX%ABQa-7#+bBoJ-9ECVPngv{Fm>;Rls9GO}~HAA4ZUu65_z?=3y(SqqMA| zf*rH5e=(gmi`5*$)no|8SCK&PC1}pzw7hw^5s$|Scy;NuBb* zJd*!y_!;{VGvC8V-N=dmL7y5NEo~e%<_k97D8v`aa4ZyBvu*Cf!@4UA>Bk|d(Y<7b zwC#seM~!vz(fkb0K1xwWww|~@yp=ebFmW*5=Y!*|TIIx2e0}X856t00Q#IRf$ktY- z1n7d;weH8Lmp>;0H9T2Tg|DE=Vg4H4NESb{;e{oeZ12p;tYI`gLoHm&gS&k|5LXI9 zldEXQjXwPQm5~SE`)MH)^cP3dZ7FA!ytrb^Vv^miQp-8)^Gh%iH0}C^AkGxmrDzUJ zfeD@A;zyhC|K}r#Hti?&d}E+Zkahm3jD$m;86QL*Mml&82H^idG&NK z!`%s%R~J!ewAg;zrtIoAx&a(NKXx`7Izzpff~C*y*FDggqt8Ctw@Oj>cfwS%vIVS_ zAZ264{yqDPM#4uzD&;7=FXbpZjQ^xA+}}3;?lo^SD%)l5>2&8}Ghi{w7-T*xmA6jY ziUfYaT}9~sCSy%Q%D5r>X*|w?xfBH3fX!3%BcQZ(`})r;8JxIncNkq2NZrp?(+BUfa8I$# z!$n;Rx;?n*Fm(K9j2j_#$`!l)(s9>BeoK#^bCuaRLuxGOIp`2RI?#1N?C2jp`vXqr z7)jL|d}SUujPbc5ifiNJdAXn!`@yX$hp=RW87RigL_bx1KLV((}dXMjUirK z8fdtMXIYyF$& zA8exrK6lCaepAf+p7whJKthNAqausv6P`{Af#n92Lhk-;s7rr#Vqe{YS>#7U$DL3b zzM{j?(FbNd6o-vx!mZ?^)yRC5*j+i7&&9MW)V$Ob3tOR|3b{pU`oV7HN6yi?r5lM5sV`$QSDwI*3j{Vdm>Mx!p z1<>>X&9NWH906-TAOC&^6jd}JsMq<>h1hcQQ$E$wC<}Y~s8*pW za?MnPz?n~vEDRwGi+FEc_!m;~g@P}&HtZ3v^{rPIK9)!j5{F|c{A00u8AoTPA=7jO z^=WcNaj=!nu9}N`3&t7d^57K;cDX1uUAJCDd{?@7m*#%tf410i6DQ0n$H*!y8=CVq z4hm-?dsLi`+I|urFP>oAHPCQ$m6aRX|T&T3f;BEI8+i^F!I}x zh1&{16>-$2%tzQEStQ4V&Ua2N7a~g`%yJG94pedVyJ2t(hmRs38#;n~fF+*|0S^Tn z{!dTam&1W~BuuivW(^zkOZ_`j(eguUqMu;%zcle?vTi01zItDQZ2@A)a3c~IH|OF&k|RL&wBm|!)2?u17&J4ilD0_e{UC2vTJR2dNQ!8-aSYu6mkh`I(GY*kcz6a3}*__bT=~cW~pCP%2Rx0se+3 z+gc1g#2vzh9qDC_B5mW}R4hzlY~`c+vBC_cZ+$<#pEC}Ig*gSHXi>*e@tJ?$eKK?1 zd69Wm^TWB7;!W^=iqM(%09UwX)@;bM0k`%-;Jp>;Obwp2+{Mh<`;RjN=^B>_(P6~# zMfL#w3{F_HiS6T^b(xL76A`3zHb23jDh-p_ae<7gqa}rp1-->DEBBNlqn>F9@R^xo z`IP_=D|~pAD6lG!?2UAW*R#R@f^G0XuV2AI8D!vV8PZ`#yU~dtZ^_Jy1#}$*`mu!p zW**W~Pmlk|0xSUwh6wyvbkeK&@~6^i2!{di4yWP^`iGZtGIS;RMT%tI10H;_0}MO` z*k#&DSg{HofkQqR&Wi%ts#U8-y*3|Q;1eWo$+cApIhOSb$F{C}NKB}UCHnxXi-1N( zN5Z(z+9MXe7sm3sT1Z9yR7M_Q4iv672K1xU$(k@8&aXpNeG)Z821v>Cu(>|NbMp&? zVi8KQ`bv7a0Y*%r2fcj2R)64=M_z1qA1_hs-izd~-qo}X3y&)a1zL^z`t;EU5%$h> zEUARt86jxoF+W8&NOrGm!KlJO%;=!gh@1hw-up@RFGr>wI9fqJ3u1I_G*xk3I)S{% zV1=*!lPVaxJ3U&csz%;#57*(NU`MJCy(i;M{ZVfJK9a`V!d#GA$4%3A0+d>Go}_iX z*^~i_xo7(FaY0DL!GCa(ihQ}MjvBXe#UJHNVmafDcLGHis?(u2%w4Jg}pA!+hB z$7S*;g2fB%CH-)~l|vSu(9j{CVVX{eap?hKY&cbUR&6O#MF!qpYPU!Q1f8L{Ae(29 zg5(GHFPQuoo=GP7*z@bgt7=HcG%iB&b$_s411<-YekEC9d%RrKaPw4hf??rj!Omcx3j*g--VAfi(o z{9c_tlCL4$$Wy(?*tojsO+4j;eTZ!YrlHlzj8#R4`{hSP24>&iNY4NR)2x%s*SWe# zp?WonRmW8g{V=M-yR_jWJq**Mm3$5J3*nub0Y#jmB?ov&}PdyJaOK^0Ap-pUy1 zX&n6=(2{zttuMFw>(nfltClywhj{lY$q{K>2uV1wK$pD+P=EA_swz|={xs% zSfAa)@Ut+DHFeK)A@*6^r+_Z(H8djkT?A{fpIFD~bqaKjQgPbW5HM_(gk~x({sXH~ z3ppX%JhiWeWM;?9yoH85u6B}0!9GUtlcdpf7D$1A2m@>_vwf;{WvNgWV2D(Pd(J=U zf_LH8xrRcy1D4yoEvRK9KhypIO2W{yU(mNffpBe-xGfruL%hdxNvBGGNEU)lx*DJ1 zmK3AG!z~xI%?`7s_q%xGEH2B-8?)C!Fa63Ke2SxEewe;z&|4Ld{p^98rj7heqDPBT z#0^@Rg@3^b#L@dOfwD@`Mm5*uXLf=00)zmKJLF5xb(2}tQei8q7F{hMiS)W#=}^@7 z4?lJ$vW1Q#mD}7XO^Ob^Pa86bGkkh|oS+$95nUrI>x$B$Pz>--8YI52rB&w#i>yX4 zYL|v=c)Tw%)e(f(<;qa&IHcas2I`5++;kg2(QB7w?D8G^Jge&M(%ys$)} z_A>r$(Axw1(x+MjR2XWxR!rxX_{x{Y5O8)IFJ^Vic!Sy(s@m0HKOQUXnAusx2(M%p zRZ)Xj1S2j>cX{OCTg&$}d8S;gtFMdHX?_1uPlOPU(AGlO@}7NcRs!`#ZSj&#F5*&l zwl2%qf?ktC1lqCn7VaZ9@i}x*XSM+5eYFF&52qo>AT*D&TkI^m!!Pf9o7;9S9_aUc zrZ(kri7eboe28D4_R@8BZnkf01Knhw$e^lcl+D|0>%4pC6e zQ+OSI+(P{yv<+FJDExv~oj%;s9uHGx# zNom|@T70)5qe@mLMA~u`Uo|OBkeCO(;xHiaLS{p5MaFyEDV=kg-ufdG{vvYs@ABCw zw*N9O+ngAm>rqUgz{qAPT->UxtP&)v)`!0x$U@}I?$&>eUI}6GN&fjLtAC^0pz4}0 z+5FLMp%~tzLd(_477`0*4-(ld;fSe_k2YjTr=-M6q5kyBi-51VP!J?qk=836q{Y5P zS@F#p4+mc_*;Xsmnuni%F4@yFHLP^phFRBGA??N$dlU;K|SB?G6L2~*a z=i@GIA>s<#W3GNGN{<}BSQ5=A;uAz-=ols-tJH-_dM>-bmjzo&>iIS#F-gq^SAV05|ZPk^rBms*Rpguas*umCNDhjVz1+`l|}NZgEHO@ z-U;AW@G952FKvcr_FY~&vp_0uGj-5^;Vw>UD3NPH<@N(oP$McGE*QTz{`>73dI4x} z)hd=@%ho|X!1iAuRGpOTlYi?sE{+|!@LGq9=>p8C4PX6vCA}maTt4KU$NQC>)GEdM z7%{n0Ua=^<_kJ=yZZhvdmZUh_O(|_$?0_29Pg}*er_^^5-u6HkeypVaJDEbYx**f+ z=Tn?*xt!nCW31|Avo@=*-K`RHiqu{gZf#Xj2R-7dis$@Sr!ilqGKc4gqd2b_&LH5~ zP8CcA+9cHRM)JX*vsK_k|C+@ki4uj|K(iP2q* zP&BF%d>;;wrczaY=>70%`gQM*Jui50k{MLT5H!$T7!Gh?v>huFFzxDV4Xv`Wv4fVL z?Vhii6wVe`y!p+PptJ(aH~gbot95}2J(5efz{0P!0tmTODx1L<|9I1;(esO^0<1atC2uN1k zsDH*hZtnQO&2qIYe;w&quq;qc%Da2NUsRMsjDJ38UNA^onAz)CHEqbE=`JW^)C360vuc>EF}ir3q&si-na$$Zj1HJ2FG@(t{HoEfxP|c3v8}$yIV)Q`TPmlN z{Z~~`lQ{ZI(0M2Q@LQdFv9$WZdAdTrZmuUsbOmsi2jRTZ%2w#K@%|IPWl5r@<)6UB zR6h9z>my}F%SCmbys9&%i#)~$9=MPrrp@QMfxx|@L+Tjs46oKy+dSQm#<FZxL0elvhej&?eYh&fIW*POxXxRVr?9fGlAe654iF?(<%JQ#Fww^8O0kM64v-x zShU_?Kk(|$9LQXNNGz_3M5A3OHQyltNbqhEAwXA#QAY3ZH^Nj4rMmzg ztmqVcHmXS+#!#Qa_&Nd;Oe((n4~+$Hb%gS1&?@eMl~K_zeYh$;@vt5; zX^Ys-@VxdQpFpiyBZ+(-%vbhaW!3x`!DXd0GGUU#ssNNFL$yX3PK?P%4GQV>%$@W|Ac<^})MAjw@79dR$9^McV-d#I z`0R&lu9+#z9`{s>kXqeiu0Ms91)R;puZc+9-%RwZ?Q@nm&7<<9+wL9%!1kr_omPzS zhvf`~84dS}g*4?Z3=S?v@g+7ylM9z9TB{N|5jzGC^e!>dghT5>>e;#r=#3gX z8XF^RFF0eJ!-FS&5anSMyNH|QjmH;r@0`D&EV}vEm(wO?#3zn=z3q!B8;|}}W0jg) zU-alu;LclWR-E`!K;04Zlfc7kAwUjixegMI^{&DH9rmGQZ%}lCGlyo^%SCj9<$kqA z3Lf3|t*6S$#b2%k&B_8V=$Rgjua@hVLru=CNo|68_6`6yrDazE0x_o5z^fp7hWET6 zEUX+0Z~_UFEvUp|0t*csSXg^o0^!RJ7^m42oDU`fJ3Hmq8(SX6ZsMmZ z`Q>baI(Q!*-ug8OZ415)+*V;pFD=^IFQTow;3X|qF^Hcsp`G&3c-rsqd`wyK_4E<% zL;~>3!~E~{wCT}iEO3(ZxWNWgF$peMHmLzLx_teCALs4c3t98Dp;ezTc<&z{_&pz2 zDkn(S#>whP4it|fX`jUqD<@g*xNBoz^t*#aNR?C>phTUqfA@mca3@<-yg)wf&3p)K z;?m30>3*uJr*Kh%1|3mw-Pg+SI(#4@l!FPe&Vdhx-+p!>hQ1m?;<->VSEBpj@}|*9Pe}^uYxd(zOyJ+oY=P}e@O=|vS4tdxJq(>Ep=$w@ zdT$;&LU)oWe$N?DYO-yPICb`2U}5?oH$UImVU5JY3GKwO3-V!kbSCHz<=|xg|TjW zsPAX0=Dom_xK(;0SnmzL+3hROsrwMV=0fi97nl~Qi`gd@mwX1KOeg6gF9g-t6lOyO z&=N;5yy8k*)vfSHi-sZ)I(nfcG$=amw$9j|Eq6w!WuL3Vq-B*T%pY`Fd~=(Q%UIgp z;US#Ph+UzW{YtxRU7uzow-ghU-Lf~#XZySt4>bKoeB)&Vm%pNNUY@?RnA^wz=`02C zKEs2Zc)Ma>7nj147Wf)WHLe5Dw1rI!!Lq=OrN9hH)?e_bA;{8&))usl_s_}8)FE~& z2Fr$Mx49F2N2EjyW8@5TLn@kpQVVb`2^1gvtKAlezB>w}lsdmg!Qa4Sa6zx}O=A*P zsPEocqya$o~hwcUVCTNH1{zo7Liens2tv{k&A($Q~keu84N*kM*DxIYES^Q*D9&w@DzDGG`2P2t4ap6qU zd3^FS)6%+o$b5z)yF!NP!&NYGSo=0Z*_ronK)SE))lffHLZhAeMxGU6&Vu)b;zsQ6 z4>D`%8g?`(xpdqGD_C>(z8=0-mm<~z`R{b62>;!EKGfY~Um&m(k;rAnFNN+i_>J}b zodg<=^YS$?`GH!6!RWFhInx;m=><1oY*H$6?;V2dETT_$!V2L<+0+s~&y!RpmCMUg zuajWeR_#S69`b}1BxNs+>@SuQHSgm*pOclvXS#-O`#rKxi%--~I^B52NKxn)_u4iw zGad^^vCSq-ct;qZj-z_G)g5I-8a#|@vO@*%^y)Ufg#W)5pa!T?h8M(Q|LJ}D(5`o= z{ozME`GJ&(%@NJKrUu?ggGpl@XXs4!@q$aLO+$Ntzc7DlWS zrKpd?|L*|k;a|p`bPeTkKFNEiU->*pym5laAYz&Y_8gwLJW4PE{$la7W8!@Jz{@0I z=VIda4O9o7xJao@PIUfuyqkiFW6CC>W)c5R8$VFHsCE+oU5XMG4?(|2F=v4TlYR3H z?xx0jYhQ+?_Tk?1y^7Ely-m?UrRaGU*ZfkYSLtLMT}_SC@d3-?Y&0OG%I^m&VH3wD z*erof-8aFgvLMO-T^So_o*_UPS2$(eDHXrhT#%d4m%P9G5W%VbzskvmbP^MP@f`kYD=a{^%;PE%b5AvS5 zTyJ!zwQ@ysB?cB0L#A6(8ZiGnuVk}hC+i563jIrI{=~y(V+5Ox@{4w>o9FWj7RLPD zKV`5VMBN#L_6i0#V-kz_!7e`$Y0o?@cso;MN&Xp%MAby>00tE zPRKVJ%hqQ1R4TQ;Be|r?H8jE8D(LZp1iF-|iZfb=1${%pBoDD;hE71>;MSBwU;MTM zdlcG`v)t5P1)HQ9VDIOJ&YRNK`&7UnM~G7nafOpND$B+I%C7){E6~>*0M$wT~{A3;YH7lrK7E8MrYRjZXXlTwCDw1-OZc2C;eoC;` zOYpuGcmBoa?dnQI(*Dg+ir$6)%qofuf44H1tuD%0)lmgRZ)J~CIDq}-nb?I{U*6l( zlg(}I2dv$#`QBZl+sA*{$a?wWM}S!r%%OGbWAmm-Aj!yD-9PjeORli{YXW)Iwe1}g zMzC1%$yVb}9B@Ga(mw8;lKyq7HgaJ^=m8?AQj(EW|5vuS!|>DzeBETO(;ygr_f|R8 za-7zdt{r3Gu53$`BvME-F5P0-U8?1QOf;0^ujJj*c1 zDK|8>^J|6C_gE0zm6R}4{Ny!FK3^TK#-iJF8gW65TNmgFH;M%ZYSbuWbhG4yJ7R1> z=SUqDr;BWU14b@@i=D#Bc@kEncPnwZ=}Ox$=x@>7Q<*zAYS8XFwyEY;+V;Yv{5oOB zv-x(%Hqifnkn(WcR$`tvsG{e2MkmZ~abm0MtSVs^#7QuiL{0Zx?D~%hc(oFA&kX!z zReMr$M@_&w@PhvEnij6&#BTMDSY|xb+VF7`M5M7*S8z&e8)C-V#}RDg z2j3A)Z_?=SCnvjW#g&Z33GVrr_2UZ@^uAsdI2WSvv=(@pdTr@yDy+!$XDa7Uy@SQY zx7&z>+%@ zNZxBrGf_(V)@Ip+rZecX!{Q^I0OC)wWZ?A!HaZZ)7!&!&5mVngYi(?GEYR9Chbi3* z1{z36>;!8{H_Lu0dxWN;5pVKa(M`8mk?#%8DAv8j#KEOZ@T~U&SsVo5VFoG?R|Wyw z^e|A0F0mQy1ZnV`h{{L8FKgoV;G9R`PZZb5XaGvgs|#2w1H-+b?}IzQ9#tiEQ9As9 zX&I5hMKVK+|M(|78jqm0$a(37PE=ARK(2DA-nC_-Jnn=I@BUl_0_E@ z6_!$y23l+cql}1}&jC1){qc=>gyrM7!KCk%Vz)2C5|#utSV+N%*`T%M8elzzMo{3` z&P6!p7q(G~nGn?+ndnh!_PZz*f9Y{0?-VqijI;M&SPFgNOxaF@>3|Cwn<_Xs!ziA3 zxL$sVWHELh$@Y8+rK@_{PQ%`Xt72Y-U-8-AL16jwpz5;Gl~iIK#F8mFnt5w_$u5sQsflqK%gs^$@opWVF*L=LaccO%+61~-Y+VauEI6Sg#PH>_V4SG2 z3V8ba1HQniE;V7bLPA8xmNRMD;@1bHq>fQUYgr%HlVb#)KkmII_^fY5P&DcY8u12% zYNfi{J1c^jTs_bhqjK&BW>kQ0WZ<-1>HkPd^r&5&ReEY2WpKsQrqS0SIcDH zm(ilu<8do4gBgJRO>kR+Tt23i=DO6!eOk55m_z-Trtm!f*o@maiLv^2i6gTtUvLR~ zZ+KT_&HU)6TneR4RW0l*keEvgHouzvi-eg*09kXe^>=2bV-#?lFYmq!c+~o6xNhj% zg5F}@;7vgh6MPLTyt}ly@oQr5?JpJ)_9AUN$2P*^^)JJdscIpFrAc?{`o45J_}G8^ zmrj1?mxe*~6)}Ecj#AZYNLaGr<^pTNnnAe1;`^S%98B)yoyS=I^+AwN^0{%@*`S!G z&@rsv=(L$sb?Ct+{v=I_K;Tju#K2?r?)!(P=5x1j;Ifbh>sXf$hlClfBwX2P4z#=Z zQZLbZ7ub?L?`%!47r4$fn@tCX~1#t)e3T>Dw^@tYmEy=sy*h_ z*SHw*mF*>@OokY7ju5CV=6V`o#mLunJR6IUjTFr3MHQ(2#g_QZUMWQV=wSp=)5-TI z)v5Ycs1!P>tU53U^4Sz$^Xbz29fT_DGp_%Gz|>ig(peyjs(8J`f zbz>HD#qh7UJ~gy`2^~Rdlk{{?dE& zZU{wmN61hCg^`h_kM^sg0!)={^IxRA2KSal|1jg93Q#FV=B_?AgPXo(O+1c^C~f95 z!B4BJsn0*oFT`M7sjxaRDq?$EIHSanT|@`YN5)fb8?plcbjb|@cS6Nzo40pr}Zn&rJx?(3h^o*nf`Xc;5?Zvv1UJX2Y;;1<14Ol0XlQ%xZ&bXUf~Y z#Pp>C-GpF{TG;E4SYdF=O7F8i*MOzWXC_?kN>xutetDR%Fg^8305REo_}czhhR$J8 z33GS2lkNRZVh*?E<9DrmDlvTXqqaxZKXwi^ZvFa{V!TrP1IbTEK+LfGN(uSyeO_<# z{#U9(8MdEEaHu-LNB;XGD`UIzC*^I&vEwg(mgw|YSxFImqN~Kg_xQIanX(i^Ihom) z;GMzuj9dcZ0aHwmjk%V-IHJ0@G^WoHR#fXo<^+Ilm#_~o(K0c8-3_~ z`eE&-hx%W17wrDXHwOrlO-a{$y3G{u&I(fYqa3hh0!Jdi5@4fWH^1KJ2W~xzLZWpc>NgL5ZZ%-FRQmAP5|sm^=oy8A08U-Qblcrh%(K`24Ss zAXy3sM8S+1|6}3tgDw;>r#pkCdV8n5Ql9Zlbci&TM41cFDr4l=q=nN?06-EjzH~hWUq*s65 zcE!40i`h^Y=gUw_+{_>OJ&lYku?CVUdYb1SzzMr7p*9@SOBPjYZ8Wvk5h>yqDwL3! z7-ZmGVKT-RI{lu_$;xaW?6C*?f?RfW;0v<&!tGjTTbtRgw9K*Flh~!Py35?93QfXk zbel@9PH%5$#`_iy2t5$xh#Q~&en1b!C~%Kc{AaKk2grMPF0Kp8g0K{$`{x#~8CNq} zBaXQd7v2>sKhrW(Apog>TsJR0VH1v`%l66gDdF`fhI4oM(yixv!^|g+z*GL*qzh&+ zy^#1X2=e=1fm~~I$kX2yU*)19=f(U?UAkqpx+)W#k2p^CzK2auWKM&j)I!RIiV~MI zs+)}=yFPYQxEtTcjZu_DEZ|&iR0jBV)Qptu*d$Eq;EdQ8Gc!IIVYomWjHHcB+T!)t zmOVv~)S}SHgzhHg>LKbc;%xuM**1NVJmzn%q1-1YhEFa&+0Un0AI=A06IdilKSjbGjwQg&>ziD< zk_@W0JV3vu?_|(QypFaR1wA>n#7EJu{#)VL;^pEjPbsAhQDiqZ9aP_XUE+U!WE^@I zr=ljq-{8d|)4ga^65ai@*TD8ks_3Jhpa3vXp$Jrh% z$y}vkDa!|8mq#~~TuDh1^>`z1Wicn|)jVfI9;RQv_+B;=iq&E~z;@WwFvO*%+u1O_ z{V`pkJTcvq8rR%bLnuD4q)p`~T_A0RAK&{*tic!Nd~gyU*Pn>!u9c_liA}fyELJgK zEFEkZvFi;*caQz!GrC|gEgm2zIFdCOL)%GQ=#W#n-moWeZGsHE{8$<-o$xNqC3K-+ zZcl`o){8*gru2ci2(@5ow&I7uQR_7r_qkDN zJ@a|PEKD+ah0tn4I*cgr?Mn9mqTs>sm@)FNbm=WZ@p0B&dRv_h*8hZa+=`lN6)O=7 zXX~v5NWaMhv3rN#`3*Z;Hfw@N%*Rhl8&{T1*0#T+gp@@fVMOHJ-T-E2#{}CiP!Zn# z;3pL-`Um!Qlgk$T3^GtK6-f%$Hv<_)?WD+j?F|NEQA3rL=cJDlJ=C|95fz5NF7y=T z502A}yG!-55As{=ad6|M%WR|Z@>H<1ei-jiL^|^uU=h3wrFbg0u+APGwIm!Q_=`Ew8YQO*CGjx}9cStwVpoA#h z4FV$F9WzKsgLHR;bk__ZASD7KD&5^6J@d@{{r#V{W}SEQ>YQs|dw*&xf!6I7VykJz z5-jz`eYW>wJ;^M`cLnH(W7~qN^^{%k_CHWNANt3cMAaK_TW-w4mTZ@_b%{zgb+(CT zAtqp3cA4MZH4G@CPsvY}{b|%I9|&RgvC4U}6Y}fhCfSGd6OEWiB48u=uek=6yqV4E z&Z@EyYc{j!KVSpM*aVeI(l_Q&7qx2os_7Ol{c!&jLO);7&94xG=`!Zm0;ZL4_b{}z5ISU)E7j^u^H+S=d-loZ?uX0*&?4o@tf-c zG}vY~6Er;TAWOf~pfmNGKC;Xnf_JR$(eF65j!;->KpZdMPA_h?(^KZUv{Ps$5|f69 zsnYy;*M_fkbGh3!kx+XQp;x^YIN?*kT*jqyl0XI)Wv#T&cE?)eJzEsVnqF3u%^+>W z3Hf_inDBUP&y5IrLa*!A*Fg~C(tg72?0+QS>A`{b{IH(6eh)=7TfUp=fPoER2;*M} z(YFkxCPiKQ z53E-M5Y(n^7i*DdJuoP=T?EwxcK|Y69;jewqg5)XUBDL3*GwGK{w|gW8=OpT*L!NsofAV=|*VX9Y}H~S%iYV4DG=@BM*YL^7H zHTNsRPUc+~`Nq3uF8=maQ$-cjeAWN2%T-^{Y(6TV3Bn5S7a4$RW?^dJH zAwUrywVX>!&UJJhCF4-EVeTbPgGIco{9p?II~uWQL1u5x9Dp`VHKOs!rOx$CVKmwykZvWNvn~m>h(god5JOu5g(Z$-#r0IyTW?o2g8z_tT6*o+Jck@~T)vKiB zNj2bYNwh6TkwF!;o6tU!(nFZnDDr$)PLkz0qt?^2zXXS2AuS^h4%g5j=Gv#qLlPO@ zAD@&~8dlfe-(a{S@QsMWny=xc{VJ<5$g*<*7CT!NLoD#k@!kACver{1tot#@wNrgX z3h%@fZDhH}qpVxqqk~*(fa6v` zn%r1sgQ0#>x&NOpX#+zPnmO5PlQG-wuigOy*OA5Gq0x%*MpM9j|D161?qzH2ENVj?lQIKaq0jgQ8;R`99#*ncC=|ZNUBV({GO%p4?(gAl&5c4WH0?lhZ8U8 zSS~?rmdtrc)Ud(SccXtPF)*yu-16{$%HC>4$lSdrQh^Z#8fJ$iMo#EH#qd55k^4W+ z$VGlS1!{;eFeSTYgz-LUhfglXn(?8SLj8IJ2l08y1iPr zcfa1EM6H}5$3jWEtF6sW=o|`-jH_q)Vb1+%WI9TX569O)b zhmZ9m){^cIy{e zL}d4xa<9;4u(U6Pf;grT!6;AiIyaThn^CjB10VjDGcS5>}|4zDXNK ztF@aLnYc=yF$^yPw|_$-VrXywaUX0wCJc_8_xw5tb}C=_Djq81`7_|lzE>n|sdw7I z`mEgnF*xCXe~cOxQuZhi-~JR;>Nv4Jm8D`|b_P5w+knR7)WGqJp=<8TgFB`f~L9SM~A4mFI$lxe_7NZ>xuafRer zH^a$*;HrYH;@YFO)+`nXwbm3&)AZ019rwvA4eu;j=XoWnYL`$1)|eQ8PKuQp0kB=x zSn(mc6XSb+E;=XzF{XKbSEGvd=fX%Z=95J%(VDZ2o+&1l8m{~znkuyvmsk&aQ2|l% z-gw{X*AAKUr`xSLxp++(M4T0U3bm~8I}8M>3Z678g6+;gWCv?15MNCw9c=AoC0*vNT7~RRO3EjPa=@o_OvsytZ$wGinLAkWXH?(6U zv{{%ZpR7TxCF?ow%)GwFy7tSXrm4Q}H|Eth$A|IXmJH-wkGNOEYT!?c>p}$NtFdIq z97Xh;VfVeOmUU13-AXV1H4y6iyHnpbXm*RMha~7lp3E1WO~0$b8mE$P7>uJkl`#^sJCg9?faroms1} zHV*iZCEba<`homd&KJ&}@i?@QVH|zrKye{0?4XY-5#*%?NL-E7aRH6|>V}ZpzG>^u zORYUD(tkwVK}3VHihCZrce^@%?`_+jK{#3GCx|W_-&SF8GUAbK;}q8fDssweZcfd1 zS#gWXEF97J84m13!vE3a50`CH6C6BM3Fyq7h?+k|&`|v?N4QYlSrZKGk9(6-RfvyhPBKA8x%oaul-puT|6j{YU%z z3+=-!`Q!J5(3XXk$6>sa;}8+KsjcX(?!n=E88hhl;ceSAoOj9CQk{`94euA%{LQ;Z zK3k%A0;a3IWW1$F+EshP!oj#h7TV!~x04{w8ia$a8a1%Z8a8Rf78W*X{-;}L?5nw< ziU|u!($ah|npF?m(hKa){EqP$llR}eY>lrglNACDRx`=K<2Cm`*CPU#_EwXWZte$v z`ut?FO$g&FBw#E8*Ig7Q-Iea!z!e=hsGnYQAJjrux5fTP2wv=ooUK~HI0m3EOG^Ea z&4@2D>(@^9DhlyDhcpf`8NYt()p@xECUkZ)bV4ZLEwYCOO6Ywua<9edV zb9yO#!wvC4fCJlqM^aOcC2Zs5a%s+UcZ7GyUzWk-)$3AWOY7a_MGP`Q?i$C^AHb4? z-MQaB`rz04^WtZ!0h{P!$$kF@MV+m}PhsDvuXT!OSuI(FU0pQgnTigv%sUhqG>8IG zd@}nxZ+sZQo!a4m#tZ;J;2Wh3f0iB-ON4@EAyHLEGn5{3UpRTW_V*xVa7?i-%6oLPqr>_X=QyuTb0zjhrx zg0eA;B5wgtZ+dlLzlS#n*yrV{Dy%9eWUNcT`b9EHrVb$0BBGi%<^d?H|4vRvD+}*D zg74ozqj?*MS!~uIOsu?+#2{w}`DxU^6?XYa8Oehg5A>F#6DXZqSMpa-M~CHCibP9T z!Z8wHep`vT)qLftr~%>RfY|+x1{8mp=x`(0riCzL!olp2Y$BFMIuuu060y&?{uZ1+ z3hT!P!Ig=(Zy<6F|31;vNqUnCL=Y@=k3!)2M$AQ3kGp62M>m8aUh|eBnD5V!sJHOb zch>2SZkMo=`V?3k7Dtib9y;9DTS7e9aw@GyOkOy0o_^F= zQ){(+9b|z0fd0;b;&7uY;@LntHJ<%+D;x942 z>$4S|%FOraTVF-{nDgw4a0G9z*2^LDTAa?pNa8ZQNV}c-X-hi%#uKHZ#b*WZbZnGUKGBxeV zP_j<#S{X?JaMy#5{73 zP_*Pn_y3O8tDqNH8yONV@@ipk-r7WUO=7*iaYl+%%=$`K6|1Pq7wGxK{udPos zuBkUUpIyyPWB-CT%tp_PCbQzPR_NE(o~@$F1{8moAOWROXrO{fvHS`wf29ZjdQ*-e zUl|v$)ObV(qLtnYiqNsn0T;E^5zoi3Y7~(8ZOFN&KtM(vvFJHt7PMXT{c9 zvT$kO4_KM7B77a3G9j>$^%eTrCoAMRA0*m4i9=zKhQeGOj2 zLxG%J^vI#WgSuGn_S%=KDIPLCJkLt*5+(eMk}?$)T56_=INHDtqSwdsv6_Va+V};j z_6}LW4FjzXAFlDX7X}7r1{)2Z73;B;Y8bBc+y<+-Vbdvhrn$U$14 z2Bj@>wnb4iRCT|rXTfk!tK?zDu$MTXjh@jr+^Jc#^jK%7c+pUQE6 zZVVRm`1X<`9TJB1In^Lcb+3kq5kF&h4$XZ*mhoR-dfSEP26WoeI*_Nr=cNxcwh_!PIU6PtXKSnTKco_rpnSxd5^OG>zT! zmuDdRkeG|&D)lWk0Hf4FnbL3SV$WieqnGuZIQMiq|)B9TNUD5p_X$uFG;|;xX$GUot$3Wwqf|!sELf5^UzHgZdPCYaE-<+B`e^x4kk;~!(DRWQO&;Sx zPd}b3GN!nTuA+$!W|&BhBGK`h334+R-MJJf$153Ei0m{2MA*;(Di*Y_%E%}L=vL$M}1;eTNVOvXopP{BQ0Kr%ZPr~6Og=fpsME?Xw=Eo+8b#NXAcL!SngE`u8I~xk*Jcm$-e5iA1oioo?x%2QTyWFo{ zn=zX~^&+EZQcMJ187(JHsKB?qkMqVbV2ht}p%<9o0EyeHQ}xK#zQ8NIm)&fh31#Es zZ>x+wlzN6Fl1&Y^#`O8jC_l{y+tXP~kbQ@}c+ya*eY{IFeyrN$P}AXqZre=I)v~>q z9i_bw6#R&;Uy#CQublg5OLw;c;=6j-EXDo4cWqtghWs|JJ&c0%=Og_jakXf5#{yX;xmvD30 z?V;hkeTaW8oBxTnpzEIpt(z+|iOpKHyfG#0B{2}eIT+?5RR5{_wC^QgS45X3&t%C} z-S^D)*X|?@I@&Au9UvO(GfEbVWMZ`O9@DON} z-j%*JWw=FtVJ05@P*@n1*5l&JatK)o$k5K#=a68`cV5Zc=}w?s<-&r*MRwDnRK8(<&lo08R+4=ro&#gUDU9h}e9-MT*Wg-`&zo zwo`y3t!#T8~2xLs8I-6R*K1)|#F0p*ipKp5WV@ruahH-Bp zNRs^U$}8owxClJU z?kCbH2`hOKj!AFQAH$ATnzhTAC9I6e`W^&WKa|foPcIw1bc+CY$+0No@6yq0$zI?0 zo$a$1B4*U^jF1+5TrB?SgFs5jrIQr`Z#NadI-BZ+_O<3hJtPQpU-EDD1q)c`z0kwc zV(n888sOQ`07uHdkZ#QrZCuHdvr+txot7L^X@D}!YZt?JFFBt^s$*^dO8JDUk|%v* zBLUygWLqnsK<8?BL~eorzW!c6dvAE_`CMKv;UFO$(F$Xek34us7n{12pm!MzYF&xlT(!xgcNW1vI+$QapWn z>yye|Q(S!omsLAjt9#B#C4EtBiwH3)>H+k~vLNFm?)M}V4&I5ro=0G+6e6_0od>^a zG(g0_NXI?Eib9#+GTMoLw&d6Y1BQCd5(XdG!b@}Nvk#f34hog9G2SY*ES6)V z<=TB)^E(balj|-77N>@7b^S!8cd>lZ_ms@?8yC1l7>9i`_1j2YJiLC>Oi*f_n+6~ZDyZdav z{{^izhrqO*-f9ukD8!6WnlGRqSMnAj%k-Ty2X zD=u@P;^BS9Q@?U6YSwp6TF>xEE;d9QjfKiq8tzbDcrX(bpx;*sNsJ06EtF)RuG;w| zoruC{h+3>6rhIFLVF&r>E*~M)`YH89-N-M=S|Ny$h|d;5c8i%ls&Ra?@BlyD$7G7G z0cRS59SgQkrIq3TH~xkM?B}h4Np2tPr;Icqp_t!m7YS~9(BK{7abui#W}=2>j@ zHOtG_s%I

SFK3C5hg8{Kso)+=h(>V5Rf5j>z|(0c*2El}bZ2tb1FrND6hCRsj~^ zg>PSP4vg<3?~4kT&-!t!2G&@SQ;mUFnWNR6BASEY$;!3@6V;n@SNq zX$pfD=6Im~Yqz3cvIF{SY{ErK@MnF#V;99 zu;?l8ygw=4&k ze2?lMq1%QsBdP&Rxc)Lm^Yit2Pg40ys!|Gf4BM#V%VB#T+kQtaSUV3mJ)Sji3}%zv zZ)l#sVaDPzt{Q!s@9f%W1X9C1sUe-UO6vG;SiiyI7l){sh`RYo4u9=+gP4dK{kLo$ zPVc(h2D!Z1@sTD!ewlyksipR-(|ANG0&W9&?I_+M^6@WilyU|JC5V}f11j+teLlT8 zbt?5d?$imT7%^~0t$rz*h2zjzh;woS7U_*QxKw$HU6SID*78Un(bNACt0KMMNg65+ z$L1VGO}se$TcNZRyU3P&pu+Y}sJ5RVipGpG0uuMsCqe1T?t&wDML~u7OmP%TVi2Cd z@O9=1!TZjSs+u3zzrxP4L61b|uSDHqL75o+L=a!bE=^4ojLu|8@2mH7D+7q}{%Nyx ztT$L1@qN#&BKy0BUOy#$myC zlE!0QW`S=vs9_nft`}tlNEXDmSWbOT}l<^<&?IAA~&nkFw*z6B0j00ZRmWwh@(>kr2bbbb$ z+_jGxZm#vLFpF)2DLxGY7}pum=@@`@kN?E?O+>_ZCM4mMlqZeAUCqDul_d};#Q1Ug zrSZiJ+5rXwGP82_>~e2>9GG`hMr=eeHc`FdxcX4jWv$eU>T&w8g^l4YU0Ia5{aBS; zg3?fdeucKT3V%Z*5c=6QD%S&5t+jcttQu=jebKuTE!;_s;0#=VvIH4Yq`<@ zub48&ut8UBoS^7>|GPIa!pEVL9!aXs^N$ zS8}{)R(ivT8YZo5PA0_p38$se+D3WD#$b6z&C)hy2d0EV z1+poY^yh){0|PDJJD3ulH!)0!2<2V8RU%{J9_T7|krQG90=H~vf=HWrKbHKnoJXKm z`=R`sk3|^Y+`f@YEIiJ41%urd{pmmb@2mJh8u4d6iDSu6)@dhaapAKgG-A$m>8v4C zy6u2}f1)>i!!9v8*Gb~`>&?fGjuenwP0$l+vPQWv3MPLY(WmjySQu3m_k&5TIxgz- z=5o5K_y=2^lkKte==IX)=3NKMANOCiqG(C8xw-}Cff|Q=a_vGUn*?~ zl2=91t+T@Z7JKbG2A9^hlBcShx1n=5^T{gCY}p{gZ%$KFr;5e4@sUr8e-L(zdvq_$EU*Vn{QX(Eq7invjuS zgFOyW0&@3uiE+_+CH(wu*Z{98pwM4!hNQ|yBuD}HTwM^W-79C9@S@K8K~(FPBIb&g3#CIBEZ|4PG2(9A20+mM`ji= z)u_kK#S?vLj`L^MXhq{cqhSkh$&rcD2P4U1#QTIfRxrm|qP;@$YuONsu|o(o?HDHSm&TbunL-yA~E#M3MTp&xUV z|GU8L$fT$I>{|D!$>N%GceVPk3ZcF%Uf_3qlP@B?g~UxAu|d5-hbtC`;NzA1mz&?= z!!xG2CU(DVpAGEWk(NZ!jN<^p;Wm1T5FTL7C<2w4(<+heZH&2r^W-?x;GqF&yxqg} zF|*31d5L~cvOtB<;ToOpsc-ZBzXq~TFK0*C@16nul7ODKI_TfYiDdN!|LM}hXH)N! zjh&$~dsKK*;lKa~l;WHfX}!MU4@dBzN%S|t#y-n_tV0O|JutBL~(yU+F|?7m4`x!o=mh3Z{8!D6Gl%=+8P!b`^+1mWHwc6R}>B+C7QbQ_|;^4=sb z?0X&3mhmKG`Luo=XSiB$k#dYg3W;VKB^#ACr1;ep|6PXh*4G} zV39va*{el2EPkeXkqTWesc1+!cbPTWUl``>5E4s(HD6D687Y_S%nAnlJjLBJ2%TTh zo_pgy+wlD-ymHyg8fFmg3g)NiYj->pqHYl_K|!KR zU&s_i6Zn{aeQ(^)ZU|!h41)PeEeDs$Q`Vp{L+~4BmL3{#a3Mfhp^3%0eO8)R?+!-S zkmCa8rnfyr%nB5DuaVrAjbtr5z?fR_3rF?I?B?Hjp42ZOc_88f(exeyb#g-a@nMbT zlAvP}OKFl;-_=sxM?PmWSWid%&Yds*hT(ljZDcE8R+?=D2WeRHo{ z1Z^7xlb^|<{x1j0TXMC^^sZuVMG{S3UV~nqv(iE0l}`n^g|F|-|LJ71kk6H!i)aFCzf=K)G*;R`r?Cyym^eFx#yB3 zYhRkCNu!8)DrWT>zibFJ2Z-Pnc`D|zz9t)@`eimeV-`QZuQ*)1gxf=ySs3H$`a2X9 zhsI6x`IQKC6_z&tS>M{LOAp@u?EqmrkBGr~iP-xTX6b1EYvv9OFkHErJFI1R5a#J# z=tcL(-HF~F<~Cw5|1zvaHiknaq*wX8FU5IVRc0@d^;CcN&v3zQ`|QJR-%k5zZ8=N( zS)Q7_{i-(~S>^W+Z^muu=Y#I7S~J2=_K8qn^I$?{0<~qm6ZURhOQYEf-;`3{Hy4Q2_`{j5*o^#d@JdB`M)h6}iHBna}T<+ck7m z?q^VT&;{-1q`F*Rw_44E|M`uUziN%?gO5XeqvjA{0Q4g|?T=rc@y!YHO_R3FUWPRkrIE>} zrYKnX7ADkt7rPA(&M-iak^(*Id2`M7`!6=Z2 z{$qOu#G;_Ukq2584d4uuX7sLY<@a1OQOGtodR>Pizm@Y?2Y=N z*OaNbofc{@vGN-*GS-Ve31&CEku|Lecs9iaD9=NO<1iv_^!jho3l-PJWy)uniMWXgKGKC(0)N)UeOjc8?9xvR@u)56>DTPf`zB0{SjPg*MnwhGS- zYwO!)1$n10^yDzy?Ht<=b=i+;i6wbYUNOw0nl;*{HSDt=DmHvLmge0PYy_3_ulkcx+X6C3wO~zV!rJj|l9QJ}; ztCv|JpsZ8gR&;0lt9I z^k2HuzRCU0_Mf_D{2|oS>+;#^SbVF=+n3}huXy+^zWRDOrQHoya8;AxVoj1+vGRAf z=?|#LyfF;L7#Nm2cp^m@!1|~;ln)T}X1Q28MmXP-hWZPq3$3Cw#YoQD-D)3nM z0`H#7O=*5h%Kw{09v%P6rkjL1)W|UOa85lGP3&mSjux5ByvbgBu4~dsn5Tgq9YMsL z;TMV`vh;Oqo9B}v+M7?Y@~rX@u;gdm z4EB93UheIAQ18n1zUWOEkh_GWRw5+-^F-Q3%9$f||7egvNOa?iBX|Fv4z(ug$DW5| z1vMS(${Gjd&>fw}e(@#4n$*8pu*RBs${q_VT2YtQQ=CLHJk*JQ6JF=1kNb@z-;cK4 z2S0mSRKG%LzeQ@)qV*Az@Ec-{&2H=n`IzkY>B&S8w zVstjpd}KW%H02WAUqT-4C+IE%UFeAl0>fSb^OAfGU!HjRf&{B0WlGdxe(>uy${rFb zZe7hgeC`(aCxyO*+oQFH5C03k%3h-MysC6)hi;4fFz$R<&R@(A;(_US)q)yJA%CtO zcs?YnK7AVa4jv1?UsE2oC-tG_-m8j2buqe8LKWaf92KZeV~hOhuw$$*TFdt|GA)h zB26aG-(ZQyzK`9P-OhgM!9XreHh97p(nI!Razlb(EBB?fJz>PNU;LpWVWG3X;B&pm zwsWN3?OHy>9wJ8_X4iQ|HP-F7xU z95q*+e|C~AJ?HV*6DT|TJR-4#Rl6ZDwP$omlJNayX%O|>ip@mG@0W9?xH&%Pah1w( zi{(7j;>EveWl0C+*+;}icx*{fAxe!yi+z5GQjeI5{YT`bEql9^U z7Ex+si~(t%w+kdtjvTA(6c!GaKv;0gS1v@sE8~C>P*`S*~j)1RX7ZEmN8F*1ve?};_*O!zO$o~f=D&qC-Cq1IXvZwnTSAL!oW4g}U5a)HQ+EuJ4iBmWK$6Z&GKD>0CHK8OmPCi0y91IMBK_gIywVV$QWZO@S{gdxcd?7YrYR+0QE7FC(E*mmF|m_fB;A zGyq%rZM^(BB~adCLK@3lBtybjXiGd*>!o0&d(w{QLRZz@*~|>{fQs2c5`xlig96!R z#hifST`rV}`wWnWw9%O*1pET$N0b75KG+`Di$4)bcl$&2f=RuFqNo4k#nUSru2Z6+(5PRN{pQZmi8W*&gpPG@toX3#!w+ouif> zS#>b6q2A=zbUJGVU1ChyAeVaLq~TiRM3w}qJS}=N^Vi`lh;-hZ@Rp49c^nKcr}svB zXfiZ-Q^{8=3soTQk!$e?3mvy>zP~uf<$BG!Cs&s zq!9u}u5kpYnWN=G;8iFHVz!!qyp1V1lQ$o{RK)Zd7cv3!Hx*&l3zs(x=;IV6$kec)R;;u_xApDW zWti+WaE7WIeM}OaEy|sQT40t7mVp+DHXGT*AKt2f-bGAJiT9IbmT|v9;qQN1eL}Z@ z-exla)>w?)2?EklM6PRBU6q3Bq=Z`5GIKxToF- zI{EdJHsU%Qf3Ea~%zbvQoN+B+wwVY%q7(miURL*SR?c<8aru5fV-u*aNDlkk%P>AJ zZ-}EdTE8)a#6m4i-v@Bych}vSAyYN2r+ojtAo`IWhuE+N2(uj_Jw`{t0Ra4WOYr_ z?;hvzjk)R`yOEam!_@YeUR6kw%6^#W*-(ly$j}QR;>1e)T znJ^qLBK)c!Xk^dp_m5~2>d;g(6MQf%l7r|34kxAc8Yh-XUW#jn70IAsbGz{t~vQ|-`otopq9mk*&7jKpFV9-A>17{5meJbl$!dg z(D%QAYf9#99?&3Uamd^XZ%y#zQB$`r)#2S++Eu~jQDjkjPEH}0AT(a`OYHO{4jtAy zBJw4TO}?36&6Zdofy-i&bH&N}3uuf_M=4y>%fo^^e#Mxg73*PW}E;pvns;%%32Ms;8)0Zg;mjN&m@xeIyshf5w`RL)dijK0Ftne8xj&!WkFdR>hYa55RO^ z@J*$%lyEm^;7dL|9`Ox#Te04k?!Q|}khDJ6xsUyeCxCL!%?uNr*N6aSu~lZrnX zz?`zT9zPcs{%s(CL^aJuu}luz0;9`?<_3a&V=4K_)S3TXaxx_(F*w7&)=*@&9@*(7$36bAnb|~ zXdh7gh1a$s!|_;4_cCB|KNF$IbgNg{LM}nuXTv1fHRf>XQ<@{D{sQzCD~cX=)tMG( z^d=4qmd11PUu~8e+Qx(;q4x^nUH>-!st?KK7uql94cji)QG@rJsRfN2>Hb{lbvXz6 zVAnnC2-ZgiRDF0}qK1LD7QAG8q$n~HnohDr+qXWugMfu|0w?|Zm>)XNnJPSLa&@OFl(q6ED(bVIi?~>RliKHXCom0a{LoxA6+`2#Ml#no zN;R!!8kqZZ3LhH1?X|jMKkAX2SD3ju799)}Jdn>j>|P3Ef9(m+(3=L!$`qBtN%Xb2=%pxxY zAi6@YAEfZJwuGrC<+GTJ*8RJbLlmrl6lSK6iA8b0sJ;b$^*SV^!(h(~_}-H7y{?9p zD!DK4@Rz*OyO9bOrB?mkkmNpu68kvODD5P~k7$ zjzZ6Mv55NQsszrny-dyts?M+M-T(Ew4SSy#x3ZWe7Ad0xVdUE6c_SbWetKJ9+tETs zDEi~>CA}xJnK6OuS$(*|A!L}VPio@KZ?q#quwV$HP06E z&uwLS&t~lJ``$?z@!8N%1;KOj)TYn!=4_T-qx>*wOfEJuRCIqlPEuLwzZ}^TW;@LO zFf7CTgOrUHMPA&3GD-FU^C9ng{{RQFi1@w(?*SwH8Y}~E7&fr0QR_if;JD|lb_*B!Qwhj=Yj0v0YFNc+;R1OcO$B-)WM%ukMnzcT~);V*00&v zS@?y%KHClCNA2cJ#fr$iqRfyO2sE+IndM+&>tM`$P4eNM$hGfG&c^5A=AF-jdbaWF zTDTg-0F8%8j!Z&nr*!GWQ`x^l&Rmw)3-LNrXX_S3)72ysUF#H^?~m%I@Eell;vv$twmq zhZc)ozcX*ILl4hJvg@;1@f$RfV_39wgo_6H;E7Td?+p%&hI@1ck|#V^3RuIdbA1-C z1U4l1Qf1GYf*JQ7owb+V!Xz;s8@rRv~C)Q7oK8gruz^znvQKBpsx~crT zsI`_W}kJ5zY2iYmEYwSmviEJGe;zI9>NY|&%tgc=C zp5bm|xDi;iWJ{SeERoNkq_;B7SBNHyyT9`vJT4caJl=Nl+E_uUbudtU8Kl0z z&s^U5f&gmfdZwb`z>*KZ+yIJ!ai~<%7xCpNTah!1uY2>k znHA1Auk@tKR)h%in8VD)%d3-PPJ(4*Dt*+tM_uLX_TGRO9sOfMS_-}|=YrxUdE zRqRc~&(r@CKOxGWpYYh=?ebaXX7Ssd=&SU8{8sCCb}aGG4+E|)%QJ7gpo0f793T92 zir-AC>-;sr`Q1|pk}vOuVBk_NeBvdF7GYa&_osA@zXJ5v;II41{(*$UVQY~d;CU#>I6CCuf5TygxHi% z`QJ&X+)PK&un0y@7Z+aA(CTN=RX=dNbUmEL93@5%QeJlYc${}u)4sl^e`N^mDJKmd zbUoXw|65V=<5r0Q!u%R{AGDL`{5nn3Vmp+q4!`Wc?vK7Ooew8Od9+z%vSTu#gVQGQ z=(&UQA=b;p#uS%r(`rJgs$47!kb=0*a6-9nump#PJ_)0DBLSX02&m#iX2&?~`163d zRg+Myy_;q&0lyFrH>NPhnwh?EI4gRVJ`E#qcAq8)1*Ae(Fb~75@5-(wpa83GTVWyF+9O6T; zMzVBq*k;auGDLGA|$W50J z*tQT3;sw)GoSrng&*$NlI~8|3`+5RoaejmSOsjP#4>zBM3zHqdy0MKUo3>p5K8KEB z?+uABD&S4#KHvzu;&3gPSTgeTwlvG%b>Hrt_PdoMP>I7YU?&@8^kpXk_(TtQU21R9 zqBIra8wQiDQkAK7OC~YPCl{25;0=0LQ?U@XsI?cG5)AXy;bbv2uc_GyF+(sy(4{%R zC1^tqc^o|ESb|gcf70}du{g)c12-t?4hee01D%&>y`UBo==>v^``KD$5P(~%{%8p< z#gPBaR)O9Bi{-`b3nERh!}(WQ-YwMvvJs~xPpsSMu-wOHV^b-SSxWXcwtK7?PJdp#=d1)=eu8$auQhPcl*@` z(Q4r)8JFo;wyPSv>iM;C#QQ1L)EKIlr1g%-a$9!6NaUNu0B{DMa{5j>} zugF(kAWbCTShd6+CvVLzx`Hu(ceuXyy+Ih>(fB+LdP$Ev@Be^Z+-w~Mp?HexNtmC1 z7ap>P)Lm#!wWa`K53w~gDQ$41L%mzZ%ptp)Yxe@tAr+j=Z({!SS$TBk%^d7n0CK8R zY8I~8#ow`zi@)@E%$-rOklK?aAH=?QonGmA2K6tP>9(2llQgZbW1O_AJFsdQmk#8R zUTn9C#7MHPjjq0(pPcLM%x=ALSII;4rtkuuHM2f*Bt}<^uGCHd?mF#LKnw@$-08MU z?w0O%3eB!P(dQRE-3(gIIE>iLyCjZK?M@JB0w^U4is3bpE&37Il=C5J8f^Zw9fMR1b` z;N@%6zwt@U&mBhJR%ob4>Mks=tq<0GConnXs%e8$wG|<5DA8> zcbJB2_g5{acOD^;R9H{caYZd(OH+bbg30wOFwZ*xmP$lDWwO~1KtK`hWPPlYK((bB z!;8f{r;{%4FHoDe$@BFAuluT8Z44}Tp6r{64a>;5XP($s#xQkWI+19kyDsm)NdL0j zMtYoh&Tk2@eG(ZG5~2au;XPmfEoQ8v(n1>01Efi8}|mhrPjS zbtFfH?767~stZM=Hu{m}r|gAzpBH=HO&;{(LD8_%VUnc|vbsWYWs7{>%B7V8r1^)5 zjlfia3OE|9uSLiEv+61=+3Xk_$!67zeM7$cZE{RQuEvfld1BI2M>J37A+m3s$RJK< z5;G8f!fwi7CW}h1RmS_eQP2!#WF}5#0=IO^7OSr-vrGxU#+W%60I#>^EF} z46~4WSAkX-pu*h=nF;qJq4Wfk6GGCYNyYrncSp-=meb3lbz!C=N#pe|jg1$l;%pG! zDjrn+l0DzjSv9M57qRbMG`G0js?rQt+;hDXd{QOaTK;R6TX{R^Ukn)R*r)r-)U0_d zcOMJq!|>$_02!Fb1sLXieQ5r8SSsBG*6MQa-2)0=U!cAI=yY!diG?rUx&y&<#gz!! zAk}FVR@rXCK%lSn?9rk6pMkxdw7Xz^6DZO1R*`vQ7|nuNJh{opHY`ksmRA(BPj!{^ zc4%KrtWr|erjF~Yg?ETHpGb?wT(5Iz84{of1oyQXoCwR5)h+ZM4+yx%;e zm2SR!T)Y#xVnol(flBhpn>)_ZK+(P=4}FY$P{X475N0t^%`6`i(*+p-=cB6e!nKo9 z$p@o)1|6Tv?YsvC7r_M$muRc4GWCD&Tn>J$1O3wepB5l`pe}Sh^VR_w6G!x^zvtst z9{>*BbYCtI3nIhWFyBe#Snv$_DE93W!K;?;5HO}Qkx%WWPDQbGdkvdW>h`D!d!K30 zGeWA9zDnD*gqXH9=-H{rlO;ks2Y{fR3cG}K{{(hjV3NGtyb9DV=ou9@w*r{o);4Kg zuQl#hI0t#@9!OLl1HBA=WU@eVK!9gk8h`S_{V7N;cqnW9p-(+~W%CntaO(AHCDXOM z)7MPz5z95r|H0+f@iYH#4g3Vx0r0soXtrud@G%_WK4Cs0jCpIDg5y08@j#hM1gx23 zR-1F)$PXJq-h9vynkXSNn;Oik7_h8J3TKW+3sF3zzmNal|o1YJnBVuGvBk zNK;@K=+%;RLGKI`Fg;o9u$Om!)NV7F)Q`7Qogv=}>`+G2a{Hqnr+ToMD_lDFU4Uz0 zINu$9aleSBCg)UH|^{m+=cF&Bn&r(+j~>N4~p#ByvS_Soa8 zg2$a=%w23&zA<9gKqU8I9C^OG$F1)=TQSz90@SnSIyKP|n~-KktkPJ1+6CW&@53=} zlb5VtK`3nB0w)`B{{bP#^O(pp1E^dg|NRpE)@B%lZqyd(x7@(uRSx0A2ecd|%zd7D zQ2hHh2FWa8V$6Fufk-t*?kIu9c5K_i(KAi_p0}#2ruI^0u@uo>ufDvBZFEnKAHXB7 zS+n;s8@^r=d|Sk;rIcX_=-koqQ(lHsVfh=X5A{ByN7Y_wjZDq}>;|5PZdcZl+d|Nk zo0JTwru%d97&jbA!b2#(@K~}j&@nR~I9eioWp5s^oASxFRm^_tRt9ppWH9kKDIU%O zuL}<$4L8nrrD|t%yWf=Y(l=|caW{I~l?cPol1*jd-Q{>@pOqF=`P8+2wAhUXOq6fK z5zhc-1dDI*Kq9Er;a!225%;Xyx|bfu4o5pW zYe}a+J5v~E;}>YpkGHrp|EYl6H=1>_^1V(vDyG|j^|6lUCu~p+ zx2;sleaR5iSzy=xBj%rHHYsC~1kFw2k1-VBOU%uZ$2S&iY^{=D*NW{0F)ddIQZ2;@lp<>FYY9ul+J-F>5gsq|-vkK4Wc4{aG@Rw|{p+t*;WL?}? z?PsK{dkl~>WiNN(DxoQm{9g;93f3yKK3VbA+qkvhhpV--yb1k2VoyFrbb>u4hwNqj z@1sV>d8pUzv460I_Zo!sJo-9^0x3zAYwLykS%Ge9LjVo1`un$fNyn}fT35tT#`)FB zU&jv!(|-8=d%76DR#c$RFxFprygtSJ{vjZY{%8GR$me&fw3F$cl}Y(itIBNT;NR-E znM@ec(A*4L(+uuJ_r%a*P<$uD*ymj~;nGFXb@plOkQt)}5ctUa&Jck43iN!fcM83e zBCLL*zkdrtI41!Zl%Ypj5 z!8?KAmtuo*S@59O9l$<31S=zEK9Q?i;OCv6xd|6LK^XTew}r@dbEVKau^Sg`f9`zE zHx~p>-hWFI*+q_f}>tBfQend}!bT^T*vJ&#PFrpw(E+lZE)y==S)|!z1av&T>>( z_!`>?qY|1n_-`D<2s#GzR2OD7I~F=^Fku}9ID>NB2Uh}eSB~fRub?o0TRQWP(r>Nd za=8|F-LSA75fmX9Ln~6)wD$fvsaqeH15_Ny!GUg4)OLT!m3|mW9fp1WDuM#IeO=-f zTwBK>plzUbNc>K2ac}bK#T$LM!)I@km{8$zw2wD z2SxGX{z+V(iFkG@rU{3ch|QZo{j1hZGO8K;-kNT~3Nj2!YOB2d?vl=}T{_f)OGa63 z&i*3r|AMgdd~6SfxPeKS{bf(@uIvi@%8!X~U19#6QC!MvQ5XYkS%%Y}CGP)`tn>Tj zOo>QW^C%ZHtOX`|<;JBm5Ne3Z0Dc1PVtVYJQWj$R=FGa~)xVzHgWz(@z4R4@aK+B% ze`Tae3NJc(dq)V-=hQ9u`?h#lzsFXFTO0qoEqi&?2Nl~Wi2xj0R|22fLY)dfnz5Fy z{3s}yHXZMxr68D&J{zUOtAEmUFb;_v8xx3K;n{mpbjbl_7bIfN?2-<#i;V`vJEIF> zp{w#%7x}glfW?MUK>oR?KF}X{rex5qVbM}qKTx@(Ay$wJN%-!x!LaG{rS)}YLiBZO z)PG!1z`@L@@BL~HV*_b=lpNI@CQpaN+}H@VK49&<-K)v`)}g!o;de0N-Wvk)?YnO| z>Au53@%sh|ybk~Y;hc0b0M5dI@A_T-e$b2h?vz##isdQP98#;6b63Jy9(83e`M@+Y zdStthGt%as@S~FT@CxkWUpn4H_jPQAF0)skTjPtV(w!vp8YU8>qhhnGZp(_=E2?FC zDR9~(xpND=Y4`iH7zqJKqjB(@b-jgFH>v5a6p$Zrcsg9(EHYj~gi$n#(nY@MI@rxx zB}CMl?F%T(b0<|LtOe~M+mKr&c!w`pf^3`&Nx?p(J*L?mr_uvsf0nTY6YBu^o+RK& zOtsnrB3h#8x;94NpB=lKT>!t5kaEw4JVqr3HHV8cA$V{!%n&)U5 z1ic~8m}ES1!YsR&>JZS7Z#F%Z{PbuKXx(slPRRzH3^$P2BiEY#`=!v5oB%NK%!1Y| zX7&RNC#!d&0{_zqPK`eB`@Nb)1AZw#{^vdxW-j1vCC0SN%)bs>36b)7BX6&;WS@j8 zsWFqo%S-J-0~N{tbWF$lmNPuB2`v8ov|eO0Bl&l;d9pq?WIB&~Uyin`Bf9^#Ulugq zrov**E*}B?s*ju1t^Wj=;j{F`rgucH*V9B5;@kvy38C8flZqV|nkv`HkYp3x7)AX0 z(m+~E<1MQBl?T0Z1ri@Slw@rFre}AR`sgNZL}jJD1YDh#bEWvn7Y>|szcclfJG$cK z@QH~dtgT;hKhH{QBAao-!aOf(O^{7}f{rz~<$-WHEQ~J&TQi!7G|k|Hnc%&$9R#=M zank--0%}9N+U#~pjxn^AP>?<}yltIg>#}I;9fy5B_GdKol)?Z=+LBv6{;h;F)>saX zwW@l)?D8}$kg!W?04!tf3I;8WDh}r1&2tJ!V~4R5{FMoBzirz-9ykNNjmVQ6SOM-9 zx?Jd<^b3X@-cN7*x&3Iv>lXT=nmgaTlh`5!@Fu^DnyR|@pmr_nY(4oqPe9f)Xb&L3 zB>(74Pf;|94s!$H=a=8g}}>h(X^(zpQymmaaZe_SQ$k zLlL`+cfIMPEi`y5ts6nSj(XAsk@gedm%U{`GvsByXJ#0W`R*{CdqEv66aj{M*S+-d zy}^&?Y}(wG!@ACsl%#{}F z2&&IBJLpN-Ct8djoicLY<){BrWb8TtqJCpO7}@^rD<-n!rbSA8I*r!HPchd(<;K#f z8GfLiqwXo?pXG=Z5AMx!fg$-Ibvg&CGmVTu0JK*Vu_#`hi$ecI0+2cN6OkGfuA&cF z4_-0X@!#UK$Xyg5tcyG>>io_%bUB(P0avR^jXTcLI3*%{+W|H)>bmWlZ-PtJhyic+}C0YsZrloqD#WVl>wj6`HZ?ZrB z#cBRDK8<|RsVi&nb$0U`U8dgBe-jd7`##C|?nAtGr9lHq<)xQJ{b5#e&p|zxI?Z=I z0YZ$yLTL#>480x7`&=0m@xiBT zhV|)-wkQ9ZU^#mlzTvB1$cUVY)PI6`q$}HUq@nmUQF(s-;1=K$8`Xz#N3zzLl zswoQVu)O}!Z(eTLgrzj``NWR-P^Kt3&a`t9a{ZPB!%OPZ9|3mQxLmf ze^|z>!Cc=v4wD{x-&Ez83C$YM>d zIf0HP4_<#qvHVrlg5J06OMXs_QrXxmY%*8a$B|#FLkDbD89=(h=kIf`iI6X2#GcH_3w(t-(VJAe6Y40UW?HqAe_R; zDiG*DsIBgQ3fGk$byUPS$TEd|z$5K@VgYzHsO*C!s)(h+SQlAl4?`@54_2MDFcXid zGqq{(rDum`u9H~fFU|bNiKC+vr+K4XcMD3$(*0Z6CXLvLVPnOl<8*SflwL`4*xg zB=#>J6zCQBV!mzqD`Gs4#6U`R{StgeS$B3(lXw+|S1|f2=Tg^*wtbteS|}<8c%ZZ@ z!_@w!?KDDnmuW&F&;w8I2K1iLica7 zz=%AA3z~^BM@X5lnzmsB0QA8C4g zq^~3yn>&tHyU+9iwex)$M4_rD)+MrR+3isW4=l^8uVyuaS;Ci$c5=o%i05b=^~N#u z7e3bq`XTov9hRhP_!1U1F+Wx%k0OcJRsnqb?`U|zdFfBnt_oeIH7oPBC;$T3bN2Hz zWSrPrDGl5o`3cIn$%eFQhUSlNS>g_Tmbd%uXS5KA_AM*B&dkxsN z^F!_(b6yhIyl<6KM~OQ9iB_p=*w^})of?&;@UIYm>L2n(7^2YC_oN0t0yVYsPZM@0 zuy=Cl4n#eX6uF`2;(?-cP3)QpCNV%h)~wdAeKDyo_s)H~A^nLxqCfHVXF!-mE0E|% z(#S`DuZvL_!!1=Xuu6%9wkah zl9qOoLtoz?g%k3Q3C|{6Z_0rpONpFA^08Yu?R_oUXa!xD9hc1(1vk`ca?iWGl3y*- z0X*!Da-~s>YutJH9C(#gS>+@SFD(52P!y;|aRyoch;zu$rNo>fR2<{A0Xx*QLWL~* z?mT0oo<#}}s2Mq)V>~~>f`Vcr%wuQEJ85USIWp_tm2r%Y2QTh$JXx?3e3Ldj9LK8wc3V9&;Q3gA@jcHHarln0vAtx1g!p+Bo8eooV5ZBV^ ziRm)?Bior$RR>3jQ2WHtLV74Z-Hx*Gxi^W!WZ=c8w!o;(!d~u^x6(~@Fhxq%QXk6? zbMLweXDmR6VqqS2lCX5*K*p>UKb^UQNO?Aomw^TnaSxV#+8;8qzwPFD1TXx_`;1cy z*0=m=2neg)CR1&EV8Ym`M~Su{?9a?2isog}97}!Qi)s1fLQTl-1oAG1q~jQZCT*rI=0*~pzOHg2n{#A0hr07IjNs@!;Y6rP*4&?Rq^<>? zc^lWHz^ev~Zkj)Ak^|xT&4xJ`IMx6hmE_tNFLWlDdpyxQFyMhi0|oPQR|OQnQ+^^x z0Z;{uGCv-N`SA&hIb&%G!0+=%pF-*K=n5Xr2{XHunlUFTIa8AK?-63w|F^xnzJz)j zu>npB5?9ANU3UqAS6g^;po%DigE(PW72gOW^=z{i^3!6`JIstd# za&bN?-y{3tTh{3`fr3I?NZ%0Bx4hwc(o#&E=78qTs}~TYULMK-MP(u)BWmv-gknfo zgUruE678;jg56_SNW)o4WkPly$t(E9ih^gml5V&m@;6;Fq|=Bw(>OUe<9 z-=qA&41#St*vxz~@Xy8TkoV~s(~p2H8gMN1ovr1ok*7pg9zDx5r{%Ssk1zf%`*)0U z$~Y)Mq3C)a6Po`HH~$)^;$6N*TODztiZ-?jjMpM>CP1W*5sc@$!1Yf>CG7uZ!CXeM zV~pJ#36iHaRQV@gmVgLj8o2N}4EX4kL3=yezfM2*v}%rWR#vP~An( z?cXIrU%0xtf1Z_EZKYHg!f1%n{>YaP2r728&z3te7*=3gJ0P&49TNZ8qgTg1JG%k= zYbM1W_`$6DF>YC-ds`^|gxKDD%j*pM=v-c~Q3gC>@A&3h^{)HecF-YAl`t5XT|4VX z1z@`~Jd@g>0M-20hd+@ww4@&R_(Gr!rFb)8_LwgBIIM|Uxp5u4)MDFD&O?x|wFd4! zAk~G>z}Kh2@vTwRS?ZGjqUOjkedoVF`ahJt~SPV-I4f@CiyS_$q=@R zp05dwt7YU&AYF8DYupyG|Gjk18AWWTmkZuo!S-r`cPkBL9;yW^bIU&#bDju}C6cx<1{EcJj-#P3`9xW21&a%#~m+n|yh-v{z1~ zSSpZ#N31nP?RJxA7uH{+L3uHR4;`yqyR7svyMZ7irLp`=-(4Q`L+;83<*wsSIC6=5 zG0xuh0<0&n0 zMbK8_AQT6Hv7SEFnj`*PS1Evo zg%c$dZp6s6VI|r34DZ$ecPaDS!2=c8C-GEV@Z6^0DQk)CS!i&!SAxARW#hrTc)ibd zmXibq-{0erYwk*rY-vSD8}{-8Nc#8ehFAG!euTrWYgoVB^}?>+il|-hiPW!bpEw2F z({ZF~mWYU$=!Ro^q%A6(&W!5}l+YvvDe3A2EO!YLBZ+UIYS&`uyQux#r=Y{sY z9G|5=%{n*6WdH+vNo~w)W+4+U>~qcIx6uYV_QXzE-oGVQq8RUWfBj#ym;8eVfHKm8 zIV`4Qp_U0e6iy-NDtMv|6y%c&y%KThJ!m-@Z1C>{J%WMAkUHrvmvR1506QWJU`0Gh z?Rz5Xorf!xcffT_NH-Oiju6^&Oc^y>pd^|SQ@&#A_pJ=M+rd7utT#Mb5o+BBA39|m zx0{}^lv!Ai?X}q8m;ITFmugu(5+GKI5A_+@NL46us;q7^EKQFG?WJbzMjVRr=_@hP znB$w-Z_=GsAL3~PU8VVe?+5Eq2g?xL&u0VGkMT?9r#Gk!b2<_Y64Os+G7obTa>yvT zo8&k7mXTpnNXF0}atR3f;{8?1H+wjSmN^!4v9A_G#~p#;xISNst^EG-_vQp+~%$5cOv_vrUokt8+cw}Jog$Qf>U=ifcb;*5y2 z>JR(ZNv}t=5rQ(XyNrAM_NClWCe0&D+f)6bzs99k)v3*mxKJ3x5h zNy=mQVMA_G-7O zQgzpR^q@rLP`x#>;M6pM5~GsuTY}WAY0X{VB>KKshxj@iKjRm^a|Yzga)XmjFlM^< ztfc)}#l+Vl?R_gM$BnGPCXzBSAx`EbHL%Ke%zP<&9F=!v!7CccI<;RujVw&-l~gJU zul!$>XXfUqHwpQ6`w|KfkKB{MIX!N}xo!Fk9C;gsd0(wSr3!sl}WQ+WL$iPc;?pYs3-c zzxN*7R{~NUSP2Z=4~TuA4SJj}fPAOiwiee`Bu~zzlVK5(yQ{XR+FrVD`qpdzPYckU zRxlX0wHGv1QH~3kG!d)vqZr5x)Xl~^Iz)($Yl)*&6SaKsGz>RBz7MVKc~&t~1zC8^ zBbGaHYFa%_=&aH?clfUOj7&i8GELy+-M-6RNTQm5I4KMlyg_Q&98I|nEO&BaN z_I7@3@JQxx*;qQ$0-NpW3^;0Ii55#!w6P!mbY5slg9=&2#q2*N*PKdGe+nQ66TC(? zsJ{p7kc-`S0=gG;Zap1|(dvXbDog3xkGiSVlp?-erdba>H7CY^-<%k&Dj_M={`{#Q z?`Du%XO(T0a6X+`jp#5LnfHrL!{~C=ahcD2nffNdD!8TUew7R{0Xp zbSW~Bd^3=P3^*C^z2;%|LK=Ofd2Lj+wTIxu2nHCk1WzXb)Bx3(CVX|Z(Q!QI1!-HNx-}ey492L zgZKY*s#}m7VF0+(=Vx5_Vj3(4 z86@_haC{Ym4NC*;F3K<9-Wjnj5>~DLn^IiE4;Rx3R*yYPwJj({wQ{Q8#E_@14ZQN; zPP!t9u+9@&aG>73l~(8nFv*B z^S5j~#F-6xlHee<{r=-2v~BA?`*-Ez%iF^4uKOTj+us`mC4`J`^HS|aWlwg{ZC10# zBQg1a=zGUc)-)9}L(bSbPXjH^wPAG+5NR_&gGL)_Xgv=Dh?94Hon?O6VJ@aaKL0vF zEwKG|eGPFillM@*#^(e0j8K4LN5VHZSN#yrz&2`Kkb6~gEL?jFgLI!lBeq{mFFLr_ zI*p!0l)IHn=_*@LmQHweWv8nzornIaH^Uy{k$bR*EqIEKoi4WhsTm$stydKO=lx9= zYx~I$yo|B>kwISHPukJqlW7fwgu)q1m-vu{{K8hCLhUR2Qx2b%lMIdu`>0t@V(2S* zWsy%cUlWA4M_rf+xXyc$DBhqC6_EPqHyn_lT15XusY9Km3=l{aK$vTP)V-vukbJXzKWxtcvj z9x&j^NB%ZX0ZJ*PDn2dq1!kNLL=x~&~I)sTmqI1bn?#T3FELblG{m9TZY zKm|-0aEJ$LNn#}O0nY)>I9{{Kw4{;mh4SfEi$xSaFSZZReKcG22wS%4AVR!6(aU3p zc8EGIvaf3R9l!qUz87jxWrAZek6kX|oV(m9roKyLu~U+YFRA>p8o2foy)Pik+FE z`F%tb+7aa-I|w>#HvFj|{}jV-L+x7ET^jnN-o4D?+h^RFQ!cbKD(>u`hY)(#@cJ$s zDvYbNdNJPNU)9xdI{A`O3r-D<9TtTjd;m&SUK>?kRu0SQ$z6Y;Md5r~^rpKZdD9lp z!jY#aW&eM0|7fNxrA?nHI|4@wVAx_KR)urh81QP15Sq@^v%C6IkP-D%tVAwhf3sII z7Idp%Mt0Kx%L$R%7kaak4iprDyNr|d(Dv3u?ICAN zP`9UzYd#l5s5OIMY@NaWg(7?B`>+Q#j+4g|h!PKgleCAF^q-K)viS$EPA<&66bfh{ z1v3{$Uk5h%P7^5L3YtJ9p%eIa<*=Q&vvS73zD#1%W(MA2?EcdA{FyPFxVq!~(zpI1 zYq$w5KmEp$V(Q2uM&jY6b?5fz3=Gm?R&8AS<@;kFGx0@VQS_g5EXSABJ0X46YSi{o zG5hp?YaE*gCsv23u-?!x6XYWc^Eq+;VC2_GWaRcV-15doXtM}11X|&h;_Fc^+|yuG zny$OV6P2$0(1qs3C($;Ea6aXjeRmVsFammk)UBtXW; zNHvw0{yAorY%WV{R2gIhVxx*|PiDjQE54WfqVHzmd`V?g*nU&m^F&5c=Hxy@(3D2s z?X}k;QO&QT=k{m+A~{kBPAdo zJ<=MTycWk#p)-DJ8G_+#`lD>O7STJY3SO9#uYMeqkvw@eKk!WjUQ46(hoR#FK1 zSVb6UX3DlOwPjQH@sa{#YzoLwjBzQVNb;mU?g@a~#pgS_-WofjHbq@r+m{2im$gkr zO{$PU-OHhpyZFL*LXMp=M(6vx#rlOPdcT6bCO+pqnGq#Q)6s$>M71Cz zdRG1)k~tTlo>(PeoB;e8bqn^~Cg|1>1vFr}CWGlf(?x5B91FG-H6~0}9rzhi3cPg+ zMoU{FdJ}gB!<4B{m`B%XD)r#C#GK|MyQ6!5Mov@d4?RCK$_`C1w0}=t#5VsYQT6VJ zPO)Tg^^@WVjua8Ek^R+Gmzn7uq2Rb3d4Vc#&%>0LUIPP_B5*8-p+YFFT=oY+xYKyj zKFV+nq4|0L-^Hr`Arb1|+92x>#ZIh>+`FoI2Glm>k@4AQ!AJ_gv;2~mhB+FBxlX!a zg)5M!s({x@c_7}spiL}j>Hetgh0|HAR$jM>EpvH6jK#>YZLRP!=U zBH-3r6+^>K&rpUd(wtmDli7Mqa#YLvpJ*~qfSzYPFK zWQ3bzGs@D;be9akdNr|0eDsQ*vR_qo5+Eor=MZ^M!xZE-2h>Fjb_}TiW{g^Qc1=!y z#tAOYsy!ClD}JoAs4e6?XS&<`KVurx)}z=r@Ga|^{RsG-iH$~HI+ypwR($NUI<5Aa z1ay-1z4yo%4en?y?MPj0+o`k80q&=-T-^g)X&62|qt;<|2t2q#3QqI&8tQ07T1>`vjluM8&SgnPF#$XLAoE?x0oKnT#R71RdVQ3 z_pkxR-GAM+Wt*_I{*=8ejB1BH>bFdu=d=y=Wz-Bv@zyR>SP5GZF?-RfblzDp z2*VC~7#Q>qItEr0*Ef;{iSn?_BCXa9LP*TwD#SQQPDkq;sR8(Z6}3SP=H#xMdK4uY z>48T9@p-Hk@k(-IeHSyU{|CKN-1}y<4}B|sskDTUB$W%^t8_6MFo6FD(+U`2!h|WIG9k{z4Ty(c zsp1&MovFcKwK^XUo+T7!TI)j1Mz2GX@&c~~2nrQ;1@<02_!R~ynO9SbeC+2nGH8r` zjd)}cI$JN9-7oSD|62LbC>CU36Ay>)?CQ_0DO|68amkXMglPrxyJXpGdX&D)c+Dll>W?~Q0)eoD! zKwXN&uU(V`#|tvpTtpxO$Pep*dxM9+zXz^~nBh~8G(AaX151LyjRTq>>3l%C&PRqp zJM5-U2v1tBJbvZ93y^9P<_UPI=g|GCC%TP+xsC9j=`c5Ob`(3uL@Ru(GbruFBwX3S^KN80-y|wXD8$xRth2~6gVz#wQUKu(Il>9bRX_@my>*W zFN0|&6Jvj+hIRknOBVBVU}Nd&E2oAx%=~nW!#d+<30%K1+y)_W%}aT?`;lE#UH;NN z{x2-D-%F||Jq_Uu^C}yK;%M7VbaJx}S z!uW%7eg)5+*9=u%D}{2nOtcswcek%ht^HEH^+k^`h63PA+rAv;R06<@z0pQ_QSR4=RGScYs%qa@^|9uRIl~LT_S*z#jgo{|Fp-NF@&I<)qKP<=I~^r1A&Hay zq=OFR&JBpM><#AH(-1j#h0m6NsSoIW6Jw^dn@TSo^@VhV3?QKbFnw4=3}}EB$B30^ z3h$Ld_I+hsmtQ2*TIhMbmjNzFyTejW(+o@*1z@J7;(n#ArQVgPuUT!`(&)Se59-Fx zcDO*?n!R}-z9g8XS zn*kr9Y1LjYER+mM@u}S3dxTj};c)g5)(b+Vh^R=)TjP)ff^b0Z*7~W0#4 zou>Kn*RJk@|JXd%^$H+OB4$pcIvAvkS!^loW@o5fDMvDcV4e3a0a6C$(l z+tkF$sL{fmKmPU#(5zC=T>y;GyLGx-yoZwOM`+WwuI{}O)(iaBz1ra9E*ew7Nf>QY ztwV}AXF9p21KK_n_VC_)W=g3@1L%vv;;0Y`rw}WDQ3HmH-~9!Y36E?le+fD?$0J{@ z=TPFj`Eq)I(xGN z-)Ug~Yg6wHf2_9Os5~dYJGN4|YTfQziKC|0?A{U*%!P^%cKPC~m8_qM%H)NVh*(Ht zYVLLU0&~Z^XRrlLp@SE=M>B9`53!X{+0k5Vm*L8H(>e@sxLx>NEa)DHbf;5j7?190 z@;Q-Zy>yqg|6Uo2VffbCLaMp)!CHKkTLi^$s?=D1ykqa{HS=w_WQE!zH{j%7 zi`VvL_FKM^bc_XDeH(#&w6?2iAOnt}eQA@eL1n=!^qK5USE4t{p82z@ulxOh3-b4W zlM(4dk;l`21+7@woi|y?>HY}RFeIG<;LdLK-?F9)D#Z!|ov(+Ip@G_1g!|OcPFChf zzuH&qKaR^A{1-HD(nZfi_v*hl;GDi{#yG^XeP&<;__vnn!jZY!XX$qWJY&Sm6Cs}{ zn$flI-HMqsdvAx+(SZUqqRk~<7yHxMAgr?E4iPXXCDuE8mEw$-B7em4a^EaQHB5XRqm|y}@v@_=a1BO6(zkuw&hztkw zPHEeN4x(EQFS+SIscb3?YxS>{zA6FKq>~G+-AJWl-F0}Ihv&Xq`et3;I+V7_57)K0 zc!K6t(%(gn-X%?&4op0+K6cILy2|S1Gu)Ln-peD?&+7sU%6uhPa6wRJmDJ7u?EQR)Qgmf!3qnW`~X;bQrb=T0AP7F6mq9jWxZ_H_jYr~h&F z(-#&@KhK)=D)JS{HlDj+JwSR_j5jxJNIO++?6s%v>$>i!^^^H@PL^?+v@t`hc1WCI zZHug5a@?7Cc`Q=R!?yWbEA;f-ocTt1Z_1?e*5OPU^o~!{Mp8zxgfo{gb*2};ElmDZ zn7MHahi9Fq(+73Bdd_X~lK!RqZThy)YOAj~{`8A&XE%Mb*4bsU`t^890lax~ww+Ah z?~S9X-+7I#8MpDB@s`J}UE7>V#MXf&U6*zp*kvxV>!f?^u3Is1>mv6})am&<&Lv+v zmfba6+ys~>Wql=M$nz4Q!xU$e;@m(1^<#3EGyRJcMt1UfzqfGB6Yx|&`kb$}{33+#gZ7WxABAbgV?bllnw&f}LGdK9x>!#=+rcNip~T6&%co& zNdO*kH;i4N^ZNvd7BAPQogi)c9kyZ!uG}j5DNnTAOu7}wNuMCJ?VHm}laK~VO5hE%MWI-NcJUTLU$tf%& zni-}tWeWmZak}PN`KfXfP%II2Gf97Q%3G1+?wX@nGUXDUa+0c@onmbVL?UR-ux-6P zFlqB_(llkP`jsw+6&6&%ymB@It`Vpz;Y!j&d2gzIW^kZogJ(2a& zo#RR3HUUy{T7LKP@bc^}2;1gXJ!U-1hhEuw3r{Cb3*yCt%bc3|v5EvPX@DCy=z2=e z%#0=0SWRab!f5WgBhW-x-TnQ4?6J@Li{H^H;X|kS9{dPsbN2Ie`V-&ygX8H3-bCp_ zz+q6P{=*%dJhR56c5UV2zagY>h?fH9I{`ot3xGU5-LnS~wIMstg0BXG4milsrL=-I?J$>9y3T2bPKsd!GiMHN`? z-Bc`m&W&LmCcRX}xAWZ8=@U9X%|}&$_PHxBP#wh#jf$r#PcCAgHxEj`wBhux zGv9XS^lkO&&6}zoY`b~$llI>wO$~m%zU}eYowsdS^cZ#8$QCcZ=&{NrzO`fnY z#(Kl|mbS3tKo_rK+SZkIvt;Tm^DpM%ninsw$G2rp%AfqL0D4TsG8NQ^UnCQ z%0IK$w;xmseE`;@dhWV?ohJ!tWm8&Ri6@CqpUZozsWkpYm~3{FsifpSOK_^ z`~rXrl=(nDAH70Rwy%K+1IBTIaW!CK!ZeXu(`U$AFH)tGe$Qo<9scMpZD7yy$GR(S zWqqb3+ls60)DN>&{_pjj9n;?$t&?^I?o~h9ri|EmV^PnqFk$B6*s$9u zwePG2LMOf5Wp*c1mA0P_ch(xN`o3rSZ}SmFfTs*a@{6DSJBg!Tq z^`HH+r+(GHccl_v2VJojUoR~`@(BoqyNGzGzY0JIVS#ad6Q?h(s*XdT{QX4P z{f`y^K`8-b!XZ;qQ*1e>1lupSRZs12JKd}x12l_;4Zm#{aF+DsaoO&*(*)>**Atg_ z#=j$Y=!JZm;y}q0lry+o_ev6&VO1?SEk9g8HjANz;BHxJL9b{Qn}l=(D-`Pfk^5yy zq$mk$zG`sFZvVMVUl419DsGw(nbvVp8Yb{Gf@#DwPT+$Pv_{;a*p#wRKoqJrhzfxN zL@NQ=y%&wMTv~Xb+9)6jQSvLo*?}Jn%5U3DkLQI6 zFJYwa?fo}4Z6VwLB~M)jZ{5{p(sPCQkI3^YfLegH+~yQPLIE-4qpK$m8N|ChV{5n^FIR-R39M%@q_dWl)jh1Om)0V)}tYVTeUPzn1Eu{|@pl>r@Hk*v2TUBm9KrWEg{pdYTkNTNB*%}kdwB=M z5*NroUb*LAg1QF)WntB6s(j^g?nZvi0*>O2+9LLD_HV|jO3PKui#uS(EY<6Ym%dn_ zaY>uJ{@A9+zM;#`r^m>)V zrjJEt>x~BnQoWRmx|N$L2Ls@Vaf)*=Utky*VTgTm7zo5bSPbLYQ-Ai?J@J`e|Kt5i zdH6KfB#)F%e&Ub)_2sia_^0YG0;HS&hZ{J1fv{PZTT{deK!AUx0HFFpKmees_fac+ zs>!Wf&Qc~Wx8qN!d$nDFcM%L{$kl4w%kZ3C%3X;$f$cey{MiIlJX@cJ*R5$=q1vNf zJn^oyj`5)a{M*vZd3Pq0rmXA>oHl%~B4v?F@#WmUjgc3T?9NUeC9L+YGs~>@<$O;d zba<|uOh1*&o)`=egLwilf<;*nMlhzRX_fm24iP^$1Vl4Q5%7}m&xpcV2GV0%ROOlC zGi{XHO%%acaS{T+BjOoLr1Ygj_c zq0$CQU%Eiq&C(>G#?Bv7hd=>=N{N)~i&dG|$xYgrQrc2?(iRsfj8)1=UeZodel6s0 zlArASY49Tb42W%X4&Ww=66FA~j|?h|<^=#wQ$2~&=TN1iLA}J|lr~g4;--aCDXX~a zzQ@XMfqG|C`%4*tTJhwORW=b~n@BxM8+(=zT6v}w7f@*+{hp(kNK3kQu9o`HZL5_Z z(ymf2NmIsx@F<`xfyTTedzhXF6vYx$y1IMEra)QS^Q zXsn2zm)?#UQ+7x#T(Ty3P^R#vh0PArk za6_{5*-c0z_C4V||4QvfJ^Q7W?!Nr{bQ9v{@y$3$!IfFwhY*}7+U{m)|o0rIg z9^UZM&};8y_;?V~a=k7890sZCQAJ@j#_XK!)vw6eRbK3--3sKE`ffRDwV^13NZqd` z?Nz0~So4VU=_H!|D?CeSV|%>$rf<9b`oHr{4g-&rb{Sodgx2@p@%ZtN{mGxfc=Ad1 zvw$HGK(}%H%(?<6V*W2+Ki>%usJ;&%7n4+p$>|@|cEVm2*-2Cn%5AkOogS(q&rM#I zjm}`|hSvkCcS)hDq81RFG*sS=_Du7j_{ej-Gx50--nPkHJlbrj&0|(*$V{H| z=JW-I*f=a5jToHH4lr>v6X6LsjLHQ9M?oxIb|mL0aU;6%s90PiK30V(*~#Q2IA)+Q zMRx%g1Vo`t-?%JaQ4mv24RB-!3#b7`;*HlRis=o&a(?qx%3JcdKn=)6a9tr^>{J2e zBNX9V?<|RB1Q0={fPq_yS1H2MbGYozN}c@w?7dseZCO?xHpc!>)v0r;y1J{Xp}}by z+B9HnZVD)o5}R0YJ2DsowgCa5LB_PX84!*T1dAw`$bNC0m-r89wZ@p=_{NxX@4u=qb?JHZpS|{6bB_CM&b=;I zfX5eMzL@o25}h6c{#<5H?Q}T|-vn?Oc^_Wg#o zx?kSg@w$z>FSyPgfexHsm)mj2=Nn1B34jai1Hgy(wujBTt-1bvLj|uaGLVPcip$sS z+wr14oFBIrr`I<_+}*%G0vWl5jSCz)O_Q^?bqeCZ%F7Z8JmYD+to^!DxwE5J8Z`n2eGTo*v(Yr=mp09 zCwpc$NsD=0#AA(H%1kdX;Jgotb3R-Z^6Y>y(ls5a2ws-Ib?JPuFPO^}AnF=VOr%a` z7KgSqnC9}|d!O=Sf9Uyl{kt!o;_hSJu4!f;+jjpq|N8sx{lEX!-#+O65I}q$ zz{L%DboWVl@bVS-*GB%~0M7xyD*K?5 zpA2#4njM*}{50qKh%zYJJpJYdTgL^8g+Ke%4vO(FbC?7rxa@x+hF zfNlFiN%3k6Tp;@5Z|^e&y~~alu6(eEAk&*Nu10V@I8O-wshyX{pm$$5`GOD^tC)wp z&&T-!%k_co>i%&Qv4f;rZbDCfrHjWO=iMahTO|Lwgt|nU9j_On7<+IN#dYVKOTH=O zS04Bk9(Z*F{<7}&?995~`bL3B)-THF5@7p+eqWT^QQ~c-;YU8t_2i3t++6X7@e~*O zE>~Zi@BV3zMD&Jc|KiOCfn(_3t_0U%lXwM*I-(vJ6(>&MA+3_0Y@Zq1ztMt;)*eFkZTX9pListjl?dR<3N%cOC@i@oz*aG*-D@tkX@z{vHs|VxD3D|1&s%aLee`g#J zuw&6Lme)jqN8;*ZU zkF{I3(NJh+ryqF5bt+Qm=AdzEvt^jl;uV3V2!qT2Ck4QmN_-R>?#$YfOwyutr@=@j zyfF~48~%qWSb~@GKhkE+eDp7rSL2TzU09^G@*x-rT$e;9i3Liw=ty-2@PX&jRUFN6 z$g3GMlL2qObnwe~?PWb1<9JJPz>WK1&N+q5tOzRjX-`xzumx{$O!50^vbbtrl+zgiFOO;x^W$@DIY>|oX}Hv2AVpU1-A6E z?qtyU@4E1u*gnMOBA*4FZT~5sXn36J_XIEP_zq5Y?t~4b{?**+e0VOm_5BXBU+hAk zk7@IXCHS!S90AgEfWHK(A#zcda%DQf-ooo00o+ zkx`q9)>4*ujJ%MAd!q5fzL`gNe+773Oe0f+T z{kDq{TtviwB0HMNQB010qu0niU!GJk>-{TpW7_DWAQ! z4Tk)+4o-3;+Y1#@;o$z27T-8^Rw%&05LYw4OWtUDNSDW&QOpt_XcIu6Hu8!-qJ?qt z{7AsSPp1pstVqS2jd^}97qfkv=?~ni^vAiwfZyRmdrUkv@@V0s-^qdxl2Q7TL0^Kw z_Q!>>!;`E-Cc)=%aZRq!@@H1*O>>Bz&YzfnmI2T0jyrqjG3X=G(2Y9hjlCo{4$v}z z!!ZfRO4a8nUZVs;JSa!R#pk@w0nKnz(XKeRTjV$n9dip^-r9JmBJ=}{$7swkiq4Qn z4|bb)6C-TTTdotI7yL86fhvB4!t;T5JkLrDZeO+hR;z=KaSDI<%&YBI*!@_x`4@-7 zc6IClZjY6N9Q2U#^5`erg6I9=e7`C#)yQ5?M3R$iUuA;BZt@?F za*zk?a|Du~QR3gdagN2Shx~jKO9C(FBD`g(iR~>LT@GO1=-@Hl4sJIO{5iUuXU2=` zmI$MWMovx#$G2V}xbc42f>3_s)cq0H_^^?@SpEDD=cf2bHJqBIz zp!$P|%Hi_CTfgahCY*ml+Dr|f(Dw4LzW;AN{N4ZU2jf`)`y~{5P&w#@Jid2R?!SB$ zB&p+as0Hvmu$GIa;;Z^@b}a> zK+XEjJRi8agiZ7LSI${Wh2$*QagYUFos!o!r>Rmqcp?S%HXSa@KPA5a1D6Ws6M;AB z{b04YPBz(rumTI&O8hyv^u2j4pbVL+N-^8=>t|&v)7sby<5W85HNff@PVrSP!$x>Y zo#?we5qk-d!B~4(k(}`9AuFr%P9??$W`c35Zw9ML@CXAJwoC9U`a6H3Icd? z$E`m1xhFz=#4Yp%J{OJSTqDB{PNJpLLxaENbHT?+Jd<(38vD*<4(p-N>E~dhxZQM8 z>Sy~~(9`9bj*cm=2~VeZ?ar?@)Y|!EpFVC%->NPA%#TGlHua#|V!Jul(aAVf`#z?< zvr}XFft+3O9WMh;PBg7Zn>^xEHoC0v()l36PBc+GNC%#1n|OsZr@1<&BIX<;H1ixZ zkN&KSAuG)N8q5$J1>K5>3H%xlV;xVDiwyb_uV|}#-0)kh%xRyK05zYW0a z@gT+Uwp<%HP$3xS0*T23c_feRt{fCD{Xh8C>nc}I{n7vQ=`Z}rzkDXAAD4FBRPu3Y zk6-%5x4itz@Ba;Z{PG_-?3YnoT#QWs)thqn_f)Q~hEh~EAs+M%J|*;LLChrgprC>;%lmhnLT$lBYbWoowNwSIt^&zh0~ZI*#?@p(3%Yu z0I7IF-_;)P6lPZ#&Em}&$$VM>@Vr+N(^kIJ(Z1qqg=0on$CKnSXT_USbmDhu#;xY? zIiEkndmS7rU3UHF!dk)nC{-xk0_3bBwWI5V4V1Cxv~%o%&Mg-MEBHu11XGV;%lNr$ z!*RCFon7bw2ZDaW`3jT_^cV+D)^#RrbSd_0&V4{`poAowavstk7y~r*Rl55;xN5Tv zrrHP3x-y=pJsd)Si}-yS5RZK=rE~jfbBgAn7)PhJr>E(d%?;t4u)WULuamDhFRtL2 z%pa^b@!zuXC0rGWCN~<1Nmi5BTCAZOmg3 z&K+cdB9vr}u}1Qx|LIls`_upB@a+G$J^1gd?@T!SgtXV(EdU}SPdxjjcW!;wcf8;B zL}DV_CH{UZfIX6@pU**T8@oL@{AlCSfB)M@Y`$?6!JVKIymyFVozt4cX|N9?W1JObPDxDK$r&c6U`H!ndF5&B+vL|29sn9EW_oU*Os=~v3GU6t?;`_7_UjbEdlw2 zv3h8j#K3u|u_x2u^ECo*q7R28yMcKkIgsP`fU}MD%kNC21o&hhK!ge6QXDn{r1wGa z3|d4aE@O0g;pOsWDF7ZiQUiRFr|rf8%f;e zBTy_^mMOwf9b?^xQO*kZaeiERVFc?yG6Q%4TVQp+mlW#p1iI} zOtNP?X+X0Zv_)#?Zv>;*?tZCl;S*y3;sKX*_`m+{AK0(hyv!1JpVFVoM@wn}iMf$87(w_ypv zjlTm3$LT$2=wtCGrAk@9BwiuM1}u(s-cBw4ar{JHsU_K1 z(3WyrI?OS6jLZIY^Yw?%e&hRJYm0x8 zNv?RUv^&52E&s)%-~WZbfR|GEg8}uR^7!ska{uLH7-Ix-9j^zxI2@$hJ0&C7WfABw zfCyk(okMhtQSe)n@$30EIT>&m5zfWFS#$jO$)w}Bb6|DfXU7ZJ9ZzS|GNG%;WC1tD z%vn*q;16+af!cZ*(|3uRo1!)0mDPf;|)8wdn7}Nc2s^S2NcHikVLSZd-KwOY%ttSj>cu zxm-jAoSEKzE<9wgv$eu6*%UtKCC-6P6pxt(;-#Y##VvQ{cjN*ruFzLpwO5?kK^a@3}gR+!*=6BKPY16V#Ga_=LB|Fa6?MUjD#$ z{U3IG@iRBU4xw(xH{|YzZ^-52p)hn&x95!^M7eZq1Ih zca5{#dA`JcorR@17YB|KNKORZHaJgkw@>otc@3Pj%ee@iiJrxn?t97ql++T;S#qGF zX4*m)HkZS>4HnjJ4{757Wry^_+){YU_|E(ceWPDcV#mZyNz*#G2Eo@C`yCCIj}yHC ztEYocCswhDg1#pHa0;_bOS&c@H;`Ha@<%9Y2TL-#E9>;+o18QnNf==>_LoMXQ; zn|P)6*1_jKkvX1o?R7@`OioQZXOn}^O*b2P-U-|V$NtHi!QBF$H&+sUGGdoD@H^i6 zTEeO<_laep*nlpy0)27%oO+-cGp$vk=4hyPnJ7Gj*fMyR-7+jw=tl)zdp~uAM%*# zX27g@T=y8S>$>J7y#NRM1Kk4d*u*&lIr@CUH!RrTyB}`)G{EQ2z=GQYIF9W!UCEW) zy?SYT<=;W%;$r`STDLW-T)geK?!Eo5eBo>H*ME`M=q&)=o_O|4@7#LNcYeDaw;yJY zN7fR<0i@dkWW3IW3-Va-i3hsz-2*S;+wEmL(D>NKLxhfT zT_YM4KSj#o(2jLZIR=L>$%g5GtX38%41dzv;EuQ&hgYW6lc4uez%Uw)LG4t0NQ>WD z_5ga@z{SbnoPg*_Ot+`mdSFhpQ?O3w_qlPs3d+dG0xrxk+pptRAoY2O{ znEf^aRS|99RR~@9Q(9_{!`vf_KUS@JX0aropF`i}E6m*=MI=;f^2y|&IYqE#J z2g{~69{iMitR#bCyYz>Pat#TC{#fu7Wutdu|fVaZHJa=pVeNU}T#0g|=G6y^SBY z4i3Hy6+|{@d#Ats$4?|TVH&_b%8%xY=aF+F9<7=G{#c-~_XZhin zb(KD6bU^E3Ic|<9^r0Q_FYT$W>Ubm^qLSZCP)Y0K;H)0v_`K6xYPm+{+ZgPqW1<6n zFpk|Kofa#!`#McU#E#zA^u*eh9!*PuZbU&W`vWEUTD_GjVs99$2zYYU!JJHmEYGO~? zbO{29CX}_GX8Q%e?Ugk2eRNWPXAfjI9*6qssX?*pCB@AWP@8jXa}DiCREmwDZz0_L z>nf5E_|dkRUK#R8eh4~>q3lZ?E|Fe0O$Vhvo2D98bD|UW!w#yoBlqmOT@}{?quE z@fao*;m&LyeXD_27S05xB@yL;^Ew-H_|klKZN;-S{0V(We0;9y!#rUo{VPJD=uEj3 z#wgwTI&?SQpp|R~K&J|d_eAw7!JwX$001BWNkl%l;wwGRpy8>(S70||g9Sc^gfiVTzMvSx`($HbftRbV+PQ&CHe$9* z%B&rpcZ`Wr*6=okyeq!UZS5?w(H+8WAail1b>BW&j(a?M}cLKkJyv{FpNoMHRc zjy2+i@r7+PHeU*L9r!vtIDSTRqMJLVj-TOJoiF?YvSc%zoH|&V$7dl)O8)2A5YtuU zjPn-!am-0$gkXoj>b%6x#19aiG;&+%zO$><`RHev(f1&w`QE`x=RpROP~GPghBuM> zOg;fyNS+HD<9Ly5vf+2;BSoi;9nJh=6<69=KB24iC(?m~b=o_BEV`S;IijoXBk^bEiXOflmm=b!j-4PLVU@Kt#GfrEwOZQtpGfVgG8BgDeIp9LK8wl_=QH ziHKavrQE-IX@5hV#9H*?Vt>fyjTio}yKn!|FL~iJ{`9?-cs|kXHGc(wh{%n%e967r zfAD+1%|wpdZ2*2Nz^0I+iQIbr@&44sZH`~&f3!{E$D>~fu-_7J9EM;=SF`2+{d_p{ zxV->_9X>oQjGByI5A|w+4L1}t9NZbEz&LcCk|_~Eb7)fM?Q_j`(3reAL291ku)?|g z`=nTM*~OIyCxWXR7zS4vq`(S#!64sn+H8cjm&Nb%UGGQ-FZ5Cl0NiC!xgR5~fd^^W=`hJp zLEiK-E|;AC>MnlCf0ACvAr-D<2V58cnL&F>GUy8pER;hBPq?ocr_|^8YqG4m=fxC` z3;mM6Bfb)v0B&-BmM7{K_`=15(r2RA^#nYZB78s5@<{g1Hry>T!{x~z$vw(^KIR2) zyRZg&(tH_s*oWNqcs#}lgYQ9a6A#;snPv>*2R2fy!WhHyKv^)%2t$N7^nvimc2EZ1 zhd$RWJdrN8fsj>~-<%IEog2Fv&|asBBl}k{o%y#_^EDEcYb4bl#>7 zb&%)BSF9b$9}tA@W4RVuF8))ELbxY21k*Xo&+R-+4A%E?oI$>=f1k9YP(zw8hH+h6ym zA}WXDgJ-|>+g|I7f05V827rjj%|GzIe|Ym#|LwoAUH-oZougmkVIp=VPd~?shkO3T z7yj^CK!2XVKGtW6cr4HG)H4$$$iN0kYXrRgELtu(V8q3)060n?fSO`z5GuJyfubdi zQDC9a+Q4k^5g`ZLYXwcLy?ur0_ezZvwQGv_-_o84t7a%i>J#8*j8Uy^6 zWG0nnb}L-07>&opg~-?hz}F-hT+YbPJMgi1i-STx(u0L6^WH7Kc|!*m6DmXgV*<&D zZ!y4ttevwQqijhYDvBx3gGITO4EO9X* zG|u%<=I%1U#VPWkBu5T&isSKwj6OV9;>v{1!Vkq>F7z-yrcDpvQ$T(xlVaWc@FLC~lD80DQnL!L>x)eI-_ zgFHI!yV`^o*O+U%c*yYq@ukj~KVq-v9ZQ#>V?2g&LeF_gdDdfnCL+ZT z?5EhwHJ8EoPz}j8)fXJo9Hhfc!X0#{+^b_z&7Y};((&EDW*^;TfiiMh*gvPANp`pq zA`wrLtgXT*=S!ca@kw@QPsle81f2y)cCcCeqPU6k!XA)@<}EO7Jm?zAhk1}mAI??6 zO*qTKeG#wGj>@yiX0d>I=x9r}Spn6xRaW}2;qhv}@#j`J`Y;Fhob9%8iFlgg5X9cQ z$2+2G@q_b0b=(4ADvxh{`QLckXMg8EY^3;_Yn`fJv+eTbU%h$f1K;_J_V`18SQNPPHB*CTej@<$!MObxKM^MJnhKOIaCPvB#YBrVP95lbK%A6{Dg=7fq5dk zOt7V%JBQD1!PCv@_$}_fkk`s~T3)dakT4I4X7*d_>1X)SUz5$$HWCdTeej+2zAD(- z_{{p6#GRJ5Mp4(Y59FhnpWtXczbsBq;No;jbU3obj$8hAs}t9ABa> z=LJDUMsC^1ISAyg60aHe?DdO4Z!!h84c3py()OBgJH+jLaa=AzK?>V#ADk<< z8eD^hqK^iCTZbc~9qkMGx4C(R=Tsv*-R4`X&pdAsm+TqOQIg9(4t;{KNEu)=KJJ0s zlS_NBtvwHLJNmv3m5X=$>fKNIkuQ7UGrr>CH6-x~YpFy0_w`N{xMxGKOT8O_!#?FdDHL?iM4(Q{y2IIu`# zr2DB;!33xI`4dj(JSf5j_+|uxXm+0zC1(TPq>Z05B)$hGX=<5C`dp!@5{A2IWog%R-A{bj-6Miv-u@hIY!t$$;&$ zOg2TfQWl(+=@g%$lI)G`vE^$qmVJZy-~~sq2Z)EQEfHN99|9K8C(ws97J86RZD7ji z=bXbfzy&qNu@cV_FZOHZFXcRf_n zHL`1$jS*udej#rij!B$r8`6$*(Ozgw#6x~=1k=GVIX)TKQj(>O{wZ9zM4y~QxzKiB zLZ99*{l~@sMjHkXj&a+$OL;7hueQa%?!W%4KZ{O8^l;h$>V6IoR+pZ`M9xQ)PGV5g9%uTOH64EDr=SD$ViLade^Epp+N&c$>!23dX@VXs^%NKzj z4(L(n0dMmHU_8a%go*}+OY{X8=Ew4%Fl|UX96P;f5hGBbfb-$42|^nQ@x*B7i8dg3 zyl>*flc)h-S=2Y;bUCHXf<>Z6nam3Cfm{e=%P&3^A$p4g4hfoc1QpFz?8V37wk+JX~mMGX@tZole>I%L3K<`Gr2@o&6ck zrNx;^(H+JwZpEj&W00*MpIYRy+*lv+saf7QhWV|9wIo@l*gv6V!fIze6nZxPb0!_JpWpd%Xyn>gUJ7&3|6&^Hu2P!EV3^|5j~qzPf5J)&SJ zE-@LQPl6YuA>1xNFnt{@i*a#W2$NQ4c#eX|doB{M*6io5R3T{L?Y=C`Y+}bte}72idMZk4=CJl}ER~<_F*Q2fzITjWk}f zZRJ<5>2~LrzTu}H{mT!0)8U}oI~XsiEj`Ou9VT>YQwcq4$Q91e##-V&xK z1`+nE8qBp?WH^PUpyc-fVd16_@x%BbP$d7tbAm9)afI-Q6sI!Fqz72;p!tPe9s>bA z70=Bv&MEzbqvI#hGMQWA(~MpWQZpV-;9{B`Na78uBZdiU>j7q_j}Bj_D}z7><4(yN zai$_$U@ncpB!;zhCeKZaB6St}KT@o@p-r z%JL-MnC=>TdYq^))&tYy<1ksbdV+cBm>sm?A!Nw3qF)l9Y5YXieB9Xt+rTL}-J*nL zwbDcID$?2(<097Adgt$r?@n6@q_wY=%}mceANi#C<#3{FCP%`LxQn0oVDcwqr^JvB zPG<`ZjE;xG8~F>6Skv%APMxi`vV*y^d0nn`{4;)148VB~&raZLBi&->fIPQ`4ZjfY1~ zJ<-qThNts)zANd!n5=B@c(gC-N8GeQlZb3L-{iS^0NPYyW8i2ecaJZM=y6;8`&v`? zThK2=uAcnj|N8b9{Wsq%ue0{Ly8^)5Q}6kazj$%;Q+~sa+mrr}{&{7)(Ot>yXZIDI z$^I}slcS7>0gkr<9LI4aC^rRm*F$iIB zXOE%+Af{8&f{icQ)Brp%H{f>U(fCA?PuOL_fjA}DEbf>e^MIa`8%yM$1Zf^;QWLyM zNzh`=+!;c!g`|rJ!sAy4&NT%U+Y~h9zcm?!ht|cf+oYeFY;4f)V^g4ZKY*)kMv|jT z_+vsoHliB+1nqOVUH87xRI@6HDh78XS%32PQVCOJi% z$UmbeTWQB2Ura2Wi`YbC6?9bQVL@P7Z%7ZuI^@s$lWtIE7$a~F=!SLHE=)-V9Cr=+ zcyTWTvy&S))*12`Ht*vQ2I-J}gyC_HN;*h@03OFEvLE(CpRY779b-%;d8p<-)<5dU z{Sy1irU~-P1n=Ji4*63WYME4~6Y=91j2#Xm?8N6tg~R+~%)rvlcXgcbIOtEZNyZ6rh;j?(pHiQpuLC{l2PX7g(m^XHj1>ps2bD}p zFQ8`e5zmvZXZ*+cy)WT%zn|&HoXq+|Sc2gss1-W4w@+*nQk?=p(^@Iy#ebI@{o(M( zqsRlhD<(%#+3)$+?N9eB0POH7zwzL0@B54LI&81o4S*+}{a0VU{W(AMb)q-!x$^ya z0RD==8@Ddy$(u6bsfdVeJ36NI@_&Dh!2V=_FcT46rsPckkY^;@PCNN8W(I0)UH*wk_hSpHA{b zesF+PAHzi{noPH8k`}lk#U|4N6kVZ-H^@seX_aDo2&=Q(&hHiE-Yr-QE^J*q(X9Jq zvlL%34wg*^pg%S<7&pZ?q_4y#v?&||c*}g^Gbw;$Bk+;@Vvw3`UVtZ)c(tqn?x2rk z;^*9sx{Zs~#!#2L3eUi+>l68KT)|&MN3ts)i}vn3+29*<4f{Zf*+?JSx@Nk@WC@)Z zz#IA>gvD!Tb=*=G&(krujcot!z{?wB1oQ#4#%bWy2eH~>oRdd-iFz#Lz(GKh*@n%) z+#tbf>=Fd&dLp_ux_4QC43;@b2E6#nK1*X!Hp!33mf}44o1agy-)mUl142T!Gn}h* zuHylHwc&+faJ&?xACJkluuV6&m?p5pfXTcQ4@gs~lOZ1XNv;`<=J=1Kg?G(Y4s38i z`TI$Xr@rA3LqL~Ehkw7+hjM9;wuS$N!`T-8+baN{xOe}#@A$gs-}P1W==s-2d);mT zh=@G-sek5!x8C(F-<_TZ;I9eX7xcHDdn6YZV~*|7VLT7OBqEeT84s|M&fCbhm_bhjb2@8!4capaRk% zAsv#Vq@_VRhM=HtrID6~fi$Re4j4!d5Jrt3pYP-G`x~D3ai90xbM83>dh4G)c==cX z?@Pu(Ln17nO#4AJaN{1QbudY>5I`4dnU)-_A$?Q=kXG?AkfaG!XwNH+X-4qmd*e#x z(nF+FAu2S*L|*!L6%)CvO8kmC(#{c)2q)b67Vw^$35XwX@lh54^PB0EvnDp@o9sL{ z=++AJDmh>C1-$fsQk2?ywg>jclKvP}B$7MMoR7o4bt0#cGkXiIEg3kJqPlz+j!(U( z7HRN-t0a5Rx{iY3(wj}-h=*xiW+xJxgc*D=M8m`awX(J7)Bfs(pF?@1qoPD*8TJ$& zLD5DSFxYn~)lGHD`;)-CH?k)vHC@;ruVcgfnoGQHM?-l)xwOlLu9RyGzBgtY*^sl^_3!t3WkC+QjhEx?dp!*L!Kv>$Z=B3W?Q=BliZqd ztpdv2-s`W2A0x`RtBNc>-Y!$P2iHGZ1@VuRW9gX5eBTo;azy(pvN2=|Y zOlKj>?Y69%3L-%RfWVrLl(uwde5xlc<&7_7)!{2goLKTnN2$7I##-Fsjco*Xa5$F& z`0Y*>q`9@=^f2f8&XnaV1U{?)pYc7!HB$fFZFu8&+3Fe)wiyb$*zUdER=)0_NS?ua z+2uc+(YpCE}rIf3rPA~9&ZV2o{ng~4{`T4K84IRpvdFKt;mwqep{udO5JcD`jY z?W$lMNW3>sE97m%(Ks#9CgWpI!d)l%#R?W_M9TJDlmn{ zq0(VT@xSF03*C5uLkFx@$}pYa*ITtXeN4tGo*TMyR<#02SnZPj(2%cS*I}yrQOnUU z?D8cU*WNWlT$a%SS`yjW z=*w$c(`P6I&H@V90v9KFd4`DTZ`hM%E|un18Mat$732-=3bl(VPHmQ*E}w)QDY6IB zcRT6Sx@G_4nwps$vv;&N587@h*Y*!TP;?uGTrdAEEz>-{7=xM}45eI$1;utJyO)$9 zUOn+5qokz9Znx6RDP)_)o2c5AOwx~hB*i~9^TbNqrT9}`IaI*b+apS~wwK1EU|3{rGDN#^)L{a z)17zNB+~9I-45ipZeIzE`!L0(>>i)8{b)Y4q&7*_QP(HJ7T5NuBT=$eh_vzjmXg_H zbRks(Xv&g-H6A}BaQg*mF=A#>ONb1nQ~6Ak@1pL|r6^}Roh@9w6Z~f4* zZESDt<0pNOa_QUc-87e&PrI2LzvS*V&@MBas4z%z_(hzOv*iR%4idQdUhR05S+4Qq z+RLSE=61GZTfC6EAb#>OI+y7dGuQVzF7dkDeyA=H(*;c5 zS0Y?^EHWh$K=#C5XxNeG{CCg8jp-wa{EH_SAX4Ldm?oAz&nBsMrS8qx>B-JX-2MA4 zj;frae%gKGq76mj6$Wp9F;Et2sM%k!7dflzT2HZ5y_Vc^&C<(A#IwdCy*CRG}Vu%R3>eeM3LXwauGhJ?$#s=w!VZVKB$2R2(iQUunbm49z_8WVy z*L=dXou&6@tZ?Bvo;1d8oZbdj6vfwvUbVMiHy$J_0vAa<9*#OU5pM68XSKs$9gwj; zT|GnJ49*PqkLK(Np(4YP7zoe;{yFXWqjbZi_UPOk@S^YXPPlFe&Y+eAyto66zj)D zhQzf0?nOKNp4`0Dk*98FDB!V_>97EP{b5XJHra+VIla832dOaa(vH&>RiWwVbv&FO z2sPD+snMp}#B%O^IU>1qx`yV2677rhxG=erNK8eaco|2cUatZ{8b2j7sJldDxk4F` z##L(4a>EUYx9v6tWFV#57Cpjkoo_0I-|&-o59kd`DIi>lX{fhaYl?}cBgqPRrqs-x zMnbmNbhdIQE1MG6RQ@!LF|u)!loqw51Y!65HyXSwc%g}iLwmYgkN-C<0 zIpaw7=F6F10avH40~aI(MCs{l%%`6hn%w-jrgH6-B%k7o?@DY*B{$KBLPtVIo7BNw zxndA~jrH$4gVb+9oxQ_X>=lnWWL41Lv+*RYeKjrOaNuxeul9#&%64@y%Qj#a*j zNx?e!Xz{8O7c-9iNqns2=^6jOeV5=0F6(yhPuravizoTzeh}clIZiYSmGH3qY21WN z+)^m}QUKuGfN;7E$M_>Jl|eG|njwSZoKOm;F?)sA3Vk;g(%P%SNdZp*zp+4l(1aLW zl`&wWzdk^IMMF-^9XrV{TDV&yJn?>mSi0AyUIgG7RN;E}kxQ~u7;pr&KB3`0PjDNK zB^A_Mm57web15lsWeF(b#y>`&kli#cm7Yl1$9g5#WZus!LG3^!ix>J>D^K_@?GZ{8d91`^W%s zXT=Tv9Dc;QFKyIDgM~6DNf$8gE@-D1LtpV@L6&vT;Qwm@oNWuJ;YrH03eaghI@-4n zDZ1`yd?`*Rs%T?Dhc@d(eABXeXuC@#aZ!4nE?UcyEEWnLiw%>cBAJM5)t91JD{*r# zM9vnuIjB+3P?_TQsUhr~OD}*EMUDW)ueDE3Kjq><%E52_d%Y?2J>^Te;mOUUfmAFeKK<#YpF<^~5lX!UUa;ceJ)@ z9@`G7(n4a2*3hhVy4l9@UU0m>rQ3jQKqG-~y2;_TT|CJqH8^KNzGJNNtK3w;F*;sB z$@FJtJ}MSXSJ7tCe@R2b-5=%G*)jDj{v5;Q-;~8?@FcJ?EaD03U3=dnc$~q>lYw&# z^?sEFhlGL^RqyRTE+5Hz4?jcTX9b-%E#sG4x8}>hK4m?Ki^pfTKJ4fa`fn%vZ*?Oz zA>>Z1qfrdss$FbPpjGXAsO(O_Y(ZPYn!4XaRPuomztLe%M3U&Gi4eg(caY8RIZ4|S zM4>p~rE3B>IH>UQ=c+6)0Bn)1cwCbZVPKvw=17W5?&)DGp$b7PSaH>N^e>VWy4C*lcSHDOlY!~A6F<|ZS|ay0{<=MWU@s%2{XMc3HGi3T6uqvEVVg@XR{CuhsWfx{`5;$j7 zI_^$MC5DJ<$+6}c0OyxNaTaUQv4L4cla?eIv&Fqg!g!URdyc{I%*zub9FhWil z^WVG0n+jDw{bdHE|3vUP>JPfbX;4nep+7UJPbG<%()@wbH*0#pfV1MZjKW z#WRiKB=HV*+-qfJSH1d=ff68!9ME_!uOLbKclELvW!k%*@n28U2iIg?3i!6EdCPbHD~hoW zkc?tP`)WpIFf*P_?^IIoDi=*5QA4+H8HO8_+cVpZbs?Hs`C1LN66F{b`=(qmFl)iC z9TA%5WB4nBqUB_VL8;KcR>Qw?$c1!J$ z(hrHQ8wNV<8`R&1tMv2wlV3PV3w*E5UiBVv+p*|hkh%Guss7bqLyMGRUV0e3qXa9f zh^bCQ=?ezF+jic!acq-;NAPNNc{yGltSca19xeVBA&gJ$kHhD&wlwe0dhY71^y)-c z3!`5zEv8{JY)ynakT39&h%n64t!$M8iKJTA?od+0n z*8Es_s-fW@B|WFR9w^&Vck?;|ja-s`~4A6Deoh)pIYx6enfQQ*gdxMA)>zeA#s?O!$Rjcp2bA8ugW8t zU?Uy}D46E{;N&A3)ecKes;`z^tB{XnDB^)^wOm^^VH#z#lFT$I*$oMnWUK_LjkKf^ zEK1m{1QfsA#(>Mbb?!yfi+#JZsBzB}+|~?gr^oMdIyE4FFv5j z9c^Dfsb4F0Wn54yXpyHivqSe?s1L0nnVx&#M_=3BXeKm6XL+{ntP3|-AGSb(Y@=%I zky=o}JYRMHOT0+2&1JNxZ^SvPQyY-y{k$JaXs^zCCA>u-0eqdHtdUXCITktluj{Kkyau7a>L)e2FO4zKErYeeBylco zyquVeqJ8fq=TW*>c*sH5bZVr`Ow?8l^N6$rTc$T2hW2=Gd%%L5vPk=p{eqOoT6GGs zz27e?edYNkBtJsyn3kofXHCqv7Ha^)+eR8}?Q^{)({DFU>H+s*+(?Ko^N4a04u=(s zVUgQ>50bnk1xBky9q+OQO2sqXWH}d}{x{ys_RGvXSE`JkWzWmy^}8mA87U4s<^hAl z?bmz*QQu@ZWA$C}wZ4P{kK0sh5U7~Y{qb>7`~ry|ev++?8waxrU_8|0%nok~_Ept$i^x?brM2CwLO^nkc2j;woBv*XNB2-PpfSPSv3-|Q9 z)VgV7P}*Dh>8@(s^q0U#p3@{e6<`Bd576vxZZ#W$E=9;AG~}58y(4|XsptV05Y1NM zO7dP4P14MjvoK(n-qWa(0OqGI75=4Bvk*i2v;4`~tb(vC*{F8MZ$+0{Bosv2$pDZM z0t@o;<(WYQ=7o2b@htu?M{GcE(Uy#Qpoz(BTKdssJ^w`i@{rb=Lqp+cZbCC}3tLv6 za5C%HUW6R+^cE9*tUI~pT^{aik#D@&5)euD7B1lVF7q*~MaENSk?J=FV@ zwjUwo>I&LKm(M12+F84NL4H#CF68Hb*D-yMq6GIg7HxPNg$GlZdMKJC^6N*Dh+nhw z^Nj!MVPMPY{OFC}`G)igA?QK(Z0jXP8vP^J3wmGsg?#YA&7<(JVkRH9>M6V@LKVz& zWzX+RP5mYVVXK3rG-fDrc+oB`XalSgJOjru%(`T1=-*s&Cv z>!_ws$>MkscOl|E9z<*YDgpHy;jE4l&MmXUpsIXi1LKCQCx` zQYH}}%%bOVIIgYp>~2PX7k8IF;yL(#Gy3wGXV~`6_|+s#EUrL;E<)$#JKWLgk1mXEwiP(OXt;uZKep07K*=Z8?>s+Fk%w^YW^~MO5QuH!5V{v)c6{OJqNATha=JkT-9Nugw$-XH8zbtI- zshzsD5b>WVkm#gnX4TP9E{kmV8E@b_vQ7+p!=Y zpksV(3z2M}fBc{`PvC&W)fufS?8Mu%_ir?m*qUj7s_vs}0+mX+QJ*Al9g_LJ`!SS@ z@Z#rr_H)5^{d-7?M${uKFMiGph(JG2(IEBqfllZbUe%61kbLBj|_IB^h zhtjwTtM=Q)bNKHQ#Dx=77LEJ2uw3(hYWT3fGq}tBJ85!t(}N$fiv%_MdLV!PQUkWl zJhX)U*p?F`k+eK0Wt|m-F;$5%$9r}bM_^a~Ub0CXAAd|IS%IWEKDE4{>=RxWXk8Fe z7)73^LC*x_nsy_efNb){W*&pm=qSh0ctClvC9qo2y9{e1yc@>7x|Jk=`y9@2P+xZ@ zd4xc7Mb~Rt&MTuX!b{VpG#xjEXHj?qgx^D%o<@%14+YLTCS3>we;wcaI4}NqQb37S z(|V5#NZ*k7#F;Yr=p9*0#2Q`+cfoJOI{DF7s18(*j;hOsezr5D8(_5A`r z3mwi25OP87`6_x_A#4m4&dN)gvww>$?MVFet2+#~0kytHnZ^)8!&U~_(00%@8L1Wt ziJ{4=#)_L>_1o*?@P};TobCS>IuBX`IuBFd1+XuYU-qx|jVWM?OnE)}JM zR947k9ZE zg6%A}aS?!dNRBMRANn)o){61YsExtw<$b*3t#ylM(sldei0#4-@|%lCz24gl)ha@) zOwxXT7=c2tJ_9gRyc05ezpFPFg|KkAvfy~cq-Bx~=^r;ofN-SWvI9rS z!#<#ZNbkraMkTMRli;+Y(W5tnc{>C$mv%*l+G?YEI(v!g*ggB;Lw{d0$vJl_ zZSivwGH;t`GvO8WPxEDVeYp}g{dicNujk27<2ZSM{EJq2Ep;HcAlMRcaHo$sYoUX2 zZM|#vHQ;1=R zF!&Z{K^k*w-^*d4PGM{GJ3X`a|JzxoMsoa8KJ7PMm6_we(6^nMf#$nV6uZEi%2geG zM=By7@C2rIs@BXNd2@|}%8Oou07{bp-&GB025rRN@nlqgO-~=56JvcAUYR%6&w0hXGsqT zJr{rN(tKvo0}3Hkmq2sjv&EqGnHvmOcRd-mlSPPvMn0$xVnDar|CV_QT3 zuGz4=RD&X7j?;o;%0dTcH10*{!M`~`m5X(4Xplh>oDjX^WF$VT3cmE!(;VNIJgf-A`zWbNTc5?}{(W6v zSB>+TG=vTvJ5F|d>!l2r=M!{;=xtJF>emAh5XME?uiegGPdRdiQ2Q=4^}GUCxxEnz zcGxuLUBQKrvG8=k%CV=|vG0t%dS^qJ5@mzhIO0#5m5x6cOAbufa2d-s_qPaZ9)2S& zjb;8XzJ*G8@M*?_A#TGD7K@xN7Dr=Yuko4cQx*B)oVw{3k6;Dv+8UxIro(TTj~rV9gbj5?JRuUON79IiCH}5Jy}d-)@NA*|fr1xUiIymq|xNZaD$BeP3-@R5LxiPde;@TH}p96gyWfy+VZUo zJNvG~#(T zvy984z@O}43S|n;&JGH@hg-aQBPNBY5!xsaWc-7aWoNjJ59$G)m(?V9FHz_AhLxX4 zhJ!Jkn1DuHsP-inF5{Pgt5xMO09y z-VC59(0F0zKN3-F>Tgpt-S)R%--JmYu)+4XY|Q8JNx}sd%u?)&%~|<^r!Hcuzqh)0 z9nac@t6|&N6fs|NA(P%r_!`n;*wA~^j*4#CO)uv^%{oi_VRz=*azMvd5$ys)&32do{o4ENE%(vIS&l=`-Vw>mZFr_;Quzzw@v5P z*aO&kkMeC%S$p?F!8+O9N}JUnd>M1bd5Q6C_r6+!IXT+p&~@Spk{^IP7F*vhj9XAf zHH~T`SwY9UAp4H}anP*Ei-qZfr3ZZNCW?{y@rOxyubh^&+(Pai!P>zZ(g(|Q%oF5w?sNJA|A8%D8#bFKo>2(?ZPC@S61~#Q@svzGt?K4 zMBlUIT^_JfYIhmoT5~XN5FEa)K9w<#w=LZ3VB#dQn)5HM(P;Yw-bRp}ytg%kBzTj7 zwVj!L0}phGn3Z#(g;8Ek7e8J4!U~5wB)Qt`DGN0n2QdlsH&9Zk0AlFU*R*%h8V9C9O49JU?76O@%Ohj5Z>2yDtH-jVb<+b8=L@0TnbTfT1>q&@iV)&fI6_cXH z7dFrKEA#>E7Uvl(BZ|{p*7)DJtgw9CPwjEL)`|NaSFwCSUfu{Jw+Yv@VJm=OPCqTS zhO+5@D|p%>(a#Fn6Vh^7rb^=(sp%O_-?`c_m%Z0*DJ^Q`VoG7SXowYJ0*HK{fJ%(T z?-vu*xPT1P)Y|hWLi9mh*pXZQwHG0tBk&BQn(Q?vXSrbh|7tTPb_~m z#H>%Y@(!dWFt(jR#esaaQg`}r7Lo`7A)tpS30lyy)Ds-*X$EL#lh#FmDV>jN>j~$C zExppE#emrk{^8EwgeZYFEAeZR!!O>iIF5Rcnx#7H73CFk_KYu0-(DYk1H^$YQab21 zwg~S}i67Wk+P9N(2_)N?pLxk*M4a(Vjm>*6vPPsjKOi=d7}&tiG}g1{8i^-+OquN{ zco6*1j&IDVg$GpM-1#%TfB$}qR{Kc{d8f7)*J0T{N`Sn_W52d`iBWKs?24fCF|_Y! z0=)=J_$_Sp0rGItTF|7BNq*_RmHq(@efvu=S#iVPpQ9> zV#CoviRF6M({!%M;mV??5+3^I>2j%r7d#w|C#$U9vNi3nQ&m~==_DvPa$$~q%KSCS ze(ICa&7;BWbop;X@{omS2Vw`4JgeP(cLnKRCtSaX_DU3B`>-EI?9mx^^Hmmz6T^cc z-VkQl0g2mEZ+QE^G3(~@p7XGO*GSm7+fO-wy zNAu5}#jxWeUs8&G?E!bIN|yj#(ceV?FR7Sr``An-QcJsots_&~Y&5b3NOK%hBvcVZ z>PrAZ#4p(bAAu(LeREmuo%Kyhs+|kvqThIcYFCD@RUonUTLx^+&-4EUgITreoo*x;oY)rr)X6O-(e7|J$E2FEl8SuEz zuX4^CLwThPy^(bVj(T_371(#JY}PBRD0SPIn$St0NQr$;q#A6XwrbgKQD(lTQIq2_ zdu-z%9N!~}RLV2kH!UDKTqMW#+ewh#h%|WSxBojyz^hiZ{2nuT#+>*$sYih=(yrhC zm&6sdImeSQnFn^=CLpB_Egu68cFrVgoWz+m?v3(`=e2^%my7q+m zFNMdk?y0cjlf%G^DA@JM+0i-Fg!)Q+!h<3Z9&{MH*ygJ$*N0mKmIBg#Aqc%@w}t~9 zyUq;R*~gso>E7?xXb`Z4?sj!;lMb}H9WzMPWW6`Vs%os5*fz_$7d zzM7rei`It1iNlV zLBt*Y{HESxLT{BDnbWXUXlkyFc8`Q9-2-!2_DFwJY*?YB7>0KK@Ne)qUZW&oRgz1c$#b{#V`#8{W4I!6MkLuodUaxWn68H0)#?cVN`$ z*>zU_KOr{#c<4oE;N?m9`j22#<(2Zv|oV zHcIHI=j_dR^uC2;SL+=Ft8u*Ocb-|2n|)6;_i~y9iCTM zY5TQ0+_$tAbv;Gs8~nX-8l*=jkQThIMUFjk>gT%(qQl3|Q!IbME1$+kAl7tF<>cj2 z=x5}dYPtLZPZ$hz%D+tunM%X2H3Hd-NEy4u{AA!@`5MYbS6u^{wmlmg^Etl{oaq^h zZ>y-nh#8SQg`~;Myz~_5d=gt6;XrVg@VGEv*A zOJf}lP<2HslTu@^Mmh+b_k6~mxhWxZB;9w-#!iZG8= z!X;P19T$7{L4VKE*?i1T6Ib^EdtRFbiU?u?vetTMEKR~F3x6#feI&c9D65xs_XN)QFZnt)XYWetUx(Hr+9cd{R5>dRx|0NeS0 zfXYk#Zbk0#ZP>^-rgV1gG{Gi0B$jvd$>6vU5HaS;1W>|2)yss>j(k8hbx$O~hJPw8 zwX@v|?KvEi=^swS+|+COuP5&0F#`9{Uj{DM$G<=#I>tDaTAEHdPIB{>Jc@v_-ZHat zsQBe-+tgy#VI4=t_%Qe&A8!fW2TGEu&z+u^J(FuG`A+=Pm79LsTr8~RyL>N}@nlZo zQP_d|H!R-}`KDbT{dBO1_}Ki!QqW=AWQ5;UaSabz5w(;l$yVK6+$2PYykl2@S52KVq)dW>;RY(SWAimXJDoSu(Z5 zR}p$t`#$$)R6Z!*DfjUSCu01UZkl;zi(R`2W@7i)iiaiFSx=~I+p~zrT~Co`X zPC;`jTONCR3!(^Hrv+c6k(FD7AZKDwOor;)c9m}A=b{OL= zZlaSs`D@$1^}O(~+Y3(YU61&qaP2RVr4SZED2z^?;kY>%hj$Nmo}plZ$PkG>F`%s$ zOV9H^>#R*ypW6dXpX~j6kyjO;E&8JP2P$R*@8gBbyVi@rNMZ&It(TH#iKyZ`CfHe^QO zj?M9Oykd8si3~_taWA5a*%rRqaejR=^%hZ8#5?Rk)$8$3NSbW<9_J{Qr3k&zX^fr% zQ#sB=M~Q9E(ALvvjeI)!nhU2&mFaw_!dJu}~)s?3X) zS~9+MDsWj}eB<}d{v|vS6Wg43m{4RfvqBEt`RDCXc~3TzqXJhKq3}?UE8Dd%*$jIB zrXnV1ZLWlyXs;%vv6ITu=ttdX6Z8F36!rVYP|oA}?tnqA>DHm8jSq`i$TP!d-Z%1L z7cjPG8-M@A9sWQO-&UfRZz?$KrCC_o-P7t`dwoVM^ZD?(rXTrJKf7%ev z)f)B_8IT5_J6@kJhzb?B@XYY(w0py)C~=8>H*ej3q?!Mjd+y_xy0ny**N+IP!3RnC zC?WnXqe4~!4nJp>6qZPvrxRfo-ZKRLebSj+W&w-9B2^%N+K=sZI1z>jx7QrCQu z-fxZJn|M>C>4IJ$j6KDKYOZ`St-C*+?CJh(&h#ZF;J|TOS3u^z3AS2hY+Pn6J11eR zwZ&=kBcc0)Vr8Yg95dTgKSUc)&}}_8_8w8*7|4IDm@|;wD6_89z0mjGShk^-<|tr) z@A_eF)Tis}T1;C`?*7)m8{bmG5_)?F>8hQA-*Ki3Z~R|i^Cy1KU3`JNN!t`1&(SIP zE9nool{O86R{5~d#<`KP>m-TR+nGPi*+{J$;D`B2M_?0tSu5`fuSea+6ubZA#^US5 zv9;!iF2~^TMO`?&aiJe)(U@2uu@ruAh3i{@UMb^h+lLkkE*Swg1=tPiEoK^VrEV=8 zeDVqPw6E&S2`{&s6|d0=Z!zplTH>2~xIh>lJMk!z1Ron8?dt$rt2?2NYM=+ip$L2_ z`>1Zx+-Q@x&fFyW_@jfojpDXt6kbV)Cw0mxhcaQAZhXYA<0A2=UX207-ht9SER0tm zoQbCLF!#lf6sq?^=QLN3FTrE5sr!|{&he>+7LrM>LKy41?H2yV+3)!_Psq{7q|=Lv z)WeM^bVhOs?%99B=0&M{H9u;TrD1CA{P5)_CcDpVLS3d+6xI%{EDBk26Ys7)@TGrP z>e^Zt%9SVnTb$#ZwuDOvIOJ8YzL&x~{`7)T4lB$MHCx=>c-?+wRFLT4=ilzvz;wT$ z_NyA3ZAmqQ@_~bTPD#BeoMQJ|;v`jB{eunLft4>{Iv+u0;aU}+a93CQ;DHU$SIhU> zF(D!zBYSHMdKA@Bcix+#F9nW^E;7ee1JUwH)k)!u;b|Nrv?In9y+)o~o-=6*f8cD$ z3(zLL_3b@R zQp1*DKkk6rP0Gio7ydvyCD$@VN!atdRLjoPCIDNFRTs{O+I5$ZS&6JUPwHw=zr6aO zX{eK+N9!=K=Ic;I;?zA<1Z{1Lo@dBUu=-MCqG>M-L=@efvox>|`jP`Qu9p^LJI(|} z+zgPpa=GzP9ruI&J=cIAIE&dQSX+Pe13#Wrid^Y0 z?4j@fbMGdyzE(L2X>7%6QB+jOJN}%rSY65KrrtDPenCH}Q=I)%^{_YPzLP>k)34jo zO1Mq-@C&b_E4S;G= zhh~`z>$n_azijX$fB7tggs7mbKj!nNgwQ~(VDW0fH#?`gRSY)y%5w%Td7>oufDwmY z^8Yjv|D6`UwD9xxxF?}Jn@--4YT|@!if!yey*(R?4fIHOUUYL2@u+&KFy)|1DtQ?D z@a|=OQzWDs>I>Kh_N0TOeW@yC?yg0Ar&1+}YxU%??-&WrE>I}uh9Q~fVJZjl_)?$QMM9G?+Nod+;k-Iqz ztzS7Auha=Zxbg6$h8=c>k1tW<;%*Zye_+Mszq!-fwDoDE5C`q38>A5C#m7%%0j=RC zSy3EkdXf8Wo(zLH%d?B-nw7UP0&I*4vr;Kv-kj^%r)S_LCYACND~yJcc)kFzvJ`HT zXG(-B!ntk}tf3h@uhGV!E-%HW)?Wc;K8tEV1r9~nj0TvX3ATqrB3Tc=5`SDf;ji z&t_q^MgFV#!RFug3i_|DGg!E+(|@-9wYz?O@W8TUWc9r%f2hTwn}4=vYyVHh#A!NK zNATR4qoiO^`pLtqP^JfC|0c&`t562)klJ)kN2y$6vpCm0*d}BJwfKBX0lL&b|K&yw z^&)ZlUgz+ip^}7KwPU6)?I`@u;~x;NQ%0;q8e2|%aeeErsWzj0??m2?iib+vU;f2K z*t+Nr1!ceL2z_Z?-&#z?3K)uwAR`o*rG}}ADgKVw6Ua#7#n0vXzb{*S*dbJ^rQ_NV z6`%ieEnz=0+#mBl5Vd#*qFMsNPhf4aLMsgW8rV?ZCM{0f+<5cVF6^xSe;X8qVxa`; ztXWF^eJ+V`tWL}PA-`V%N6z&j3Uw7|9o|%ugZ;27eSigzTAOz|X1JgT<0Im^DkYUgrMQ2lyyQcIUV7L&qY5xz; zM!rc=LG0(p9D9OS>7@wSYt$VZc$l()enrtf=9X3re7DIX1F6p^Uk$xw#mZnTK&v z!bm8!FVULP_nN=XlpP@JVTnm5)C=(9%KyZ#X}G&Z`Gev5ait z($s^|DFP`7-YDOf6Ouu$6f+|A&rP&oV-H~EwtML z^F9H!1sN~)3TxM+g`0bC=9X&&_04RE1>8RmY^=u@Z%rmHh02C9k`H8be$_1z?KLJLds&g3e%_= z-{;0l4jBqKa6B&OQ{?G~ zp2lQit~M5rkKfB+bCtvCcV62`==Y-K!4|W+c?NYB_u7W43Ad#F%D@}T{wYB>d@Hvv;i`7ff!{*bpzwgjD1T+-me{B%2 zki$u5A}MCxDZFYUv5~X7R2)M>&MfBMkWXiODmlA5(Q(fA*FSIV&B^zz@WJ_DR+2O6 zWo>rhLu=i+zZdwM8S{S*auUpMcb@zZ_Z(R@IAPqSd^IuTJo(bBfTUBzGmE?kHpjI2 z^eQq``v?XwSaP1I6B62#zZ?Zpx8iOT_j9QWQ-)FYElvR%Z>+DakN9t?(9^h~9|kv( zu#0Oa+}w)glf%)4F`)<0IgUE*t_R7CuqlzjlWZIkY}yh082?YKBASAa#Y2f-B)_)GzJRgmjnt{UyI zcn?r__+DfV&=fb`Nz^2V{G@lt0E2H%jDWk?$zyT=J9R;j>S^0Cj|XfC<|$n2f*UI2 zCYQQ1MbW?zA&|nYRdi3iO3=zw5Hjk{`QXB@)=rQY8PA=sE8-gwT_#oz~g%-UMSvAZxd1l z=c64+fOq7g4&WzMGAl2|<+-=`Gwv6aQ84SL{V6oZbbIgp>VbCq2V0uZYi&yHuR{zJ zzrN|Pbc@-ey*Sw39%I55Dlj!v+i`S+Ww}deOQn7NzGV3KbgD+_e82A$$1ZLYpu`*C7VqW>M=qpwgw{Y7Ok!S3L)0Wu>ic zl$*T{v0a+aw?BeeF#>|Ck#M6>DRZx+gJP{?(gVbt6YDr*ZPNaSzK#U(r{X-xoPHfY z+Y3u;*Z7B2|4VuYxvyltT+kE*#zxOvbyZ|Sl}v4>2?e`iNA&np- z?E#w2C!=t9Ib{EP$HE@xp)y)|z!NtBU-^Cp9a0=pD0Vvh_v56yqh|V1Nsc(ssJ*)rs|;U(spooaYw0+K zLd`%#E1t|lF6@g1+jyD9z~2u|lbsCD#3!!@q+yk4_uDRx!f~3g{wK@`@BqsS%Tbfb z%94_E=u~atLveNZx}rk+r+2>Vzfi@?+>-qCf5d}6{LC!6XQnkUK27O%93+NYa=-nG zX}smN2|unwG+(@hVee7HuU)H9fF;fF0*GhUDg?z|(RnHs9&+_dqQhV4tRe?gc@1AqF+GsfZ6EyZ3PL$Q&M1Po6(E^ zKP4u9faW?;(YQalXV>phqH}{bYhznuPjI4qat}d7a?uq`oiw7JC9y&WXs-nL?9%|# zW!^S7KUD}AIyuQb`2h9I_WuE~Ku*8Y*e%&jkx%+C>!!nJl%R8Q-Q}NoB|<9UW%_Je zVWDU6$;~ll3Wh$})*SSBwm7Zd$^w*(&aMqy__2*CeMFmC%9bvY;Bzbhgs#}91KVx< z)UwRB5zuLDmVLm+`7!?6xlfErjZyL&|C!y`DDy2oZ^;`n)&5w?hQB2nSkd~7n}*c)0MbD}`MU+s_~W$v?JQrTV;yeGa7s04r)QqwO?08HeT2>=Z5hA-CR(gdLXUXlQ`EM6`F zF#KEQ!UUkR=sKR}5&)~yP6EJoVr9E+2>^`V6SNI*Uqhq!?RDt~M5mVksQK(ANx-D% zUV^BSVYag-UWgT|*?3dNXzZ@whU#W`j*_j0ADjHxxi&fti()G~`#|!tv0XU1(VlmB zjI6bp;$ANfN5xiG7Nh&Q!j1W~{hAF3o+Gr!O`=b7v)@MJc&z2s>04@az&v!L84U)MtU)^|UMa@2DJDWqG`_gWFKh)DqthB&|a@3bMSLO;x ztCPaF&4272SSd|7dz&Mym%^*E2acIxfhgv-*|Z>ags~`qW=$85JGeMQ@miu*wCC0V z8n5E2TL0v`0!+WiX6<UB5Y^S8b$As??2kje%?8h50|TT_q|uD|~(_IvNlrPN-n z%EtoR_W}Tz?gl{lW`OZ(apj+<)F+=wT){_m_$hFtCitiYqI91qTi${=-AMpkQkDgu zMDMZ*fS0vlPwIsefRflZi-|}89PQhb09Y9=od6i2XA=Ol3p(qqmzdZ^q)7lQ8J?3U z0bpi@oR#in5&+SUPcbXJ5i^~jIlXNOfFp6i1i;3H@s#lQszWn(jr|x#)JBSx86ONj|8MN6#VY%07&jUAU+qE z2fW^?!;7<;50szl%4WxxWvn>(; zC#y{W?DM5%aWQnmMRXmpfb`f`b@8Bqs83L1s~s10>FH* zO&R?xN&qYkc9u&g0I0zH!j4fd)hlw6KQz36fc4CJvW<2OCfWf?9ftyaptPIETwumH z{6eON|BgK=aUz**ym41rjjloCq--R+EHdEfIeQ@6Vz4|}TS~6O#A^iB#!7?JcMyk& zUFYA?o5)K?TNpW3&7@eldc~NUJKjW!3b=-D4?ob%BgeC710W(MR?)|<0*}X6S!~YZqxG<#oE(hqr`qzH+6*u1Ur~hHz zJ@JY4`;+4f0*AW-ruPDDZ=T@v=_3F(=-W+z;jeo^;3^?ydMO}i6JTto^$q}i7O(^k zl(yu8lILqYhTjBe0wR3yDh*Rximwjwc=&kYZmTu?T{Hn`774MKN<3<@8~>{di@KU3 z5&);K(vPpa3O<{p)O2(SfW?`8A6t;H(l|*ejWq$FH?MON=1?w30N8F=-<2nUqtOX8 zKGyYI0#L^Wi!%F=N?MzZDUu^h0N9?x1iZ5@RNoRx8g z=1gv+kj&xcM!~IOisB{EpB+K zibK2z(8ZzTWpOyH&5*1YiT|v##wPZytaY57B;u#4%IY?ijzS)dsvb{q%8%#VV;Ym) z`KptGL-}$xcN3V2@c!X7I-HvX$Q9Rs?|gL)+q|hYPPwp}R^#2(J79GJmw5 z^qTuW@oV3V16&GtDLlX>VDsd|FFbwIpZxo~>+k-&bTaN%0mDW>N@mvz!+QZfa0PpM zN@cYgB1SFx$G^zK-}=J9R9+DlaPIhXf$C34rZWuHH}w z7j9sn001BWNklmf(Ca@914L?duUUYdu5{RA+34mMN(Bisi0>BGc zvzVckgYf}&PHEhN1fW^i0kRX{Z(3{&Zj%f=6A6Gif9V8(^wTiN3vrVG1o8+}w(pbx z5VROLn4v5>DOib5y77pgV36nwR$d4?KrHW}jfVzvE( z^Rj?9Hf-fzQq($~cyx`uxVaF2o1#d?JB|&cXGcQ#B*z?UzId12y%ZMmn9#%9pY z3&%TV%6;_)MxVwG6#Y3lO9S7{Q}u0Z5g^@VuV3&v6_?4+6Dh&UsFd8qJcC#HET}(k z^uY9Yn~()J`#$+_!+)8N;ENnC28*#yNVLZd9<@;*Vkxg#jk8kUX6+F%u<38mSDN%W z={818139M3SV`;;yk!?3q=@6>OZyoGlyRXN4GCU86IlE5@z^HxyvsY>#M8*g!_|Gj zzv(}vT37Ay@&DrH$9~gSKJe1t{&jt2f{DZQsAX_H$poN*J!% zmrn(bMx4e%9tpVl#8qt8X$quc)JzGe?*qVK^!#$b@_vB&R)AD~V|2*zyiJ&rzZ;-g zgyj1K?D%q&Y8yMhOZdxJGs*>dBzF#`f0Fw=yfaRsW9dImv~X<8Q6ZykA^qa zzr|_oMx3j*X8N!V_577}sn!ZNgyK2uSi;5Uxo_1+u0Ol}?rfeWl{W|*U79&_x>b)Z z1%BWB7I@e{8{bw1Jc(rWB7FmJImnWYRMws4!Scoo)wn2LJPdfd;dup23@LUPJbq~B zu=EZ|oEvF}<~)i+USV3NFuB zCI+W*Rm$o|8QZkR&HZ(xG+i`J$!-9QbWt>U?+U!ArW@a`4AoNWF0H=oT0_gZhi$f*=F#6~u z0GvSHN&*li5{nXmna427fzp#oh~;>+eL|6p?pW!nICh1AS$yOo$U4g{KFeHYPQN zYN4MEQS>hE1X3G#%I*^>MHLfl zqdZ4;9LKU6*G)g39~-NhfJ5`==EVGMY|!#a&SU+V<7yQ9svJQKQJbxe?n;9}z;jj( zcw+(fykRhkWo*+bCRJKykSlmQu@>>c0nRj2RxYu|@_eowjTKN<7*97|bD%BKSNJ}A}C0HR#+AMvE|@qn^10Q)`w zbvHoyF*dn~2kJLLHg^#;JL|v{i1R?3+nRvoaKk466@O|L*Cqkr#6u(*&0m8vOaPci zs~SoGc-6{0a4rE5v~DQ@Pz#M2*G&Md9CM2#NVf|UfHKbFZITtjUE3~`0Jz1>j3wC| zzeWf0-4lXG_17+NoXj=}fbb7z&%&ST9Lfa1;3oNa1H~m@G>&c9&4aRU_DM^_oGY}f ze(dIy+I%o1rgpY}i#LcpD)~}Lq1v3ObHWa0=23=I_Dicf6&uT(&U{La{i2~~W2PC; zak+lckrl zv#Ba;^3qTC$AtVsQ`ZAzmp|n3>>m5^mj8XNckGMQQ^Ii3bOYdyJDxhf`)j`8o=^Se zzpJG?z-55fp#$6|SU>)cKKb;Q|LhO%PaplvbWFQi3G_%n!s!RDV7ncrV_GcQ7X;eJ z0Nkenk&w!J1X6uVfZ?dU#lIBq1!PhxOoS5vpad(!zj_;dL?cW9QaDBxO7)!UNMmm? z0hnYpLEGYaCK3Ray^xF&06C9vf`o-*GpBwGCvO!+fWwX}$;{GEYFWKW8$hgEnE;f2 zOA>%W7k<2*>xPf{6{Y_^*yS$dspr?CUl6=zv)UJOr7@|w)tS^Kds z*d!S$?bPBcI#9`|OX@nl4c3#A%f19o>D$(%nc1wVJ4(-HHm)@;Xf=T3=jjBuK=M1+@`n8Rt8Ime{JdeI6L-JD67=mvSghyFQ!80#qE&;3xU zGf)1NOdpz?K}Q)oY;c;d4Rz62{MS#Zms|eF_Wy2e=%B z(|&+khwZgD-f{D--~OGO_y4PJOvgaEl||m(0$_V~jGNbvX$Nk)jeoimVESZWd0?R4 z5s;=a?j?aJ3*I#4zG$LH1DZh5J{q7m49ej6yh}<r z2>|O}K$HM9NmJ=l697Nn;R<}gB>*;&vvwwu6Ts_F#QY#=5Z>!3}1?7c?{y3 zC8g$rMPXgCKdHa0Qy88quCcQlXHHdLx3-mhUq?64gN+A`K7;__3lGyY*yaXS9(mUa zzXyf<-1(fr%Qjc}6Z7siTlsDC+59H`0G_tp^b21oT)G#Tc(aUx3i}mr_(VmP)mNnf z$U6}l`i6FuD^2Ia4JG(-m8Rg6ZNl>AXcw7|0G&a7dVw7mBoVee|SCn7@nPPCp(z%+H=nLBnM(F$=bYq-{{)mqL|?( zu|e*~AWdy{e75}z?VR^GPdBj5>+v~%b#XKyWgr~^yQ{zHd(R&D>OcIzLtlE<2!4QD z2DY06JOgm^ZU6Jv-F(+y|8Au1ovRg)GO#)x9C3Qt#ZV*@4-11?e4X#8-oNp2U8-RH{^JNxvmpmFdbI@NU0GyCS&?EqP`V%Dpjn0=Q z03$P(O#qrjwvzy;q~}5jfR%6TX!hT3TLLi0la;k8`p58r{ia;50~J1$qiH1Cd%@^7HkG0CE2 zjqJ^ylR2Of-hM{Y#e!toLG+bgm>=@v#tsbrp)F7PCi{|kHR+|9;|!$nWqp938;8U3 z?N4gDJx|S&#-v1xSdV4jDp&n)=IcNj?l`bl{!_yA((@xA9i5z>-}$?K|2?n#6F=Bt z^Z>UAGPWMz_QU#pfA7;zfBD;gWPkqnrwxUFYInWH1v{;P?db_lpFWxbFaTgG!L(pGYw4Q~C)zMQ zzE_AZN&rl7j}m|er&u88z%HaRAv5PWxm8JblmJxSTrvSD?M=e}p-2G8K4udD`#tW0 zYe@oNba3klz{Jmz1c3YN1i;vJbajLHcDhuy4>VKkz$So}SJjvBMfSkv#5%Re*_NGN z=&jajgDWaZ-3C<0Z<{^_ca_Wdi{q7jqwuZlqu}7@eAP?$jDGCTIxMoqgl6t|eyn~g ze)Twl^(Pt|`Mo?AJlb|1L#{OOLYp8;b}Mi7pXf7o?4!mHK<6TRZg>prvEs4*Y3W(~ ztN`oJ#ABrkG&Zs*PB1>2Shum6J(4DNcYIRm+U6mRe#zh5IosdP5ywx)OY*S~OZ|;} z6k5EwWN}+v*?7tY9p^jYOFWI9n1>{xj!9-qCyF=a zL=;b*e^5W>r}2NQzaTg@IUb#NF{{$eGH!&$KA}~*&m)2;H&!s<@LHo3?S!>b-IpCdC_0OQ16txptHXlI;R)DZ6`+}0EoG@I=fr~KyaH9fLchX#Xhz3W(h^bfYOs4TN8J> zqF%;=^+EQhY{&6M&mdd5TyYio9KA5or71Muj!!FpYGq^jBKxuYHid|aRjJjzlL?a^ zEl;d#C12xQ}fzvs6;WN@Ii4i(uKoo z?A7tsD8bB=m0j@9aaG0tIlqtMJo}>qiYrzpQG8G_L;xji~#)UWtMXqKRH2^e`zcpVY_Z+Z&x5b z4Gxyq%2fwSex|-)!t_pn zv8_BBP`?Y1rmqJ~cLl&j@$%e$ZEjXs)V?mbpac?aR!wkg7RBWHI<~|DJ_}}%s!a)X zRfrM*nZNJ}t>$F^)zt8Al} z(6J0*9~;N4uFWPxnLk%F6dO?+f2=FQ=TJ}#X#5JscHniZ>_%>Q8G@yVlYSxMRdg;a z+OpRf^Ub_7SY5l=8pO963&d4LWlN20w%tB2456c$cy9YPg@-xlCjUnE2xsKy>YVFS zV$TR}9%+J_`D~op6Kecf`GAeDXk9N6vbksZbwI#0Pw;XyHta?A`kL{H5)<79nc-97 z!Zc2wX1g^)#8eMkz{05S)ZxngZJY&dF^R|zgd%6KD6f)ZYMg$<=RI0oNPol zlM;Hq1GM@oXLuc99YpzOcU?%_FSqs*fy3wD2cUB_@%RAwxs}z^F38O}6{y6W3L{4B z(+)TDH4LBk!%%aWi<>Ern*ifu0Qurcq3rA zb6_}EgL+O5FF;08qi8~VQU#m@Q7xBl?6gnh3raw(_r;|*I70|R*?CyRF6bNlj&PVXl z)JQiOlUz+j5Yd5{6TzFZp@O3!NpoL*JDvsID2^(NP% z*QRRgV+>;5u)lL`k!vQ)Nqq7g%-Ou+yNS`ougK0K8%CWU@RcyRJNx7-DEfyf(PXaMpVehnL!VxnNsy|4Y22Oj#8^+F*Ja2p{=7Y^{Dg!4!K`fomc`(ONleD;BtCjjXr)$d`JO@Qfm zD2h)%comyXa!zcoyw|S*r0H{k?wtXp4R&0rLEC*b0Q8OkiYjig9LKl8QDjsGY%m>k6hDIjz^``J}k};PI-Hrqx(pSW%rPn060vGGS>bFTmN`esBzQtJ}CErjA zj>zWAn8^NI!f1K4FGCdlD{T>DTya4%&W<$#&)E^Q&6U<*DK`KIjO*@JfDdZKn>Q_&1lXsa!(IuHu{fD;_h;s5ciefm75gtP(j^Iq`Q^LxMk_uTu*ultvw>JIRX!%`nOz%vZnYybAnvv+*w zpItxx@P7&9v`RoaGT+EdFdG6x`+mK`>62HmHwhXN-wiPS1-%b|kH_BwiYa1p;cd^2 zM9eX*FzUA(WWWX{6~tfzSJI0?uz*(_eB}wZm;mI`mKuC&VUUTQ!KL^%>ggt!5ky6v zN+?Q$N;1N`G&s?j;jNnhh;fS(09xpkJ|lVNKqbq|696GM%VYReaz}b_y!Gtf9vfQ9 zR@Gee=B4sfbi;Bh8X_5Wwr!51;s``PqQ~~eMSkrs?z$=Gt59FOYyj@sV)t813#MH2w@d0r~lq69$o>*%JzPpe>*v;xZ#fG8p9@e;}26dlEPluhz#Zb6(| zy1_8VpXAf@D{Ur>xx9o6^4|O|U7=1_L(~OVl`1cab(T~nAEz;)tFHHbu$IK=GdhslM>fCSw0L{m2 z!b0@Iq98bbnFL@CM~H`Ho|{`G!xCMDP{xVpb3;AO%#%vMn`%ec%aVdX@zu~RWTIP@ zla{yQDJ=5X~;9IHznmL;!ljIqgT1CFN0QJQkWVQo5@lQ4ZO9m}|-l`%fQpKE)4(Im{= zG=`eZ|5IDez%H$EGv65WQ7=lGtNtT@t78D`gw-9u_R8n}wX+9)*B^c0q1Rl)0iHE5 z<0=REaKhO;|I(M;eCJ>N-n_f^qLkpi3SjRDSdBqHtrGJ32sfTQ86FD&CJ#>ZXh51a z0#>Pf{%?9QU_t`SodYF;6$c+835*3LAE3d3Z!)zS^s)$Mj^$MQ1JHqr7So1)8+qpw z+mXhi1i;|5AOm#3b6CW*th@emNvPu^;J01)`EG@29(uC&6M*Fl^|Lzx(E3&hKsPzK zAl)eGRlcZ}-pR?f&)xq4oJs9 z$_cn~_ao=`e9brB^YTCXk77+7;KK#CVBQD#P{8)-H{5;pj_>)__51(XA41w6uf`c$ z9qG-0a;L!N`U!4cKXO+L_?G_ib%2xrz9XQ1Dv;Vs8&Cped0fCOdgX@&7jVq#=vSxO z*Dd&^Q3b8uaW4G$^NvTm=vWjNO#o7RtTTW~K3EkjNotfl0=L{)5jtt;kUoHxFK(-F zTM~eQ`RO-cSH$7@x{>ilX{d|g&^sZcee6O({CGzH&WkyS63yP6hhxaT3d|KKk&Q<7 zr8d{10+s32-@@)Mo7CE|OAG3Trc87^edb|Kk3C$odS>V(p^?sdHWK+K7g#-cY_Znc ztegd-e|1}JM1Lu*X!D@ke@3Uu-Y#OFm(kBsz6$;&F*V{biWO3xnlH3{6wTQJFR+7N zJn3y%NE!=Z!X3wW|3#o_jp+S#Nz5=3Ws)yqAI*Fj)ZI8^`=On?innvl9v4{3bFcqw zj1oNb+Akc7X7Me*Q-KvIeNz?RowcYSs$IG|5^wx>u{y#-Jk?JAF@z}(%*e=CuWn$o zUn3<<|M-F=#x?*ct+t!zz3MNV-~ZY_dARET?1jr`w+Hx;!ur?#?r*sH_V50#-OYEs zI+ZtV26qHZ{}RrhzJl|ctI4UyNHO~SuxKT~Jpw@Q3{V>d(@uFB{| zBp0Nbe(jKU1*^rM34)PjJ3W{fZFsI-70*PgS=6HS9~VmyFOmR+i$$*xvwX7tY~ONP zE*dSpiuNK20O{zK6M(rzmdAGNXO@N;3kX}dU;@x4QLRz&^N|zn>8=e1pD0)x50cz* z(3BKe1QjbKP5ih@Zu)qA3gb&a-kW6FBsaOlD~orPfIT~bog8zh;)Zfqn^2*hlcDIG zVxZDXIqCh=7Yft4N&nMfa>tYGmglzJ+QPubBwtgcj6$~a!+j0EWRreQl#hGfye}C# zMp0ap**lTEJ)+U)G*4tF6!ePi47N7$)AH#Y$N8R}t0sX)i$nRVEE1scTbuAQ`gw4= zF9k&t$TQ6~44(G4(QHGrwNn+FTG=K!oQkC`YJ1_$+{TRDA0z2eU*r>6gS&iYCHJrw?%bQ9}*2Bbm8QX|w_BV)MQzX#F@kah|A z>L>iOv-`g058n5(ulofd?E#)eu+aSu@N9vbZ~HS}d-k3m`u4oP{_E;3IKcF&jnPr< zW&kj51e~oVM0sd00j|FI&ldXScjDi`bQeIXQMs zmtcwW2Dd|{aE*QAMTu8U(Zmac3AVkWSGHyMCoDpFL5w^)+@JvQW^l4Ra$(G#$L@wi z#CtT~%s*OrUDpLkteoEqCICwlgUVB;enJ;KA61-0ZAm1SSjKn5gPJ?VM`tc&am+pt z$qY1dhXnx1DEmY@v_I?|13>3?@FlcVlHbe&*_DJ6D>31_C_h%_#)kvGEv>LP0EXvC z_puy3*`gva^cjhy6EA>9#}0qzs|ONnoTL~#hhxr%lpovkOK~=KP4p~Y6&E7fy+SeK zP0@<@w)DCM6|~;v#zHT~jOX3DL9+4~6%*$-Mp}JXSub)v8k1O-VBJu78Gkfyjhyf_ z=3-fcC+Yaw#Dt#z&e);mC3nw9>r~ITbiPFsidM&N-Yl|6u58dJOo;gLhZ5V$26&?@ zHa4pZe=b+~h7H5*>I~=mQy^iiFU-Y8V0bT?yWp4rJpb3$_x$!he)p&Si60CkJHWFH zmMn({c$UHTsh__0?4959ZR^K>>YLJPe^g@0>e#=9)BRon$Sa&Zc?IjW`FJV-q+|@C z|7bw{TgOa)SE~|W$_iqB7s#kr&zgnI{w*8@$wIV=;HxH!4~@0UqTF`pg$RbW^qeG< zaEqUpi*7M$=vnrA34o+4@Vn_ZW0ocW(ZYON69CI#2j(_co~Z<2aY1Gh0Ec!#66tJ@ z5`df`-~Mg+1@X~M?1SV^`9L$y@aODC%&U`)X;?af|E53j?%3+kZ2VNcXpdu5MbgrN z=@0HX9=QC~Zsa-_41Tw+C{4 z>ezcEPj3zy8E8ET3#aU5TI5gE-o)jOofLnX+xT?05b))I{7|j6j#eioT&S?Lv0LbD z&ZZe>!4u8Nw(Eb~oPrc~}7}`dUD< z88H6CeubM)Ucq*|LZ;QN##dY5C%}C@03!|9K7K=BxIHgb+;$_tFRJD-fpWLNcsxPU zfi(iPD2(qvxJUxvk_{NXXrbanrj8NGQnvaqJT!?5%Q3gmss23~Rf4Dz04p>4V=kQl zSh!FEfO);#)&zj&Xh8xHjp-!-R(D<6b`CO;4ikWYH#(MMtX&llS0!8?!#b`1#k@ot zD9XOA4K_3aKef;lZHB*#CgM&OfhUSRi#A+>xtuH7t$jFth(D_jcP!+Hpef+ycn*Bl z(l$$z>Ey@s^f_Bp3fpKla#WnM<4Fd^m$K>E0$9c68QGbBqd$r<>`Rs(%Xj^4c$Ib) z_-FwYj}=}SyA6Y3zn(r=JqyRq(jxnqEBuQr83BJi@Ty|_{v~kv;0Zf$~hyGT8mtN%=C3*Y|t?05=a<2 zPn!=D%R~DPYny@^=I$>k^d#{Z@e(&(OLItcqV6bV7qgN@;9$=^NFk2Zu!~a;L!k> zpkR-Y2AnWo4H(ZEjAI*6Uq;v@9?1vnY489xo3Vl1_UkP$bUZ8q{Mp3$zukmEC2fxP z>9_2s`kQ0R%B0nR-!gP*0$_1jVv4VTe~E|3hY5gRV4Fh0;O!RyErIk5)R+nFjM|KD~qKa`2^1|%|S1YxEugxoWiMeDS6LLzerx0 zmo@&7Cvnueh;~f#B0unX!!-AC3Nhzd?onVa{0qO^bdK|UikyZQ{?_8YJnLWI>W}Gs z2Ga4B>zn6&;s1Vq@0a|!2Oj!Q4sZ8=j>AP#ngcusVDrSoFFb$u_x}&;$A9YgW4F7K zPDaN)8l1a+t8;XCq-kRy=Y;d8uVQ^Rel-9kI~s6Ln+E2&{{*-@06=#Ptmx5z1Wb1T zq)KAMT;=eKLRl@0puAw(Hq&1Q;UH01l9UX3NxZgNRJShy;01>h0BVy90r4(r+Qm;T zh@7}FT}A<##Fx0x534^^l(MUI+7 zAjV_ps14&L_F7)W%M8O99n90*rp+vnt}P_i56SFgGo6&I1)rpE2~aGyG0ie1 zcK;2Kt^(K|t+#i5-gmF>`|>}1|Hpsj<2b-a1YBkbIKXoNHXrzZUV8S9@B7xxQ*ZcM z?DA@L0szBpJ*xyDsjmhMo}Uu1e)=lTZysS!3%ZSu_E!I7{sQ#H0BSFv87yOw(Y!OD zo-?#j>8GhtB>)AL3+!eLpaV7sWVrwkG>KQL?RD{u7L|xT(~dwbvbOzpCIGzX*(N39 zhcy8p`auF96B#jAO|pEc1ihppFtvQsBCa_6XWcg8ygP#J23rn zb{?xmbpEqI4^2|!`$)L}uOr8!m|u&JWn}*%e99KOdgPcR=B8KJ_6kC;0N=Jq=i~#6 zuEGcY;dEf>2C>1$a>wu3RyV23tOoP4B*C}5b!Fu_XZ{txb9PkmmQNyz*r@bk@kF-h z1Qhc3eB1I&KJ7P3&8G$Ol+yR zQEglJC-qS=rO_w+c?m&03jJ5;LCr&>1EvM)Yx{-v8x08LGO~dA5wY5xjBJX>GL95< zkb3UOlQ6yPFj!k0uRy)_F}xEYV-NbAAJ7*4drWWkUjb=bj?;d7{5d~({(`UkllOnZ z?|he#_yEsgm~)>4JjY@EzQ6zJXYcs_KfS&Frq`wfU=OU0f%2Vbz9XPKM^t`quAkuS z#xeFexgczVsC~X)ZVFKQFy7xO;Lia$4__6qRO&AZ<~i#EqYhJvP8kf$CRR;bfw^Q# z;AsV{X>@k1b7tOQUZX!EToe#L1KP{p2K6BPe3Hc zGvhenRmruyvT0;fiL)voqpSLbuO^pLe5&yzqq=$;CTGNZ_f86yE22w&SLWVt)M(sH zx@j=_uCzesX-xM@a1^H^9gr@YJ1_(;=^GL5j?B#Kh42P)Exkjb7e`$;)lM<28_UcX zYd5x=a6gZ8L428w$B`~+Y*?V%^U!0JtWH4DFY@C>Ysr!}j`4+up3;ebfwe!QS(@!GY7bso`l-I_V_4lMO&LC&!V z0=LpPB;Mk%JqMH-d+c$ZPO;7FL6(Fu7YE$S!ZU_qKLKe6q$@x=2XI!k<>-ad|&L2^M3( z*sYIoe*FZSO%et8)UW=Z%;y9#{hb~O7+Vtj9RlT?yZUc_iwNEZ>NK5>7PTI5Q64RD z>e#auCmsrS)UFmV%d4ej`gfiCA_;&cJiCy4T(cVpJQtTp005DQEzw_)h|MJcO(Gg4 z(wy{y@XpznvK!_dK_7#Y6UOLHkggqa(v{}raS3st<3^M~kCyjP_cDo^PY7A2Mi&C- zBK{(~U>bp+G@CL)mc|wl>(%xM<_5iDBdKp04Cw zOqd$jM;x2VpP8p#JXNxi4Z9fD@d;|B5+0Z2QSjU7yvH}kpvaFbbo^(@97Y>H z5#NmocH)V=!=~el4ete?T4$EV^r&AiM)7YRx5Nfq|8m!dH>&RDH1!GBSmBF}=xUCl zAO<@(xs}!8QMiKm+y<$sN&8>cx@>fHp8%|mhQ^xT zkFu4EfBTb{{p9+-uln!q{iJXFSAAv<@LY!|Tpi#e6wcoDJ-_Yzy+8aXc4zPYe82>T zJ&=xp)oKh5Wp~j29s}3;(^s)RpB@jW@oWs#^~%19)+?Z zXMX~KR`(WLZ}@ zoY*_u%zKouDx5_ZY*z{w$y1wpdWz;G!`p?GfzS2qi~UUbwZcRG)QwRVhwU5S49`Vl zBb%C&NA**_DsV?S<92Bej6?9!#P=YstA5?9sY)k`_f9`|zJptBe01e)6s=`%bWgZ? zJXw5}pPs*jcP&Krmq>QvA-tj<#QUCpdiJI^v|J2ld$GbO8LS*C=2%`9#Lv*bd%pue zX!=-KYP+i4?VJiZdOR}J^SLjJlvM;(B^MJ;XudiDWH-M&evdu2=?v#M2f%OiN4@Y? zUjRPl`zZry4`2u6J74-!>w8}FZTEc2ANePW$^ky|K!xuEd=$X>um0fYpa1F)fAjXK zw|sdz8t$R5pAby6OStmE1mp4Ue7GxMyY*&d7nM|yjBWgg07RR6Okl+6?g%hNXW48h zWF{tt2$&M;_hIuWUm5GnKUjRb$tFV`ljaiu+kb|K`2;|WR|~b^bv6MAWi;n+R|0UE zzL-fR@pycc@^1HvA6tLSm;SH!e$wyzDM9K0AHfi%4hQ%sf%W@d|BCZp{X2hr z^Zqw{4OY7=%~k*MSrGaMl=CB=Yge&8JHl=^onx537BC%i0bf2Z2y-r#bFOexpBOAj z$uP#R>n%e~K2ig3iaQdxq$wi618j$xWM}chJO^sQjS_;3Bmn5>;2B8(0=_N+OA^Fr z%-GMVX~}pj(34DxO(KeN9E>-|;YQG$9oTlj&)-FCQRz4ucTpTH7pNbN@7|Jaa z*dWG-n=;IonrA8~8d?^Apbs}U@MBr$;Z;th*X;gi9$-$+&1-Kyqdp9C{v+Z|B^Sk` zxmaY{=I-Rl>C=2c#cZ+;FAF4GfqgV| zq5E~^&uHhhBJcQV$M#*{AjY!aDtnVW^ZW*OH9Ln2x9Sk-mXCXJooQvEz6eL?+d1wl z`?t8I4wdaWoCZNO4m?Kiu6X9tDMTP>Q9l#6bI!69pED!!gb{UA6>Bd;7`2x{JnqszgoZlr(T!x z`8`OO3jU1x89|@~mnpE=zIm5$^T|7~+a>Jxox~KtRO*jM1?QMSHFpcZw^d%qjCrG> z`PF0y>WH8&5UC!!I}6m{C5z~S{w~4qCIHC`5fnPzca`Fkhe*gt&gZeD)NnPf*p+Q$L+9q+#qODy z63%aLE$jJez}tmlc(0#@p02`maGYdy%f0;_%taJB8azwSh5TpVF6>o~W1I?4SBR;z zQxn^Fn))dghAp*>9X2>ir^tCf3||dfZ!QZf-d|qd_r-ty z{+E8$BZ}h#eDuKFl6!!UGT1)#Z|+^c=kNT1^Y{P1e_!>%tFZL>&iCG#j`H~p!SQ(O^Kx}8kmj}Y-tjz=&QtZ z2~VZ(Qr#$;GYP5v9ThE-r|A4#^4=S>6x6&#dNlm>^sH<}#R_^CELW50R)&us=Oyi09M`JM7}#ZL*1(U@rd=i-}VM&k(Miqe0lPe>~8 zAF1!&Uj*lvkBhF8z5Y=w?X6vNcrFqvqqe0ntg)Zm>T_m%eHTpWza)MrKA2v?9R*1d zgVbF$-8HL$e6hKR__4GS;CIeMKN$x)V*tO|i|kS-i!0}hj6HU%HP-nId+f)UHs#+) z7;o=4xA~_L-4;k!fpiX}JAk~u>#@!Azwpm*?)$>;x&NWpJT;@01ALS~n9CgCqZ_u@ zf8pxpk-zu5*N^?vZ`z$d^4TcCWo7;XbwdCt>AM1eaZyOv+_-|x*$TUTY<6-pp!^;; z1CW4Jp79?$rZI3fcLtn8;(EFlc({+9Sgge&58hAyuGq5fwIvF1ghqz$grp1Y6^+16m%GB7>9FgEb>6Z}qqIr*2YTWa3sV%uD7g=kkcw=~{& zK;Ch;1a}!L+V!~X@kVpHBo=pN%Wa=tjIwRY+4Er2t;uA~at3nB$k<_%&auJy^au?w zJ?=XGEqCPt2`d0N8t~Ped(6}Kc+t;p?*4+mw7Kgy|Mdr6^4rcM=@0PH3A6do0S>T! z^oKrY{pde<-TJAY{c3E^?@q^4ph*e!rGRAvUzl{eZPT<1N&!5X zAF1}CLLP;W$&^?2>VNI;ug*7*4VY)E+l4K~&vKRlW?|slXx;i_@{4BS>nv9FMRvTf z$xKM_Y5Of)tv@i;dg zXmzV}&J3$0IkGYCqi`8Ii(W9uV|#W9(I;R<;_A)$Mf`6Gk32roaUc`WS%2o@X~cJr zKZj8Cv8OH?ua%8nAU=2bz0CSJkVula!(GFp*aexCbY@hzQ=WibSv9Dgg|Hogq zyYZ|4ehofHsGkvRg3=fOOYxdu+lGF-^CN6dPq5jnYOv-YZLiQ*{KkCUc_7>zaE}VO zjRG^qCUWB{tDp5x)7lv=o)*TLTr_FANdI2qIhPEZ z1OTuLvY-Lc-s53O5|d1~C=q1U&LyL&f20Q`Q-|-A(7wAv(N*K;=5$G-Sdu5^A+q&| zW)BbZ(9wVRevckLKCf(-?cjpG+~wy2^WEjK*QdZ7&o%LV^8f%K07*naR2NC~F~!-C zFJ<2mUDdvXx5&SVwtHuSvXdSUJ=}c0XZ!Yd4_`#L$8VtP+4lpOakKt2_Al$(rD17I zF2FusHr1l_rH9Xq3pd6u%l`%ar{RtGn;JmQwt)817^QqQ;(Ko1me^#2KljfwZiTANMz5ywEnh&3`(dT)vI%Kw^LL&`)pfdiD2i z@A*xC>;9K~>5V|@100~jTxdSP0VJ#+{Xair{n(GaZu8_Dz9w(Z?yWnM7>uesCV(xF zPR7rXj3=kv&D6Bz?F#GbSFl;rdjlwty5isbS6cb1e|bi#9h+)@`zQe8akR=vRiKoj zFQw+CT8>v*SW5zwQd*Q}gU(Ik>0}Z`5c7PQ<6A0H((=S~E&-UMf0+cTS6E;ssU;m% z-AB4ylDH~f)x2I5$5Mz4v6rxpf4g_iHp$i{`6;o(>chqhA$LvsnQl*3h-0PG89lp# zgU2lW+k>86FD?F-jCm%`FY(vQ_%s$2qZgga&fIi2YkPQr*tTwm&o8tqd)NDFHJ;-B z%qws?Teyt9E`cbHEB{set60u{%YD@v$)M)9{+Hl*(%zVM%Z13=oP zYJcepkk$Zj^{MS$|K8u&-17y0<=&V5o}UjSJHP=X^p?W|9AFl<*M9!a&13)IcW&PQ z!6%TD7^S{i~~sZx%440r~0hArQ!~ALGJl*SnJ|s9xD(FbZM5i`5AI`!Go6*biNFz z{VuVv+oYt-Mft957)hdJq?54Ra-x3OgmNyCstlr$*A;c{d}?LBL}D8j6ieh@L;#mt z4Q2|6=-7=7a2B0*6YmZWi{`sB*y*=`*KR*E|8rjqnZ3&=;sFc$UBEu1N!*H!MG)Bz z%N{YPiV2mpL-Z58ID6~hX*O~$7{|ECn|*gmZs&w`wdjg_1ci^>nfKoOCo@*q?V=c_ z=dEwqzUwPyCWGOk++jOl!#+JL5}>+Y^BCnRk1) zUiYq$IUV$3XBr2p^U9_g$n}4lihIm&^|?QE|4Y8)G|<2S4$y_mE)@qjKm(f({KQK(@B4|b z*gpL;uif4Fl`lZvT>&sWHdP)I0ETao0_H`6BZ9H;cL8w)SA_4@N7#;!3+(ss2^WSw zw#{AE@-dg-UQ`B=I4>%=>74D; zysdnWc3~k%;w3-!MPC7b%#+=4iVElG+$|MDz45`EEx!G?myDPCM$w*2;^*k`+%ez5 zF@wB=VUG6F_K5F>X-K`T32=whAnm+|@12F|Sd%OspUupxRGGy>2b6&*~EYx2FOJc9ZeQ<}zSnDfRjfQ)_GVTU!gX^lO06AQ`w9etwY ziNi&%;W+JpbTancr6FJ50XW9+3BM~x>;08a`ibq8&wTy%uFrb?eJ}m0_X@2X-~bnb zTZ%CUI6ws3r+@bD?FWAHOEw?;*RS0^{pK&j{^tGIW7rHh9{vt5EWxlbaAbm{4Vv)n zDfuX$cSqRVIKg(a!hYx9(q9LQf(P)g0krMqH~~OT&H3r}tO++~NEV%UvQ+!Fn**hP zT2#EKytp1W34NMQs==I3|ZmHi~k&@88 z>b9g1vT|q3)JQi>JbQ_?I>&tic~pTH_EYvpvLVocEY1yHn+G}@TF7G=r{+-Uv-kK* zr7LJC`nBCAd1QByUM>Sw)l@`rv0-hsxk}c=nfap4(urOU$2fXdhi%O0`Dn8l6!ZZW z_;7E`5?iyf7F|SrE&oTGpDEzA5oXvJO>H-_DaSZ6j0oF;gxn|f`;B}dO{{^~WKHPbWtP}eiCKI(48=>{8 zvXi{Ny8K!`BA!^j9`EJ(f5iu{dj-sSA6K&L<}}B-Hl^ zrlSOG#!qUW09cI|oX*BESMGRXcl7E1aC_HhzJ7bxZ}`XezvRoV35gGIfJ?(|N?{Ig zfEac+-g3Ns;$OaM^Y~A_c6;rOUy;{OybLP|z!0+y!4RphW0hvUbes_RfrAQBD8 zg2*g%a`ORZ!E`!K@ggdImt@Qq1gzKY6>5Q==g`V&bu;M*wGgn0nk*izXjE8n!KLJO z$)ym$7ei@CEK-{2*|{}**y1iquvnigtLsnfvrd}%5|EN95=3&psQ5Fy6`V{u%F~=J z2>m-6w!c9d>7XJL2_zRfs<2_^rHcgTmiR-aMs>wi+JT_&kXyME=@^PPqY<4SyFN1rJ>#4Cl!fISVTgCilZkF;>+I>~7`wPB`J=dX?zbtWN?6FH5 z?9v8XtWmaQ>ht&|2H3wv?iEljT9v#or95l`tT24edI#VNkTyWRdjGHNPJZL-x6l9V zAKg9gjNC%w!`|@UjOOa5Bz_x-F)!pzG8p&-p|D<4|hXl z4DSv|m>wH2308f7zz8k?C*Rlp*qa-O;G+;7&y68?L9nRAXz)Ng@Y%ny^6JV!JwkUuHi$wa=R z%O)9(Y%=0k&3`8ut~BBCNIs}f(=PawMH$g`cHhAzd5A)WrIA}1<$Ogv_kP>4%-4(^ zGDGbDvsVoxUZQzhVjna5oW-yBhHxO6$>KTO210C zOKg#$H%Eq_u1{j>?VX4!%@D$CrT6YBf@Q%Bg@kkgpCCu1bjL*gC9-Od9}kHTkNpGKJ9!bGY4Gor*hu5j~B8kOBvH006782$ou1metLJuXS{y< z{LlW;dq44a|6)hN103MC!d&P&zyY37*gWy@$8F#D<6pjg@@Kwcd;P6ngtWZ_Fu@cU zg6N3}yyj~IP2f$Kc2{kCS>Q|n->;9bJ3q#5dxZUV*cea?aPyME1lR&!73u1zyq`c8 z^-l?17!@6g=T6evfT7#S z0>gKLPg+&GNanr0Le3xSu8~>EBXriA&qnvePpGDdU&E^#)03aTUB&Iq>5{}9Xmvp6 z8+y@vnDf@}LVhVOu(CPkguWc`63$(RXU8JhZnMG6^0KXi`U<-0R`oEZ^yKcuf{IJY zWB737gL#wdKokpmKFjho_MiP+bDT8e*>*1!F9lDoc`mMY{JipILD5v_>O94(9oN@k z{-K}@6m~HiH<;;Oth2{ulfVp(_{yr2od@iZ)9_yY{c0@q&71j^A2wI_cbG0#*>+&K zkd-EW(+0z1uSW^kW?*}Aw!in~|8#rhGhe@b{%8HzeINgoj|B=kzyY37m~-_59NWD zDIjb}oo2o(9{XH>wRv6ymdoG;qT>bYo&vUG-dJM9L!rOJK5em2TkNoPg}!4kIyP%s z)oB9iZT-OTsq685R#*e+4h*;T?}2nR0oRf9oiBXL?)a1c`R?xD_&)+@q?_RyT@wQj(HjjKZ&UYv21VgMZj|hyr zv1xp-0LIS_))+o67Jip{coxf^2Q4i1oEeihE9|#N*l$+I`xSCdKz*!W=&X{kk~#3Y zJ7o$c!%6ylvh5v+at(V0-QshD3=gU(aS~9L#fzv+HX@$0k+;{!=+g17k_?el*XD~v zKE?%q%Y+Yh637Bp*j1(1(_wSB@r3=LToAb5F%$2hxJJ%f+tq9{aiTb;tUtkU& z&@AoL3qVFj2GVdPe=79Hzt}Y+nkh-tma2LynOo37Qo5TW`FeJ zH|+0t`NO-bpYrhT&X+%Y-$P&i0WI|b4)EcF%cda*IKYP-w%6Wx=kCcj{HEQLZ+zAE zsb6~4?(|*18OQ6pu>sN*44+N0UlS+^EPZ7FfPXg2ctN5?j~_RveZ;C_le+!(2zh;k z{ceRk-bH{sZWtgOTlKGN{E z{apW*TMIoN$2$8*ntqhe?l~PKOhtUca5cYNVA=rbDu6YHPgf@l8%oDO+D!!fooWt<9*$m(XBAIiP%mJpY;NoZ1Xz^(`O#tcBZB5ds__ugOQB)HhrBkx8F278Zae?Rw{9KqT zoC=yZWgQ@9uc$=kI^fZ>6^d9^e4aewcHq z103Kv1)KN%lTY3~{f1Xc>#gFA{pMac@_vKE3vU(?<4CLYOetkR*ws#7QzlU|c{N^th z*z9B`l&C~F5TnVnTn1!$Bs>=_7%dKw+|3pMz4iqE{cN7o9=|GCR{Sq1`g%BR(i4rP zB(W*FxL;3B9 z6Vb6vCgxpeQ%XTJ$LcuYLN5^pP(&gq(QT*lv3+*>-9$16M~Hk98z8J5iD91p_Q zn$t^c=%TT7&Zf=z?s@>!lVM!x`Th7et)_LO#ztW){w{;;m*2z_IkeSH`|*56Mn2k% z`OR3gXVi036U@(Dk^yX#Z_`R=paXNEdMi5?6xk&PE7xX3L zxcFfloLH3&9@`e_qn9N168I=_R&$;M;*493zX)G*{PcM5jg9ck@e&n|bNHhIX9>Sc z@mjd->UUYbBim&>QDLR}MdRl1DV$5^DUwO?HaBL@7ozb6ZpEg1GB1T5-%Dlb`ItJs zXdBznlDSqq%<1~tp3WUzBu*6(tsCg3&?>tmf`U-$COJ?ck_^2(6eU?}{r zqP~&89Aksw(b1EW8+&~0Tk;(r_on^Hi{HFIdGVX}S6}?*{gsb>`+X07;o<830X`z( zvMJC34)C0Y-Rav?K7H3qcQ=0dmHX3ozG8p+-dFC=9(~3B?0v7utF!x32Fkl+>V`lm z{!cJ{;$?jAK>3}IF%|ae&Hzea?D^FE7+K%19v z4kHKTb_2k@5255u1;=juZT(|6{x)}IVl(Dn0C0}s>iyQx-w%CUd&!M5knecjlR1Cv zoA#>*-<+?$_)YseUi{|$)fc}hpM1=_@BO&natUo8-~i8EIBWnM-~a^MCmw#${@Pn# zu|NHlSME>W^NRiYkyqxkCq5x>uRVygyE1&<1;awK!mulSg5hN`N0^Fr`$ED!h6Qcx zmrl%iV0i8<1Gr)sDGTKW!)`T0Sv2XHwvx1@a=TlhVRqH)#scWv+!8p_kS{J87kzh& zUXcUs=7Kpjp-z-2WP?T(_LL-L>gW1ah7GTn~H;i-znx&CvjdW3Tce^F7_ zqos6x(Q(mv#wYlck6c~E?mM_c9Yj9Tlcguu65dt+rFJ&QOOKx7QHGN#+;%dPU3 z&yw?)3nDc~D7VyucwGUw3E+7nPlf-&GcXkSC1<({6a9etU9Q(ya-gxf$6c+(!pE5tAoseGziK@DBl+Y1YRjQXA_ z(wjUw=mS2_zcIDB&4}{1*LSX{wlen;_X4{?ziQKj$2uQKL?@7!u)>2J$MTgnTg(s{ZmXpDa{nXw`2I)p z(F2d>D-S$+*T?-w9}J{FzyS_mAPN%)IKTmJ0d`OS{Jr`7u?O?{uRWO09)B>eKk#7Q zeegwj{ltUVJoR8sPd$j!vwN{RzX#adS#Dda$yHe_O9D_9=d!S7bN&j3&(au3rPx2g zu*d>a(O&LUC}YYCZ;mnSxL*bG`GC8;2O+0%+-h9-OW|(6vQUzzlBlFHfh$kY*^P@8 z>aKsfW5fgajRuA&nJX}U!Y60=X9QboC-1;8?Uko-yeo`$lP=PJl>5k>&l64nNE;xZ z7`=?V1E`y8Ro2Jh8(o)pKt)X+|0|o58K|3;HNh`5^QdEUmr*YK??(GQ_U%={Ty*pNCSz+w zKjU#8i-j7`=qh7V2a5u-bSN8$Ri;u96*fyDa%^zg*wUd_`7M2@sFp?TcJvpsd5Tf? zydUS*=npV`$@eT>YXkOfs6To|j2Hri+?ZJY7$Xj^Vmqu|_PdTt!19~Zm5!rS7s#r+wkLc7d$0lVfT z^A83W*=EIWFA^M?OpMUil_Ro6~t9u^Hxc8BK<^D&py8qF9r55#%l-y*YC>PYxiJx?e4t2aW{6?@6OvB_h5J9ZtPEow*Ad}u)leC zPB-ty_U1i#dv-VWXLnwglh?fIHD9qBfx{a*vODxTM$KvS=V>_^T)7 zHzs|ilc9i4c`S;KO+lQ-LV}aVf_qHZYq4MEWrJZ;XFteUugDbrjI!4|FG^u?Wu!fB z8WlcE0gBCt{|fwuR^izebwDlba!H7f%$UL>CG%y&#LWR;DFcw!V=-(tglaxMZg$z* zVr%ic$8iij|SNdKl}Fj^RexCl(*P)p>d93 z6YYH9Bb9=(=%wHv`&Qjr{glGYT%Aitqg{<{A7)eAyRLdL{pDsGh@Ef(`9bwf6w`RQA z#HyREHZJYQx!8}qkF;DdUhY6Ew$3(a@6;MM3(7`Ny=x4ZHmU97a2b=In7iI`8fCSc zY+KZrm&z68gu%u~|INw7u>IIR>Zsbq%HUW{=j%p#(SMmsjD3oY*xT7loJvNH9h@B$ zzBB&+_O2a=p%8{SKp&q@y-4qWF5IE2D$;?3*Iu9#o+6?!Lyqn; z@kr(5%^F1b-}B!Z{la-y0PBsr~n;^kfunj1PHeQAP1=ENjm9?}gn zFFYnZv643&-V|=pQ`(a7v2dp;2Nb?`^q9*}4kumyaM)C%5%4w%E6HCR`^9?7%m~ZP z31-p-{pUQ+P3q&>nQ8HDG6Gmc86}LuIKxB;(aWIN5MA`qEi)>__k71sk&{?mtU(cz zWs;=G($W-b4bPkDy@=nlI%uEVwoMS?BN; z)Kz(^3)tR)4%y$h_yq)TXSuOh7p(vQ010qNS#tmY3ljhU3ljkVnw%H_03ZNKL_t(| zoaMcDyk*B#=lfk%`=pyXC$*$jD+g!Ekuf$NTZ0MD%)Ei&ec;0kVFJ7%&JZ5sVcvW& zkNLb|fCmFSaQ)DJx_#3*XYXC>{ZV1p-uK?_R;wlR zqZsyluSy1D?a5UC4wbq{d` z)IGHJ-?##t|8{i`v{34vt6fo6uBa--{9iMi`Rp2lqM%l#b`C|Uoa3sk+gYBBSuQ8c z^#`m>rx+t_9nMg?ig8up)Uk7Ro})`=8NI0H>>Ca-C}4IV3RB1|iXh#&}s5u6x` z9XC^*wXr`I!9wV(gu~&tue=6okg^#I1w=uf0Qnol)?g~6KP@LuyzwJay!g+g|El-O z;6rK**(f|a|@vFmRKAh0r>FenNt=UACeD2$=7mbG%qwwYPZuB|dC3g!kw*2*cT z)>b&MbqAGmoLXIC{;D32zwQC%`oh2}g+O5h6Q1v!A`%*^sd<>tuED+o6npkIeMtQd zT{56<{b;BDX?9QSyAAiG0{|7$n;=7T$_)Sh&zae8{;A&Ft!JMCxz?3SW&k&SY^GZL z^>26McYS}g#2zhsV0?8Vl1Z?arl?oNkWsbMf;ec}8MJKBYp0YoluM4~|MMiXWskD1 z=@$iCM>9Nn<|I32W?3mGT(Pi?vui7qu4b-3%J{V>dimz%taG#gU4TVapNQXa`6|x+pWF(-_}dA+f}_#lLV<1FD=QC zf}#0ZFV50A-X5FBq>H4Mrb|F;UPijnB;_x*KcAn>Ke*QDJy04 z|E%%&=T5SFb{<4nnT~O)%=QPYloRIqLzX6EcF!$P7{l^(%;I>B!Wafc4{LyB<@0!y3tbo#r|vDD|Ra@90m z8oA5?c8Zzrf7UGg=$i)HUiZL;1l*vQ3t!?(dG?(zFHijQPn4$?Us+EJRD*gq2EiZ_ zK1L7|m_UN$CygMUAv7|HG|WSGO}I`qI~Kmx3C0LT;Xf;H>ak~j0b_*P)oh=cqh~E5 z!W9eKSRAi1KNvFT^;j*ZtW2lW>ew+mN3D*T-hgexStfPG_D6T%&h>E!l_OE2sud9u zCIB$jGIQ+>P20O|jqTMS6j`5J-}#uhtp7P@#B(Z$m>^WmN31~Ir(Se7*Qfu@$0kqy z+E-py2ACZGiEk-SzWM#r)8-0Sn}CP~V+_U^#2OIO8VqT5l8x;itXKE>WnH{LqbUqR zVTIyg5BGwCW6S5*GMeG+c!kP2DpxZur&Q`#E2o@ZSz>X#io>yWIK#Ls*)_kF!{^R0 zumytPD#z$!JDFBcD@-f@P$ORbYxr$M=)RTE?7DL~evkO3EBP7*B~}pd zl?KMt!~_O){yHNDT^?Y zxWIKrpi&rJbrW_pSdUpYaP^|Um*1zHpKr`* zdCO0JsWqSG@FR#wN_2;X#QE=(Jt@mm_vs+O$MbsOlLURddsX%Q$d8buT zRYcVjQBT-K`9Kl*Yp@&Iiwg^4xc;0Xw>KA_#6|d7>x#;t=E{C3Rfr;!m5<) zT$pDt>alCfJi}3+#d?jMTNYq!u=j4Gbf8F*{?aMEnXMFCwxD%rgUk958B?+WK@k*n z+B)V{O8{`>W1^|PWEM8lAaQUL*luGesu9(>Kl}INcwC9t?|kKaqC4fK)v=@e{$jnf4@A-6%lY4$s|8o{H&kJEOqT( z>_`ZiMJY(qpo)kCb^cf&tg5DCk;te9f7lN?&V}n5Ex`4{{PX5GSuU}E#|}m_eX7E- z!kE);g}M0|`on_9#wVc<=U8KGD$1hf9BZ7amnjAXqfwuUtJreymDG6iW(9g%_hR}z zqJWEPi0kP8w|eYVF8b%e#f!vmT)U<*p)Z~}lOfk|sh0|Mo`9mxYlHmeIEdEA z$3+}soS##X%FmNX1zKT>5-}yx^WR7f3Pu!bp!n(;WX7;qud=9XltsM=VzWQkPkX>GBY#e-0>PYHDXdbN_vd;U)`80G^psQ zpZ5{p9%Frbjja%=FhvcP^dUb?WeYCcy1C=;V5m;y6N$0nL zK#LBM7~$J9YA`i}rHR@7bJxx6_|hZs%GPY)^vrL5d%di?ecao4q#uGit-8A^U)sb^ z5*>7_WQLGn$_{Y&yKY)0+MlMnDW8ScU#q_*0|K zo!(`I9pS*%tTL~ygDq|it_Oie+RtCFkG1)^sb5H=@ldp0!cIPg8-E0y-i=mAsLsvf zDhm-~#JCEAV!Zj43~=Sb=)BBm9j;Nlho_-JKd;te_p)`@2_Cz@4(D`E7Cg{?39JzT{IQ>-;x~UbxqBN zHE5(fVDhY$oS@m?8xlW!C$80?A#)~Dw0weU@t1KcAEaI$;)8DZ@h}q1v5&q6X-NVI z4HGoH5$X?HX@K(dZ~m)#S@)>Z#@|nBM_My;$pn~a`?cYS z+_sB@nMLf(D=>544E<|-U19?9ZDN8p;mH_cBce*@jAUQW1{36xuo=PEAJaexjRcoq zLLyl1?WfrJAJ8qwFlT=WH+~=GnHf|Kt_qk+1Vhl2N+X!zvH7rh%^R2`%xb#va2T|} z_k_!QS$W`&$0w)cN(HR3*rEuGAX%+}uH=jt{r5O9zB7&zyHH@a{YU6u?fL2o5*-Jk_ZmqB^~ZtBasf zC4h31umQ9+L2QDjeOAAy_b^7wYwZ5D-Lu>8ST+FDGw*r68&9uH4=|=t!MV$q3A&`G zL_CnGGP_2x{R+&^UqE};dD|sD#Pkt6z?cDI2L5l;^8hbs1P=fW3E;Ipru~upBVT|| z_WV}ce0#lv>OTR~Q;DOLnyFMwq+(3P8YOEwWz9{QXvG9a326T+iaozVvGX>H;hIho+(g_#n7K zG#nhX=S~2r*B%|L9s24sle5DS_(>IO3yd|X zLK4^uMcU{Ur>K;etyjSI9|tUA`q*NGu_G_}rl375^4ugmMB+!OFDaQ)QgU{DhIilp z2YmFQ53+2Q(Vn6f)F^hzGA9e%wEtFKf5RKuJF}Mp%gFQ@nI1i{Sap=g-b{7sZtAiC zl_pLTJOjz+X2{I55QrE8bNxlk-k-gysK=kXQKx1^0vOUxVT#B<>t1UPKY$vU?;%_N zy|?$Khl#|ueGmL{mH!+rn&4eS{3%oJe&Q4S%7=fJt`|e1Ff6K>aV!(`TqFaL#8aL9K1NsY&X{ZtTo>B@Lk7*Vo<>X6u`AG6P~Uc8IaVjQAat{~Run_;pbG zQ=RhQ$d<;enrm*Nv!|5B(@=G82uYBgoPce2AR;w`+ zS5pJn5xw0%PH&+CJOiZpgn;zdO+Dvsa>N48LVfh0(elVaA337!^5xu<>dB-(b+^RM z-+?O*00v{c%{Qi>`S@8Qp2HlpbqCWS>)C<@@B)X2Ca@AG+G@y z=$yYQ1sB#u;b=^Lu=(jp{Z*LzM(-7vA^^StM4RtxzWk&8cdX>9-;;mJ)J^$~kN-R; zcAuo~`9Q8U)_({DSA%f|;{=02oM6--J;Un0HU9kJ_fTogq@FU>8W-Hp-u7>%n6Le_ zo%iou?WHq$Y|vyab&6o1K5@`!b>bRVOS({yZiI5ZEeW>YX^okC3C`|C#Cqb}0b+_Q zy!+fIek?ALzogWX4?p@o4lO^xbjBeVPk0GZu7c!Kx8nac8qOi;F5xpze2R1BIi^}s zx*ElJZ>qN)GkQI?=Yvar_OQuOXd;!Vap_nPchsFcXwbDS5g0_(kvOQu1ThyroK(=> z3e0^&BmJiDi7#JV#E-J?$?x60m1>1|-SgjB+CN2Hm|L;dt9``7C?g3@FAEbaho9Sc=iXDDl#HMrO)IUC*Gdh_iN*OFtQYQ(Da6 z=a_4x|Gc1bEGGK;UzF0XA+}dhX~~hr$NAWy53sVm@>9vydbKa65zHW9DTz88>(=~| zg>gAK7lK=O?d6xm>Q~JrfC0G&#_VnnT{NSV691Z-vhmHqXt6ffzgQF0u!i!ED2X`J%EV!AVTyAVk}{J z6VmrN;V#WAYkEdpT$6GIVo?F-3WL@?>8OZg3NlVo)po`GJ}RSdy+@{BOoVR(KOgB| ztH=EDJ^zixeG>o=`iyO5RORXXk_XtLUaEW7{P_M#)k&Kw22*jaWKI;@@vVv@B1B|?46*t zUiDjRup%LmYyG%rrZ81` z*Nd*5Z;Y-ZdN(5I)y#Y#dQ5`&;v#+y{7NnP{m=Xcr+1x02|mAAEP`o^cC!5~d>xWC zR6^aNu&CU;a5K-_{vrl;z|amUOs~h&xN@GCP8o?D_isQZQ6+cN6 z#G8{Q2!JF{L-`^pE##o9z1;yt1}KGLLmlg446z>BjGz#(YViy_#8nsiq?+# zqceQ<&0mKV%g_!P*kKC;BwtPhux%Ob;DD}a&s|P~dJ~YAFGY2s%{{5E7Fe`t=1|&K zJ!eZ-_A5#z^@KmZ=eIe%Z;U8dF%;HdBwBrGR`>Wy*12e^jonvO%yHr6SAPk+hkJtb z&(Jr0jC9xXh9~&cpmz8r3U3}$$X6OGpn^eNuc>PTppyHk8B_R$8Z_W$llz}bB~SE- zZr`6i@CTf*M=7meOHqWNPYn5Jl&TzrK>~hB`Ux6f;@G>oi#xCRDheqW+7UxLppYUk zRdIpvb@c-n@ z_G#F#!mECh`Oy-heMyj_{Z~RJRj~abV|Q@4;Pp3s6@BB?{>YB#nLb9kI4cu(DH3kW z8tlIU(VJY4{2IXrf?63qv~0b`T!S2U0L|6{8zIH#R#^}HDoR)Khxhy@r?#I>z^@QL z&!0bql~nq|$|@x&~-J$mjEKbF>XkCxnj;?q2E^3yEOREP*YqV&f*q!}~W z1OVbep9}VX;n;|;yy+ifu|f5ZD5QtvOtq1zm11KObi5D9Fo6Oh7*uUb_2)i+_gK~; zi6-DVPW&d~=NjPWro8LkyEwIb71W1&i^3!|of9ig`n0?%$v%Pj_5|F%{}t>VT|wUr z7}*hh)5o^vS2h}JL82Fs;{dYn=F<4k6e}?8$WV5pw*U4y=CKkbB&B~5fWJoeBk=pn zhu_I@J;rW|>|%(9r-Y;`E@k$g9iQgI5C17Aw@+FT zKa(bU1QkJzbSn$77xm`A1mK5bbhzL(*T0dW88S2jhMDwVRFPf-GFqBCYeS~hb2=9J zbs9Er1pJ!Cct^(#^nYiKdk$)W2Kc#F#P6N={uZYePWinVB~JAU4dS9v_v?m4TXic6 z%gVKbYq@>*E9gm|Q8B~74wCA>xaoDf-o``Nlp||X^OXeN;}=ullGZhO9ocU~vqD&W2G7=GBi?x9|LTEXF=S|m z^rUxjsy`O7iFPfa&*ua>cp#bq)0!wqXV;T-{oKHFclviD>7VM9ci;Cmp4_?QfuHda zKhFU8{epsDk84Z6hB~8GP(1OAQ}CiaFJ=GC)eI8w8+3r*`AWRT(oQ1^5AE(t90QWs zr6&H-I-zY<5kcTOMtRPb^*MeYKJ*@rR-b2V9o86n#wU7+NSNJ=W`M?Ic4F|+<~yTk z<=A&_2X8p|CLi%DMq$F|B7tA4WG?4sD}%_uU(-!YcOhX8{JOs-yYabNHd@rVG+pFF z5B~`#x2^ezpD`51Hg6CyX)lhH+?iF_viVWB%pNWHlIz~UjGbj*hK<{I{=GXlE?v{D z3Ad+1$$85h6%i;RSLE^HxCKqBL^>5dH#Pq{)gRryKe_)do}4>L1%dckhA*W3WHrqD z#~I*8<KbK8GLw-;E$Id*!{l`Df*-?o18|&S^nA4x`qY3(i18K6SBmdivj(Ecj|ELx5 zd&gVe(&cgF|y_f^D*ZPQGF=DVT;&;iNXuzIyj%Ou@Dsfkc z_Q{U%&@>B}9e>W1HtMgmK8?~!hj$N+hSFTXMS-Fnb-OatV=FEYSNxx|Ma zdM78gt|C$On;7wHB7JHkEW2;?ubNn^2EX6oM43NkdBruaWv*CYV7>JBE*1E#??j96 zd3*<0AJ7XQ(*g+@7DyWnK|WC;k*HnoH=R>boV$7B$kUU8 zODP*?_*VSs=ibWkt!L2a^IMoEu_7`yN+>P{2pJ6d# z@RTBc=PzE$wINfq`x(vh-DUGqgpp`$1hlv&y|*WeYHdV&&3<`ibDe*|(uQqi!ontu z#uVyochYB(5xQ&o?|$O_99eycl`XZO>NSQ!^6go}jR7EO7jC--@hg7a{#A>+c-@P? z220T-dOS_wmzNlGi0xBHldzR%bJ1KA30CCbpsPAWfV!z51&uaoM%$HY|8qFMl}TJ; z?mPgoSbEZpG-aGMdru_C`XB@miS`t$2IdM6Hz z9n~P|F>SI6|7sko(jhwh@b8^f;jbCwj@`O6zy}OP$~!9)x2WQ-}i|gcErGh{Qk?> zKgs^T?nEAz?$S@XFOS|RkkG{l9a|EK@7<^>CQLJ>WO;moPd@NgPA`6*@pK98PXIy6 z0fiZ|bzwiZUi)<{^!HF{N##mhOoXI&*=%IDL`{{i=|8^wIQJd>2q(9WG5Da5wau#D z)Y-h2xKXnR(dCQZ4rSMg5wE`SE9ps(p&bV4zkI6yJRNL4aY`84US2p~tRrkr;0+i; zTMUqszotf+h6vgt$3DQ_5B@GIb4RInxS&lWV2EX$D$8q6aPsa)x&F%6aNWK)dR?L3 zTnxjCv&+o{8v(zmmi*5L-p29ylPIK!U$dh`<%iY)03ZNKL_t(XME6k>NYncmEQxgo z;`f)~rY+CohWT6RnSM6aTRbzwkIVa@N!7@KX-4N9EEYLekA}+yLkTZnAfan#8se*X;CIF1E?)bBufbwd z#II+1n=I~lnkA!0S_Pa9mq5x>kj}cq<}z&}f2pDf_<{!Lz7sIXzq*hyH|6ljPjcwQ z2ROH75$F8YXni#d)X-N$9}ld0YU!!Zr(=7`ak-C=-S;yr*NcqnHCF2t*6KB;Zkn{h z(~TLlfuC>jxE}N2hyReH^XEXCc)tx1zt$**?>zSJY=(ivzjb8cbis?Rcm>;gJ3R2S zBl>m_nD`lx{)|D_tplVb-*n zfRCj0!Ul;2hMo#N^~ZW*uyc-+#pn3g{XfG}eTH#8W~EwTtzKp7rqo&|^?j*j9r?B7 z!-syKC+!iYq7>3Z{0y>gQAM=(T@|RPPx4d;rE;~qnpf?;lR^qcb|x+Ce8y~lY)%gH za(ac?zdSY&zf(p~vLAVc*#jiwvUo}4{F<6tEAD;l9jurml2yp z#84pq&bHuvDWiDTJ|R zZp>=6%v!a|)J-ml2{w-SmHg#{Z{zsv2^62`(KFrEJxzaGZ)%O75a*BIy;Znj?lx{( zcp*L03(`MG%etSbqR?y(v5_|zg;!}@{cvMq0D&|IkYYo*F8@STq$TO!gEB_(@3gq;aL<@4QY4{SO(w`_Jv<)mMFWmgq60XP*9K&xOjm zn!Cf&4FJ&y`uenRGA@z!{pp%kW&Yw?D<+f06gW(~q0(8i5| zCYXQ$BJz*Yua1R9%k%f#!LI(E4EXg4&g6O9csYt@n?+}xZ+-H-&iLE3KtYG_uwM0V zgfjh~;q|gWTAoD4e3|s8hsW~<{0cX)>R*{sAQS~GZ8^dxKKCn}TK$~Y1l0swj$Ootg|s`(=ub38||VOCB$h09jRTKHH?fK z)oX$l6GULx6PC9g<&zKoIwzLzWug;Ss})vjZw@L~B|>ydZu{e!{Ym_DV+hRVo!q?R+K-XnQbeGepz`ac!-Iu%CYL5(;5u z+c7@%@NaT_=~GO!(F7AW_U1s_af0>0&#&qK(8G6eVssRz9{BZRl4lfzOii|wpETbp zq-EW`FHh@0tOczfJBcY5<4Uu)6I@?tbLAd1CQnOoJv^sa6;VCUDv$n)oNS z*YrPj<}(~#yoW_8u_DP!G17#5(|~ONr2MR3GwJ0}u2|m5E3f*h^RMZDHcE=%ZD6)L zeTg1G%L`~-&`~8_GZ2r(Iem+zpkV)wmvY6nJJ?bWP*JApn5d%)@8r!J5P@LVBqY!X zje8h91Y=m;c9PFL_PacO=0i+%%4)sJ>IO{UI>2wLQ$Bj=zjJJK$^$>+-BfF`_*|D> z**FAmzdhBvWVvnUOW4!jcb+x<&sOoz%ctw3R&UW$+jk@e=wg6eUANAs>m*}>zyi|a zMc02T3+`$L7@Rt$&M|d0RRl~y6XXx?5nWWt(buXHYPMfO@mUF4^3g+Y=Vbp0oI+2GNB+_FgJ0R3 z5kA>SA?)fI$RE^yuY`kxo494m%bv!X{>xmtwq6=~#=l#60fCmLHO(6;O2DxD3~hfm zPM~B=(5Ii6;D2Z5^dJLgsPI}KngbUqNqE3Czgy-&a^{dUr_fVBi8R@|!h6~?;8R3{ucdml&6e1h zG-H{LxJ;MWJsV#FEGK5`J1@omhWOmeH}wJ)asaM z)C4X8wie-q6fI2010&d=vI_O?VK3l<$-*iRpLrkmKk;@dDVZ=~tP|GUn7?`K_c%T{ zfl*-~hJobs6Je0TkB#00pHf^yqQ~5t<)-Z~V(;LpOI*``IZKpC58ApeI+C?0yEJd5 z$%(#B8)PM2rvy|~wQBaK*E7MkxodgpE#J$I`UXa7sZge__TGVWDTvdOLX(+*_#GM^ znIOgpdKmf$(}gt-o%=BNeEzLC@gVS#V}Hq+@)6c>6ea|H!j>+{>_<(IOMX(DUaz`{ z{GD>3yoQ(VdE;eR(|`Gjs;5P+x!29%Kr2QN&o@iMIv1wz-0rtPMYAb=vp-SC6fmIA zfGsmu@zUG=4LkHEW?kV^$LgA?t0{v|O~wQ{9*B+2fuU8fu3>^g4Sf}+b7LM``D;G? z@Gmnd&vEG32YI4c#F9--<9F~*S|*@cL0g(9b~o+djk0Tb$P4zqk{NRu*7RS_qIL4z z@*q!(p)-8fT#)AkO8cUi1|NOkjUMVxYp061^;lbx%n$eR(p$fuPk#0%Iqg2nvXo3> zkjQ`-6E>Y`OHtfRTchowVTcC0b%Gn8zuYH20dr$DBJ%p$*FovP=m?2kU-OzNb zOJmQ%>Ru;`ox*{^P29TWRhMN=|79&&H4Wdug9BPam6w(Vh-qKBN6@+{R_Me(G+mQp zfkqRU+#JwncCeE>Zux$8n%kJy0d)!%)s)diY?+kkWrW$pj%Q#9nm|1+h#5xJsO0$I zAy!Sv$XI5q&q^wYZ|`V>%-FxA=_Ims2q`(o{*@ivvG*Tbwl)2iwP=-orif-o(#ifz zuD^gmB#Lr-k!S*Y&7G~t67fAU8|nwzQNkX*U=C*c+qmP_A7EGS1#GE@IDE)>;%cVq zsFOL+B(CYBqv zzJPthgO`6z|I;oCTDOGMcSTyX>w0$rpo#%gA^1JzqyhIaJ19>dYWbDj7t?G|vYCPq zCGfalR4j1&&Ht9&!xyu)p7Bf&T|`%h7bJQgH4-|P+E$>5kW(NL6nafCP{T-!C%!Qh z0XyVLDaqcOjQEi&f|#gNt}YMo;@xk&tZVu&t!zBbP_K*kCkzluq@y}*nLC4+k`AGP zlpU1MA-5Ao>gL3G8rNom#vBaoEHA$C``A5m2ix2%iZ=yg=P0%IvwIPoMNJ@W!spCj zT>ys>7=~57JrRoFIpo(T4FVYFHIDB^AiO9wj-9JRZrS}Z=FI%%U(hQ;fWCEg5IZw%%z$PXbFt8(Dc*A$GclH%*cMG6O>3p1^ zbS}IQIWPek;i07&e8@QS5CAK9!iV2h)w__|zPE(~!gmb%*cz_x-N>z5zVx!M>3{kq zQ^yel{&)6(+zV(b+`PoWvg!RclInl{b>G4Lj}9=4 zALf)j%QUDRRTw}}wVHVuZS_R zsy94?Yx*xo@faYb@rOg*{16Z9wY}a0C=jVyeq8(+F+hBbHWhk~s=`6d9&=$$00i$K zd!!L=x#nB3pYLPNyq}ZyG^KN%Fc$idcH~TT1p-@|^gXBHm}aYaau-M2Ew$nAn?0p;>XpeXG>?U z|3YOWCI|<(>A*k5+CFmX&pByMdQDIV*VOn_v6w%oCJ-(LK$?E9o89ymguxO?%jn_? zs`%n;G%oN^t}3tLdHcWo8C%o;^h;)bqc-l4p$p{K&F=^l9s{^mh%mQ@xd1gfS5UVG z>Vjf!&kJ0rbeSNT1cB?X{NJ!e#k6;D(*IvtYMAvupYf?H{FIJ!*0CdBt=T^j>*AC;^9 z)-wgFCsPCBdxevYxrmZ8L2i3Q;NTVih{6sq$KJ)0<|w5)zp&9lU;Rv8yp{j}V;kd+ zE#1kaI2szSkH9g+9HRO+4zB9m$PEjxeMZ;xKdq90cR(!D<7nKaLYmP`%fa&d+EQH= zV9Izhb}qTY;Xbbs8uk(eSDpqW5Wkt#%bs$<(lCJmdd+Ps0GZ0Q}-Y0Ee+ z374cK9y&~riY*H(?s$#Y1V?^{6Ff?#&WCRCOkkyHvo`~h0AchDfHc!rXPCi*KgEP8>3#J9 zuHW&-XJ<|SQ!a7*8wOAmH+~eIl*H|x{H4D0dEhQ{4w@oT6^JbbkB)O&+42TjH~NF( z`pDWRVdjOXR;a5`t&z^IGZ$NOCdlg$5q58TrPl<9@8X0$LKWU&FAhJ+lXe#)rEZIg z2Z1y@xYx$tqb3A~(2x@4SSSZvv;8G3*k@@?|0R`N`n}ez7e9!b_5;DQgtUx)N&DqA z^%|fo;#@!y+|JFw`mM$gj~`b*gX|PkYn&@FTA`Y~;`}KNTx0^lZyc4RPGHBD+cBnx zef+JQa1Sz7WlHT6MGSs%M`(1EkEjh|(?y{&Py~rLL=6D@itD+0{&mmZn*K{FncByp z>R{z>aFySs!kFfD^{uhb?0!4sZz>-O*7X;*BtKySA-#7F!L1xas?$Dh?@H8_c%QVh zy5uR8I9VHK5@Xo7FMYPnzkurxd@FnHO^hU@n7NwL`HkoZK-5SG|7M>8#fnm>|2E+x zDBH^|T(jdfU$`~>7c5GH@*3i6y#nJ0a4tx{wo_7DrabV4=A;p+3^H6!FFwenzEkq| z&PtqEDQ7XK-;Ri&uEf<-oGU$Nu7jl)Um7N`X%eZ=fPoCyGJ6ZxU-K{7Q{2pqu{a!4 zb(D2Y9i}o3ip3MXKq(?4fG2*q#;BuT3;SknW&7|2&+3}~i!T}B<9mFy_Uixm+o+df zYK-_dgr%J;Na5ieGeYADiu9L_6uTZZ(h%6MdTB092s`r*{BJ?(0zLa-OnugyfqH_v zX$Va|wct>wp0?q>~4GFaA3bABW)B zsr%Wt{VO`a@9)B7PgYve?}@MV3VQZkR8!BW@r~uiNb{;VXaRR=9a5y&@u-ph-a{f9 zh^%Q+B;kyBclcYPeNq1)84r+CZ%2q)V1m|D)KyPQ3&du(^9(~`i42$<9pL%{-^s4x zHs)*(RTp+C^Qqe|s7eXMHaLtpBz|zrS99#!`ch`?p5XJ(1mO2~CElN@ey!6-wWd1u zHrzRvTwxPmU)QnsSxv1tkm8`X`;cL<|B#r*FvPul(@K-*8b&RlnWa#{XdgZM0Ze@o zaJYIBG(j0$!>6YSfEE)p83lc2hF5Ui!S7*L|M@Hw14R9Du&LHm#2t!V3iJ>NFF0oE zS@tiyiX97Yq;Cey6tfJ?-zDG|iSO1C-<4<4vwuuA^{6wJ}d4>aK z`n$RIn(t-z=nnSuc2O9?FA5Lq4x~n@iSOwJWu~5C-`vaCx$Wy2m;tlJETdv3tm*$k z6F)cDf%t@vR;Zg&J^I7cXZ={3@CqAanoJ4FtLey}#+fivLvP<9V`gstob=r^K3p?G z#aw_iw;b$2^E>>l78VyV$9}?}b7jT^0TXEbv`s=c&#)g;%z8UGc+C%R#h!2C%Ki)4 zW@qRN7}EqyjKYi?vB%uXzJ=GaZO1p!H+^R83^Tx1)88sN)iUC1<%zEq_2HjJ zPd$RGJ;6=b%sOsm+RFPU{*JgVSbqhT5;wT^9syYU^q0S{Jbvmms$fk)VJ$|QNt$kM zK;!@oesr7&a=m5Dm0ye7@lSkX>;Pkj7&Aaj5C4kOz^SdM(^DNLO;VoT@e_Cds#iIC z@(-yd$G}a|dJ0-&dh_(=o<}kJ3at9Qdj>KHQ@sO z`)SIRS-=O)jPV;M!^))a6&7_xe4&7QmI(*4T5Bir!&%E%x#{Btf>RN&s(3C9^ z5oPotL;8IS6U6hToWmXa9gGyX9bfA;0f0J$8Y4Z_6alP6MpzeW+@v%ZHpUD+ixwXJ z2A9#=f?a#Qk@(i{*dG@YOftV>U`~tA^37?}d;0n5$WM<$G=aJ#VSk`lO^NRnJek|CogqJq}n; zJTqQE55FBVc?kCXfS;*zzGKeTi1xq~Af9YloUUnYwZzi$s#WZ0z zFA;xzL4g$;2ya8`c`h;+LA#478}dLm+SaxCZ3a+RQXTmz^!WdvTA2yc*q-o4O%Tw= z?l%BED(WT+I416>DPi>D_rkAx2C!RR`iXiZr({ibfM5S^;@&=Sj-b=-pclxTi)P%W zbLg?Vk?D70u6zsH+l5j>?L7F<0;B-3o)IKtf>uI-q~~o^V&ozYL1YKbh#xN$n_nRr z+cX)$8`5Z#r#|)48Nin0yAA_{tmyJA^`rj>dir6i@vO&g!R7OtT5X`UHdLx!E%SCgsC?>-0#K@eBaO6bMOC=sx+uLoEmK0Is<9bS~^ecq@0KV^#D#TqUEVcIiSb2E_hl{KwEl}L7rK7~ zsn2uplI{15p!F*5#BbrwyaRVysOtgHxO;I&vpoMVH6v4Gn)fj#kVuWp-+tHZu2-FI z-CZr+yREwK8y=mW?JppHpMGm>6JW^OFB|=iYQamCW-3+96y__;);GZJe+qU>ko=$l zTGH>A2FqsJVADa3&A71by>xPKa_2gR?UsxxxlQdbfYz(H;>vzzekElj zNy01ACVE>T(_*v${@iG(=DPoK^JveV53TFbMG{hDpYzJ|{{Iblg)QuV9nJP(x3;4F4 zZ`!y+@IV+vVf-k%_yKg~o*Uje zxb1Dk97Z#z=# zg5F-Q9k7TgKzacuSRnshWWS^N*ks(#N0Y9^obOoY-nfFuUr-}0pS=dE=6CSn(F*Dn zv^)mYai~wDYZW>fqE+Dmb7pa)Ktzm`jF{41udz{^gVZP0ua0`SRCE1*zIC+c%O4=S zwN)mc`<@@G4}a)~>&k=}b2J4(&1Kmp=hwEGWJc2vwL|{oa9&jrTc`R{1Et~Qz(OH4 zJ=D{iwk^d5gOUWH+v_gDyl0C>l_2bXf_^R@|`LyOpm*3e)5jdt}i{D-?kx3 zRj1#7WBt(ge5^dv-{RX4-kVDZI|}P0bu(3(q;*64)EVdvI@#4~8{OM_1{*WN`6Rk! z**aQd-MtwjG?JYqVMHd23l^kxOA?A!9jW4mjdH@T(_pRVkd0lVbz}5T)!{}p#@(`-UqWitVp?!0i9dZ|KdfIU!`S)EVP4{3c+e8fM}zV; zy^brWx?tzxvDW!GD8W^X5dfbP@0SmWq&2@DEoXl>V6FX845|_AX5ZqEli#DwcdS3f zF=BtTvD^8l)fc2)NBozY+Ax4E0g;CILLB$ViE&?rp!QX9y%o&fumA3ih`%WV0QB~M z%e!UwSNw-!Fh%eI1m|3%QXY|28{q3 zcAoIyECRyrt7p>yMs3apM9K-%A!hu}6C2Z|ftm8bj#AvDN_u9>B|SGgIpgI_*3tk$ zJLHm>GfLEWIWwm3EWaX2_6CGcea*(9VeA;X&OSNXkF&CSpZ_cFz)#&d+;Yow{S($d z>w={`{7e5(pZNLTsuu@yc|u5w!_(kp{YoxO>I+@^=7Sae+l>JDx`xNW?4CalxJ=h3 z@R(g94Uyu7E$K7sOH;xxu>QjGlTm9IZ-=Sx!>($vt>cHm*X3~?Y@1prk!Z%aH=PS9Y-t5}e zx$Idj7mutRt~s8rrG0GE`som!^-O%BzI2aupY5;Hj47Js?Ebv^8NLN~oc%xekNf++ zc0<6oQF%%X0N_?0*jhjSU*5vmk9?c14hOEXskI5#C5_NLVnd=`XoT{zi3*5`0SE&3 z{j9$3_;f&Ghb6pw^U2W6AOHxd<_M&Y)tjh@D zSZmOZ35udnFl%lz2|LrW_wiCPpc1CYT5Rp z-`3sV_Lkm`*F3-_luKrSSlqe0uW-kH=?6IXfq$VZgOQd!P?L?J1i&NhPDcWFxqq|c zo%4_l*hU1{;CR>GUku?ccz!d+JRh@eMtmClu>KYWabQarPAb{*lDpXRZ9i6QfAxds zY3T*a(`JBJ-0Gn@UHrgnb?KvTVEpMjSv!5RuFZh+c{(IlE_?F%2D#J9w99pO@MGyP z*Tvw8r(B+j$%<{+=$=hAKyFI{!A*?}S79{&D1+y{m${e!8C$;OFN*owPG53AI!$>- ziaYnID|O{>U&Q3_LCPl%>h#z_Jr(7g0001ONklY)og`BKXN2M0wX?)@HHEdT%j07*qoM6N<$ Ef@s#zga7~l literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_16.png b/docs/assets/cgi/icon_rounded/icon_rounded_16.png new file mode 100755 index 0000000000000000000000000000000000000000..225b311de322173d5456cf0ec878c6c5f5bb71e2 GIT binary patch literal 1183 zcmV;Q1YrA#P)~n;^kfunj1PHeQAP1=ENjm9?}gn zFFYnZv643&-V|=pQ`(a7v2dp;2Nb?`^q9*}4kumyaM)C%5%4w%E6HCR`^9?7%m~ZP z31-p-{pUQ+P3q&>nQ8HDG6Gmc86}LuIKxB;(aWIN5MA`qEi)>__k71sk&{?mtU(cz zWs;=G($W-b4bPkDy@=nlI%uEVwoMS?BN; z)Kz(^3)tR)4%y$h_yq)TXSuOh7p(vQ010qNS#tmY3ljhU3ljkVnw%H_00P5FL_t(I zjeV0%Xk1kohM(`;JGnEN$xJetj)t@vM}%OhM5K#Ssv;CY3Kj&ZAgCKfEs6PlAHO-%>6lE7jx6pg?$g-w|d|6zUQ3p z2teGb9t>|^I%&hkZX&-N%=sg&j~gZ>4#9y2sl5%dlT+#p?OMYW>LyiOba%b}k(+z! zSBYEIgWb95Ur=ih5GMz2f$mqUG^@982L(BjV&utvXtaq&P5KZ`SI#^u-5c*;wXNC1 zxCOk*8BC_KO-KqrQIwV5zjV5DjMdIUxPi&-`_U!1y&F}$%BPsjqxXn+pukG7#0USp z#g;~#-*cLwzh}^1W84t>Wg&-2SN=!bUzcm2)7C9K<1sWiiAWv-zic#UjfgYM{cyc` zjo*6LumZ=5ui_aGaS9N%hmBY@B3|M{(s$fL4Y;y&4hu}Uk8q@P6hRP4Lkwn{A8W*N z=TBmd?#5P>A6CC$DXI|*yg7CpN8J7)6-2=hgS9Zba*6L2j`Pj@du+vBq%LvQshh5NTku`xoENPt%Uq`F{Bvq20nW8D1NEyC41T6!gnQ zMJ4XZ!-c~H(K^-o1vj0g6uq@^4g4}u xdw5%{3esnRo7*6juS>dElZiJzG{yZt-2uixF?oLr)YAX}002ovPDHLkV1l`GCCmT- literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_256.png b/docs/assets/cgi/icon_rounded/icon_rounded_256.png new file mode 100755 index 0000000000000000000000000000000000000000..9717c8d704017bc4db9b1b158ae6f80ddffbcb99 GIT binary patch literal 52056 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ⓈXVHA3j?O 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

x7=v$!q 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

xESMgA*8LDD8PXO6S|$|%#= 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¬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

ipz5zO136gFLxxqg-sH-Uz4RHP^^< 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 pW2Uk6N^q+%+ZC^^q4n1fQ&i=g=@S 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

>o=IT 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

O0H`F!*BsM3EN*pq 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≪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

jh=h7xoK0rQ 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%0Asbzn?CM?&%V~qx>Dc6S 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

~G~dqhsUG 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%0DbA; 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 literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_128.png b/docs/assets/cgi/icon_square/icon_square_128.png new file mode 100755 index 0000000000000000000000000000000000000000..2600bae3bcb8f11cd6ab9496a70e082ff7f866b8 GIT binary patch literal 14641 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 literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_16.png b/docs/assets/cgi/icon_square/icon_square_16.png new file mode 100755 index 0000000000000000000000000000000000000000..e11979d52ea7384e525d9c0b14c2289edab5b92a GIT binary patch literal 1066 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%0A+!#%` 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

}FfAM5b;0=tuz^I)N!= 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 WI~wbg%^2YMk(XBaP$_8=@_zsf))uY+ literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_32.png b/docs/assets/cgi/icon_square/icon_square_32.png new file mode 100755 index 0000000000000000000000000000000000000000..24e9a609718148873ecd925265a16f3a8e589541 GIT binary patch literal 2011 zcmV<12PF83P)~n;^kfunj1PHeQAP1=ENjm9?}gn zFFYnZv643&-V|=pQ`(a7v2dp;2Nb?`^q9*}4kumyaM)C%5%4w%E6HCR`^9?7%m~ZP z31-p-{pUQ+P3q&>nQ8HDG6Gmc86}LuIKxB;(aWIN5MA`qEi)>__k71sk&{?mtU(cz zWs;=G($W-b4bPkDy@=nlI%uEVwoMS?BN; z)Kz(^3)tR)4%y$h_yq)TXSuOh7p(vQ010qNS#tmYE+YT{E+YYWr9XB600sX^L_t(o zg{7DKi(OY0$3JWBeeQFTiJ2swOeSfklY~$$1rt%xv>0g<+X|&vEJdXj!GA$Tp(;Xu z_NP`6LCFtVwS|xrX$46zgcfSGN==g(GtC$?H0@-1XD0XFv-k3cv+tdA=ha8n;oQ5= zK6|gncfIzJ*7>jh&N@>E3AwTkg=ow^J?H%ECO7gfL)98x%b7oPh34(^cmXh^xj8oX z?2a+*D>IFy=ZKGPXxFpy-m7;b%?c=v z#=g5S=ho%br>rrpNG=4x1XKYj#%OVCajPiVX2;h1M_33Qa*MRO9XfN7@YfN#0r=P$ zCRu;a01uWw3*@GrARq~V97H`@+liPK^F2~<0m7GzvFU%)nVqB6U1X%Wk?Yr$d{Nmj zac~LuvJWC)MrdEcETne=3cieph^M~qXK3YCP`}KB$rUlePoV*9RwaZ$HAb9ssA}?W zBoHu+Kmfqw$T}Cf@%pz>Ddqw_1#&=)Y~v2dwy*29o{%0n=Wxq>9th;*oP761-kdqh z#PA-zIC?MMkE4xS2n*8%x)Hofnm8Z?@G%q*ACY>(kDmQG&vk!Kz~F7AQgs~M_<5wq z-XK`Q_$K77ph)22jel{x^E?ql5WqM!eTqO{xFs))h2>6;D;a(mDy$D2J@*T`(m@eG zune}=w;@R#0B1b~a*7zZN#QYt-k;t%&OhWeK#56Qy;J9bonOY`z!KsCddXf;esit} z1Que89=*V9f6zldLZ*X}wCGeMI3oIlukSFZrKdNrDRnw&!C!cI(%0 z>Iw&x3$7&>$SO=f3`HVW!W1W4F93=lL=3}tWa2R@)NnWy9dNY@MGvt6Qm$S=#L{mO z$+`wuJ?o!6{|s&IP&Q}3JHR~~4&l`=&D!9w94Z#tO%lImPZ1wTz`qb zcVDImJO~IC^?YOUTex&-8T$a@AmrdhSn89JR6+yi=l;waS6^Xb+X40te*uT1LS;aS zK%mVcFT8a$F_1obM~3ca-0#9^fKAa{PEH=EHaO#ATnToah9{Klf1d`7cvA%iaR`y?0ljWcxzanWJ*e^sLLkq zoqURs%3eebkC6d&gxUO`96R@YPEY+B4|o(WLcq9h(|z1l-CYX2RVXk}{1tprF92prEZE#@XN%l1Fp{hm= zqnYkubsQP}Wj&_C&(PA?yV9gWQ6HC)qNxlkyLWtt@y&;MZ|)S0q5U+QpF@xjCLAalf#A}a&;x`B zPK7Y{UwmfMr8)-}jMvve%)Z;K#w$xlCltKGh` zMGh!4NXGJ3Q(D+>bVgXXh=hPs>C5z(6-Of#BH;1>t#gFb2PiRj<*~3LXnV)|KK0f} z0eA0REG^b9VTn!N+a&{xT#0diyvn!iiYO>Gxuthf01^#M&AZ`!&xdy|eU^YCLhNl? tEi2ag)?Pk0_#M7-pBQ+Hx@q*e{{su0PQrUKeBb~8002ovPDHLkV1oC?q&olr literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_512.png b/docs/assets/cgi/icon_square/icon_square_512.png new file mode 100755 index 0000000000000000000000000000000000000000..da117347a9b34cbe8a66856f79f58a9d1608df31 GIT binary patch literal 155209 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 XT#Dp2m>_e600000NkvXXu0mjfT8UJA literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_64.png b/docs/assets/cgi/icon_square/icon_square_64.png new file mode 100755 index 0000000000000000000000000000000000000000..d9d63e823d5e6a1f681e7d511ad0c202e685f293 GIT binary patch literal 5036 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 z%0A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

    jo|cDkI|#lCS%UW>kPdn(ILo+}Rc_4I6P-L}Tq z?eu+bSHS2kyb^rprpEt_=b-fNGZ|4nAhLNp2@4L`66YM!J4YR6#=iEm~zjH&A$CH10RJEkf@SS#C1WBCnmzyu;#-{LR{Wte`Juke)vE%f z!3uQf26WZu6)`C$TtmAoNQ??hlxdn~2QK&V@hevK7AoFbrHXdzw#aL}b+O~kUifSw zQIhzscF*`x3CC&l{r;X3ni)GN?&&B*71kqfJZJ1!hgjka-0!jq=`k|O__?y`LG?Ni zW=icGGZO@!sTqFkjokAJO#iXki$bcTP( z=F7BrQI%TlwQ))nCTWVc#*e7ocPB5CIDwZjV%Ln-YyWp()Wm4BD=yL_=slI=vPeAz z9W`D=+q$X~_%@1Xl=v;>*j2@l?~UK+q?;9ey1Z3c=CH6MXa9=yn`<||LV^0ybGX`C z9h{i%HQ7|om|K}So6R7{u~HN;`2A)K}ve4QyiMELJA%2VB`c;b9EyJQ8z?C+9mK-&)!q*ndg_1?;R*vT>isGV7XXeQKtq!}F z!nx-AZpCGj41mGVG1quvJ(6J&7}J10bZdT2q{p{MW@}6az0dpB$XF6N)(X zA(mZl&R9CmiW+vJzgvlsp6M_0w3Y?2%w0^`NJD zbyj|~84?%Tuhkzv&I2nux+Ram2wvD}NfW$HxJpz-c=(d>{7x<$0#RFuFJtCz6JB-! zuR|}&1`YqWvlJ-7=yYKQa2nb&qRtB83lKaB@QvSk6bK_RQIE3{bVTSKjAcPVoiK6W zqu;NWUQov1Al4?(qCk6c$s~1^Wy&=Rd=J*42JV2ktz_~PnF+3l#Fs+;A5(7~6?N3L zf0KfANw+iz42Xb~bayv0G}0YIcOxBwbc1wvmvke|P)ftlyx;qIp8L1n|A)1JIqRIg zuYK+7!=r&@afKXkg@||1=6Ov~;jvjdxq#e!$0(W9o&Nb^lN5YPF(kcXfjMA2JZ~Vx zukRSDmY=*cy)6TG{-JC9Fnlj+lqb+cQJ8P9u8d+RSqBpv9Dt6W@P6>9qooa4k5%tD zQ(tnRmI~0X`06$NkDLACo`g^f9XwHO(Go^+UHpwD+TuL_%u|!h<7jtLnQzFy+0*~T z$Hsz}yX%0*Fy99fx>OCfe3GW=fBKC5S6}2+^YseF?d4XKPP2;6AT^|SlC=^C$rbKPMB4?mku6N%;*xK^lj| z`MHk7QYAD|_8MCD-*tGwEWC&krZ@6ezh9YOSJ-YrF@)I{t3 z1XZzCI>zSD+*x}775!3g`FoZ=28G#L7>LoUUc7l-w=i;*jwpMgrl94NTQS9Y+!L8o z$Dm{pjv*-$A4q@yz%W>oS^|xH^HT5n#f)oPHBE+P`ceFM36=lZ`0Hd=yEp8A-k;SI z?Gm84g>j5v{}Jp#N#t2FY_VQnD*!1Wgjb6zGBew?pRyAYu@}Ka1kAgY+tI!>4f7Mk z&$33^)o(-(&;3~A*6%U(g5MZ;xLs}Duh+#nTU%5(;A7VZXf5)TOOgtj&42QhRR$E_t1$Gbau8{5fIWoqfqq>zaOs4f#(Thpvff? zD?9X0mA!5aY3LrLWy-IR$KFW|RN|UUu3(}48g4bYK!Ap{<Y&SSW|(GcWO!rmo_ID6xq%F>kq*~7A_?^ zo%=$V78A=nr}{LM>YbatI|}z>grU~oMtg%rI(Y~uq0gDE%+6<4V z3iy95c%WkSWdvUuGhM5B&TO_08_lL-3QD+G(q60wdR#R^BU;Mnr@; z!kd74l#6!0??c3wT1HJ^Qq%7B}=_?bMiuR9T(jGhb8Xl*=7lSVe>}OI zv~mVBoc>n=04-j1Bd1YT{Ab6i*e4cStO~H-LC5Iff>bq{xKl|57tE73sDhjP?Nh)@ zE}L$p4@Yz~wHT@ljWQ(EUqbK_Oi?I*>+vJgP_5@XjzE^_4HnYX2Uv!0{fcl(Nzy*M zpu=rOeK0_IOB#g%uXM42xto9oqrYilmHk~B zSkyTMpw;Ge^q-D%-Z-ki?su2^yvC1qUeq%)9(m4^X1Q*cb02(~`v&iOc%Jt=<~v_1 z;v^{rg^lpIUm1Rqq>HWHRqCIH)&bV1i*SvW<37XVkx?P@j0v-MxCLo(bdfe2W>iIM zKf@5n>1f|d@-@>JdJ@ig04Gn_hOXC-4(0u^0eH+XSS6PscEMmncl9hOB5ppkoh zH%y;z*BIp@j2Bkn>zCpOlkF&rsd~&KAiR;f z@`^s2Y$$mnbaqEx&Zf_;ul8-=V)*;{qYr7q!Jj3__=-oQ-@iK z^@C$X|0<+llUU};3vU6Z1*<;uf50R*1vO%&SeTQ$y8K#}&lORVl{x3EIOk)d-&y*l zdspe3l38x+eg$5#y@Zf#F2&e-mQKE&>77TS7$ZD-^ZDd3-ko4G)SHmtQZoAQ1lhsn zIRrRU|Ilgolb>&RTvWE!Gb`jS@+K05LbvwSp1B)`Xbw!&2B(mk?a7lwEKoL|?S?je z5!3z}8Rz{^+w*QA-uI$&4F=gpzga&PX+-+wkg$`0Pj2I3jHU4Tst_PH;l7jAuRftk z`4DG5f>{3{`%*?pNxZYHx&4Hr-(rXv`??5h!<|Ipf^&(?w-%H)Q!3zDBPG5O>6nW> zp#}W$T))|^_}~sSvySLM&^TeYiMI1{MPVnDp`B#^_td_5-kev<9a#P%z^00A3~+T& z0H7~UhRsWnqdvYd%!!+xh6j^XjUnV}X*aNsW_9 z!|rg7$P(w+Wy{d1q=4%W@YT8>h}7X+G0YQkbS)XKmc9?9V4T34d}=mfYx>q7OyC~> zI1l*?x5a(QABII={ECInX8%v+`Qhv#bLtse>di`7(UbH3@1{j^pCv-xwv+UbtoW7NI#U}f~u z4?5@;DsK3bO&C4dMWTE&W-14-wEqjTnNIG^tN$L?>&kAYa!mRLs5?509oCvCmwy7K z5S2oF4D-dxOWfl}$AuwX!={`3c5vRTYI=V~liZeYIgwE^BAWi*RvsDu9>{(`n4omE zr4dl=I@Ty!<)5X7jNc<#stZ^0LeF0|8!qGkZX9<;|Cxn;`tbyfx&3FLMK%g4@!oD* z$1!Vf4brCu44?b~A;PNu&E(DL;x2F)OYYziicY%IvBbyIfuw5Lc9pW2GE2}db?A+5 zrdP<0z%xY@S8<>yZ@h zCZmy{APc!NhR7KG1gJKxiy{|9UE3$HP1*mga3O=hOmD;N39@kTG(p2?O#d359K6aw zcM;Ld^YU+%bQllzqJpa7;^oiaRDqsm4#iQl@8@&xT)Q@1^H+~2{HQN<)g=m8kU72| z3OJa=zBaQ2Cd~W17by@uzIRec?VO5Bw2j4{C$lbe6YGwy4QdyE|V-1bD{*_ z(ooWBv3u$56aQ4X2Bhh?QReGp>-{ei9O&b~*>hp_Xs2-J|FlK#mXteQyC9DT!jJXp zbDM}V457-le=ebzl^*skDh|{c(*49^cy#MVcphFqb$8Q!GI@a4K+zeEIM$m@LFRnQ z&Z21y;#~d=9Ex%xc8ia;8tY`;K?gk_kR|bk|86|N(zt`{dCT2wH%j8LPf~!SZS={Q z7?mn}#5nLn=quR|wW;w>GsBZCgI~Tzk}Z8G?%Lv=q_<}XavDPc%r^~oW}C8x!oa-) z;pbcv4JOT#Udt4kWpNidOC?K~m?enP{Y6@|ZmPQiX z3s{dg@%gZwr`0)43#ynYSK1!4W8)Bn((@boutv)`u#%6K5-LqAyby_ZIiM~Kq(hH? z?z}pQgU#jM?-2PP^B7hCw>6SF8OF}^@|g6mM7LVdS+ zS3~(C{yQ(mb)l!jyB~2tFe`Dg6>e+Og)PGfF9PH$bamNLdg0SCE#|&iLT*`iaHYbd z&4_~-65SMl|FB7f8>UfuoFF*Z^63vARun4al+*X?bWunIXY*LCtHBALil_+V#0B4Q z1lDz4fWOGE4@wg;(cV!#g} z2mvd7Y!tbn7>e(DVRf&mx&Duk3!%N)J%ikzy_`-#pj5TE@H~niVY0FQGPaC@>KKH}~o3pg_!c(=qXMQ>?(Xc9BcSEuGv3rryJT{_u z!eD(SK}^S;;XFZ+#BR`F8uGM}%)USA0$r)kaf)XOpHB)LWbycuh<&n`_0S?S05(&T8Je#`=d&Z+syZF1#V{EgBxoEfnhgBm)M~(yp@8<#ZSNb{F4)5M`{{&% z`*d!LSBKkvab)NdXDKnZ*EM_~arkMEc`LG=Hn=TYr-7C@*mb>uD@59XqN@^aP~kEU z%}(9V^*f*30b%qnGQm&Ug49x}1L@xLq@4s0d$Nd0zt{HzvC~c*o}0UF(M9h1jh|+3 z+>HNUJI!#MOXTMoxLGH0WsHeN=+x>LFO=L@#4N5y|1>jDXP=J`kuwXau({iU^b5YJ z^&bh9%ql@eirm<#t*M%^Jm8wF`nofDV#w^eQzi^K1x`jPJ;MHd4T0`*&hB3Z~`9dQm` zC_nyaOl|TcWkyi+Rxaeg9l-^;ua=L_5iJFYY8T}Wt~VUJB9GIt_iMwG6^I``9iuTb*r2w!xN8?DfhQL0 zqW8SY6r5Wc=tXi=&SKi)tSgbn(H zQK*Nq+#<;zhg8LWSC6Hg#71$+3~N}Pnj+TlB{t#bCE#8dL;2Ydh^ns33S=y1L$emn zM|$Ti*l)(teNNSf#XZadBWC46D;~Duk;5YUiS6?)_Ur{@l85vcT9kYz5>am-?m*@M zkXoFQ|74F>b8G);z!b~am+3urS&7v5-pQEy-&utY3GBjTH8w+H`{`UL%x1C}vraQo zgD79%ghIiW-6y+e9Fu{OIU?&i+_dvCVqGG<&-gWGEPwv5p5fK@NAp{gyf@s!d}j7h zIVD(o8V2f<9RpSvz1_;W*VSr`pU3{XF9}x^lHfVX?Gi?+7Qd@sw-)zM!nJZ$o6VQb zR-@fx13%-8Mo?;wi{asb(3v>isX)AO*Bi5%1d{K$Wnn*%k+C{gbE6 zKM$wUCoWQ^4~L1To(GDLd6-)udpdEdo&LcFbJ{A&dWc1bn!mNx$oAt6`g=U z-w;=;S>?UqJ5flS02C?AS13^zwC3qBZR4?fbML18Kf^|#fy#A#cIh*&K5r$+xwOIR z6sY>{J0a?sC;ZVVi}nt>G0{8$`4*?`*F%cmoF1CeL&T~ckW@jUxW=H`&9iGE#nBb{ z=HOoy&XYswpzCT;n zFDY>xBMfsWkPYYX!VP{ITfW>o1Uj{%MHh`UgO^;uj^?H=Xf`^bMS>`^e9*D-CR3;a z5vuYIRq5vJ{cVEhB8Z62$RU?vUEA5z=0*77SG|$`Zo2w;?>}Vs%KGR(;GKJ^l zQ$fm!5}|zY!7Eh)(YMDwnCl~{ho^!qz7s^@v0D_bwkxr`N9#*baFSq{r*KqniQr%Y9? zR8}?%c~&%{jc0vp(hYZ8=QA7Wqh_12vxR-icdn&kr|RgBaO7Vz_5BDeO;uJFhqGr(~21#Ui11m&Wx;TZm=K`-TclXM7m+L3$2JOfu zHOdVyiMcPWx0?OiK2(i~vJ*{?xzf&Z2CK0ZAF`BRi-kW3g-mP&V6VjjWV%t_Vdf_d z@L0B*6RHAf%P}V6MiwRi4GJEJKbEZGV1AXs*rTtSw-$CE0OxSy0cYUq#23968}};O zSr@YRZgkM%XkZ6C+39lHc$ep!|`of&LKWFpestaD&Sz!D%=cR%{c!#__8%43_fm_kDfo)kc(kr`Kb@6H>Ve{4^K1#(N;2)N4(8r^MnrGF2kAX$^k(KQ2C@sKUbU_ zvu1)%#tnk(=r-&(9BS#V+pDDNv62zE<*MmbTxI%LjG+~zJcYNyCg-@LV!NwaSjl;T zsE&^kOdQT%?eorKdL`9&oiY&hbdKiA=cnyTo&S0hc-nlx!wSi2L;IN)Q_U9o3z6x3 zae9u?bLO3eoxX%AmXs)Ni^PPuSkM6yebK)Y_Q}Jo;qHYyW22Kn;1#Og(|$z$dy}H; zzt8Kp>;KF*_3GMBujDcF7dJ^Gm+)~vO@@GrdmQ=$1!motPEEB#v?H0)9QxLXav2q$0JmqfoiBIz9mRKl!X!b;?A28QKr#t>^$3;9;C7x^LaV<8tO!MlGFDA*(j{ z+n?Wj=<;{dn|*vI(Je`90#&w~gR#15hPo1|%dxfWV%kl?E{~;ySX$3^eHD+uSNlD= z+?2-iRJBt#3i#8XFzSl)ZhRV9_Z5gC6EinY{Mw0ltQYXa3BX}3WOm9(T|6wviK31Y2o*8Ko)-`bOFF{IMxl$xvDMWnf!Y1c=01&I_ z3CLK7dpEUr$O!|4BlS7UU7kO7F;b?A?>w9OJ~xwah}^Tf)+oFDM*%jHr*MAwP3Qc9 zHoI>;S5~1MK8Dpm^N5LHbPY=BtM50GGx%`oc!pP&V0^DJ|J9pE=N>CJi{Can`+jf^ z5rAnliJk>SjN)Uh>>u${KGMWfZfZMBSgcFqd)vM6Fh5x?B!eki#}NU9P`42;VQ+J;nqm|Y*_}!$Hy!IBJ2%17j`aRnPxPTvEVZ(Y6SN-@6 z4frq*;X;kx{7dkXciw;HUq%BF)Xn)V80s|gZ-}bHLma;2##)(yEC;PWq|_my4Nfe`eU!&p5Roeq=-<+8Lr`LL#oM5G&IM1As#)3M zOp%vbg42f?V{zXzJex41kmm3}-KDAS-Sn&5E6?nMSj??o{R+9{rjP~8lYtL^-wppA z3v_{mAwB$*KExxh54Z@2LH%o%R0R-CYL?VQZi_g&AL43W{`OZj`FnmUlG!Ov5og(u zNW*pMVzI1JfLG=(%*n>1T3{X4NJq@!Me_X3`LQQxs;394LZl0^+7X5&)21~s(qy|q zyU4AeTirnL;UK4&qHg77zCdDN{h|pT;DVqCm3l9`Q>&m89Kq8v*!!mDTRh>{2f(R<4ZUNb8E$<-Q(X)V~o zYmII#?-8<6YC~?R#+!1!mt^!41n^@@6wt;{QM$IfHc$W$I zW+$v37dGWIuIR-Ni>RoLVmk>wIjeYBp8=aq5Hnyy-myVNj(2Dn5=GOzPu+YCoqBB* zxf+BCd-kXOx6XO4JN7{H2Qm6V2>F`dT@x;f#ALORUUr?RXtEz??PudDFQ6a?NAT_w zp0~vpK|iulyLW-ucXBSpmuR4{Kf>)}JppDz>NoZ3nwX0L^`1l7t!3Ca7+@v}W%(lX z#U4{aCpXSiEN*i)AziMLE-;$zE4d}Uzpz8wRAb_x?_J_@azs^Q07e_v&hiZwTMSRX znH1g{>3w!~ya}QHm6{B#K{;Yl=eGO5u8cLLDAZ=?TSfIA9C>Fhe=37uw_ff&JaqE? zj=O`8(yqpgZ}AT}P{Se^-=t^wWBwMff(9oJMUf-z#Ar^EJ<`8nQql|1FZ~!dIHgV! z#1CijRI_R4`)J##0(+QW6b7c)jSJnEnz{EUZ|^?^hKIjk%{9r+i+tmKBO7B){k4Nh zI+H({Lv{yGeN}kxl-Jf3egrZ6!0N7^0J!zJ3gg6Y^Lkil#=qY)k!mw8XhqQ(g(`nb z#^WX5*D#Q(;p^;{_Zly_(dmTe?k0P@_AoJ`zlBx?(h-&+MFws1MVT2jy?r|!y9L+B zST&*N<lH9V1rQ3`0yHfVO52k{EiTXMx_4Km&u&S3+imS1H&4= z1^>9h_v*^n)W1<}?Vr=pDa6bTe>Y5?@+QZ5m0geSX0#`h$mLmQCr^yC1Bh%p^nm) zd`Rb@HJ->!9K2tB@a+*1M)>3e&C&)4aV_7U<9}eQXJc+#$`7U>!04L`Z#kam?|f=5)cyda z@gp2?;b{5acEWCesgvzXy!QALhtV2&vi5EYdv*|#ZOc@5hWxV2*t#o}ngX*ZX=0ER z{4oy5k^#O8@47-_VpK4RXSp_B-(-0(@_W!$MVOD#0$G&LDkh&?_<$?_?(8l0ngSKw zgA!U2ARv#JriksBW8g{9(OG4H+%#+hoK(|Ju}2GR-W<_#4WH}(diC}%;_2~t8Gosr zy#C%~{q#*YBjgBaKSrKe79ZvuZFv>_q(5=H9P+`Rt+xX z5{6Y=q}8AQ>f0JhZYc*vaWXjUxO{^dCu2Y-{c^t#!xty?Akte#914g6H2cY>nyK$i z4E6Y%dp_TO&HW?N99rjRQb`$Qqm4}g5m9D26f*MEdCvA6=NT{fXp-^#CW@}(nTq!M zyZJd?XY}5XNr=cUi|%FKi~G;Sl$*94L8LiW$k~Rn>|5o~I!^^^qxgF%5gk<9${k6! z=%}2=2g@H?(>c74>T`BiSezj|^K+PLO!zCm`FG>zpIa=LE)Y`8RbzLxw-!!UvNl{ZxE?@*wvAJ897910l7w1iEg$6RPK< z+~;UxNXP5R7F=hTz51bjbkD`@g#~(=JEBQmo8TCsQQ=0B`9(U+Ypar*=toB7tU|_` z7|qo@!^Y8BP(&86Uu#rNmx#%}l;|}}!UI<5e={!-c|M)nG(yZBk3Ksk_v+gDh zr?fxPsu=KUI%Q^*oaoVx&ucef@$t!m*LHwrXz%HVU`SgYC*oOhqC!-9-vqh(KchBG zxaDwUxUB>fM%3{9`~=-hR$T8RzLUSBC>Gac;HEJUcE`dG90R>(s~b^S$~z`C5~E{< zBB!dPZg*EQsr6NbSek*t$pr+p^m*{BQkhC$ej+_6N697cM%D53 zzENlIQ!6z6nr;dfL)#y!XnncpzWA`&fz9^6u9*iOeUg{w$M0S4^snfgWn6Td7zYbl z>G&md6SXvFl0k`kinrflM`IR!vo;4Aj3?iFhVP-r$?6wApdQ?G1e&+#nS&0sq|ER0-+IqME=wbc0{f-E7$avB@^@l z0k#~qKIcOf;e*fg-d5O>yoeaOKmCcl&IEmtqBGi=T)G0 zJTxx?`_p$+qN?>GKOcWf!*sX2qz?EX`{J+a=Vj0Oeit>>d4ih#N{ni6A<>iFd zg$h`>GadFTNEUj|ujcby^9;^m7xUSD#?8r8Yev#|n+ZPk>%?`~2$>?axse0mc=T9s z@r}+*gZL;I>ohNyt|6ua{#q z&x)w#Z5#HMJm>2aAEU#I){Jkp#wRk|fi~dT{b2J~2o-h%oD7U~mxdoFqnUwk(RKTW z^Io#I&9~#JRpNn?HfFPX?J716Dcs*}jtOyKmR?pXHmyt~VsEdRL!W7tjG z|4G5!^EQ3CoP1^%cCR!g|Kk2d3GtaZn8w%YIS3*74XI+QbQs)4#G3RsQOH{*}RQag5#L8bLja z1fkqFP3XhnG($e0-kRQG9Ar!*jWDv0FoyIc6k46yE zEX?loH!YrKmi%f@Z^~*QaKDzw$|Mi`4>TTfVgK6fXPkq^r^yRo>roF z5)7?7bZ0*oAMGa)Z6|y_y_89VeBoT_GmEut2RzWSmSM^K&_FPM2$H;Md&e)GqM1@X zFBuB@{qu$P=vU(NF7mpjQNpyt#thMr$I}sT=OFlD55i;!clD$NvH#7~!U1sZANOJc zNc&?HHZnbU_Gz#MhXM+c`aaMiuMraN&Tq0Yn}kL(7e46HeJ3;_h|e3P1;~wZp>cr4 zmnBv*#p6NX_vfqL>k&g|-9HcyvzZ}zL(izAn{+NoGzS=?aHY2Yb4%{|pHrD=s~$~J z$C$%jz$~dRUdma0iyF0fz;_b~!KBC^i>t_D6Nq%P6m7>wkN2jy6A; zJ4VJ`j|QuG z=aYjrRNXm*>MCb-|IRSb2Xt(Ra=ly6cWV$1zdL2?GW~1eXIX=+uyVDXgYu5b*x|t{ zWRlA8&0+7JHNa@3^B?V)S54%y2zKn@2aFhAXX>7+!<~O3J$(qRfnYFqW`ZQSS$BvI zS4flCbG$gt=Lt7XwJ_F}Hb9zR|+^UfB*>nF2oc zoHZwt=-^o|SUps`cx9GcZ_o9{YTeD}_9(-a?R1zhzgl7r-@~h?38RNS2Wnqs(093~ zAoQ`UILOHCN*#IX))1eoJY8isa1C3Y_P<4FO9vbR;7+y3LlIe^eoh-lmVDpZ%a@W^ zp+vWp*S$M_l`(|@6J$?|XZ~c6p&~N@%|u3L078iYvMtPG9ihi6wQIY*>!IHN-+wCP z*Q@{E5Imac-ZJEs1wA)$c_i04;SBLw3*lvHw0!GovLg(mL!KGVgMIw0fZ6UivCB|y zy-BC-7kr56mw~ea!%^P`4X7Ro-5~Ww^E4!g=4Sw8Y@(QKuNptng6B$y75fMNS#zk= zSn^+LXc^CGTMj{Rgv8FG4I4YE5!|}oZbEd@)h>U9FgB&!WsUNVEULu@8wVO}cf>s7 z==;w+VMp^o=i@ikb{92`cTau&4yG7Imi8NWwmO_zSOG5B#}<>xR>v-Q)r}9FrVwoA z8B3JU9C_tBUWh@!pb7rivftL04c?L587n$q4G*j6+{PL=m{_DDyX@iH60u8u|I2JM zng1T9ak~#h?tg5^TsKRtF^maeIyg>C_esPJxuZ^ zwE3-j9w<6INjQ}J9-ijnYnG>KoL}&OgEw%A?u}y99H)ezjzBK$L}@O{jd)k>F*#nP z$3w-BRk!L4ykvyZ?xhGf%`k9N5YNA6>Clcja_ueys0_}+3G-I2LUNTt`bbxiyZ^Oq z8UC;P=DBCsb(iCwMvT)pdw;9wtl;=9J3XJwOSYNSy&PZKOQup1S zq5oPd$J=4TCFN~xhp7=4K_4jCZ6e-a_F}&YeuL*g7iAdhDn1vHe<3Hv$w10M!-Vwh zP)QV@8lFZ<_UR`vK>9IOoDZp1KTz}OK5Jj}W_O9(4hJpPSG;TN`COvpCh}@_ z@OQjTXe#sIhfB;wclKjo?FrkdPGv&Z;=&u|6{e@?k5H}V$Y*oIm{I1joo@*3w=ILu zDa56I?z;0j(H2qPut#!C8X`vVdZNJa!X1AMcpWYRAnfjMXzS*i6+?Eq-oknn-bU(G zJW_R}NC)u1KM=1ARP?#n33?4|Kf8(jI;$(=YVrN)SH;GyqIQKX~+Y;f+CZCK6n4M-d)T?T%^B7ryB3$(12ZKf{7 z>AiX4)y)0NT5}TX@yN~VK&SD}-p<1Ls%>jcvB&gABL(E1jN_&5K>q1po8s!Q%3;vp z{SBpn&NHd8D`Pb+`IXGuL`&e+?cDX2^J=H_=&FCovpv4R%t7_MC&n+~>%)_w+=^xdL+2DM`Rn zbg_)OBx=e;e{rt(rbHe!nY&+h^$G;s|1Q^phnIpmkT-s88=n^Jw`&6g`&BRMP#FeJ z^o|_up$O%r_bbL=8Ee*TNg8~9fyR!ep(4Eo3Z34^^0Qdy)9+&--IzZhU0kY8l}^oq z&nUYNZ~39)(CxRiOku?v=-q1L%6CqMX%ODJn*jj}7k z`=iM&0~5pjF-{Ie>|r7aa&zGRwzy{~6VK8xE&lL13w>l>gE?`QGFm4&<5P{MI=CxyYk=e_Fr)` zZaW$)+R)KF61Tw3vfv%C{+F^+0Pd<1nH!Fao+w2#jM+2kuCALN?o(VXj`jJ*E&f-h z&^e04&0FWjvzr}h2uB9Vs4!5SZQf!^c^Uf%bVF`>$slIVQv*a{Ai&sBh&vrSUtd^a zQ*6||8cVzJxabM&X?@MT_XxP<4Q1(~Hq*q9C-;e1kGUhYpEKUD)m^q2c@dt_qW2v< z)=Q3rOypN&5SO3Rx>u?-``kAe!KFX9LPkmj4%};T0(r;r9a1gX?Vz;iU{w99-PVn! z<=$VaXz%M^VfD|!JVNW!s!wnxXV$R4zq4D8#uhN&fPXO+(wHgDEWi8V^G<>dZ#p0E zAAoTHHrgrRHnI*jFH5=}+bO$dPF#lLD1h^G=RLF4?3S38q?2gr6JFfBYSr;Dg<5* z;MA*rx2Uw4y_GUFi?)6PR*3;+#`JXJBSo8aFk($iK7H51wU~J|CwyKKSqyCh55FS& zAesy_#u2qX#)3kyEffER?}}6Qmgz2i!bOb%&4%`Ar&)4epl8tBLdT3P3AlT+%x|!4 z^R0Ku1u}_p>4`)AB0^m#br%>9dJ?>~suc9RI*s@S{e=iXMIa;VW;CtecGf9Wlu=to`zoA08_~M|CF$1w-*u!s6fgC1i~Mqd zhY*}wd|)GKMYKuSo=Rif&ER%WbqZM+YBd|+IXTx_vR7^Mcy1SxHUQTw5BSCewrZ6( z{S)$DDmzH<>}10LCCurM*W?joe#-PA6Z*g%<$Nkl<+|*$w3w|pEz=uipb7+yOt#HR zGSR3N&CXYY5}A6{0;p9?E#8qKElY@~h&8z+tp5BNJ;Zca_ors?0Apx?*oro$C_!v*{sxM^9}~G$F>~$m z3qiPrWGA`{EEB3j}2+_LcF6+E;T!mWeuNJFP9=7&%E)EjrWtOJ<3o^_g{Kk7t)tBk(^+F?U3I}i~s}+jN7pk%_k>ms&A+naQF9|`5DPOF(& z%Q#T{OtaiLU=mEvt>4g1rN(gQwZBL@Dw#jj^-D^TYIkv>?34ly*mYweug^eYN!S*7 zEJ@kC9IaZV5WoQDwZpu~-FL&U;Z)^qPr6x1=)=n(+dtR4QvAH!o$}nfEoJOYWj{BN zI2G5bbYX4fv*A0T_sL|A(5*(5to!9m#o$%vJz(#)uMPP3Uv=1_vS{fu0$o6-NQ@LVrC47 zJN#opq=sF`#CyK?yNaD$<9+$D!bo8Wt!*`Z>x9*m{_`4MCFCvMZM~I?I7h)=D3y9H zb^sb?CbS(mOB4crtGn24N6~n90_7|R-rJmf4Ue5@x8Z$A*i7D<072i9wcL@(^IB+T@Tt!gsO-U@EGiMKf=2jT$IckPs(29og_(dz$lJh!FVJNf-N_8a2P$R z28iO7h10eG$!ZcQBs?95hgs4t7F;(JY<*6~+}X(>Da%Y+K}|oj?o`DJVYvp)5~#@( z|A`+hxsN2DTt3e`;T8X)y29}4!DjD_N;tz;*jJ^QlZK4Dojk{|t23{IcYgcAcG`@C zD##<>at!_%`R+A;{mtEr!*c$5?QRXi|C}Vu?uV<~$1}>bd+ib&Fji!=hVU9iY$``z z{#g6|)=FKZE)lz%1*(JMxijZy|J&srMv$dnmYnvP{aVAyGW;TPZqcOA;O6 z5b`?trb=O*CrIb4jYZjIE1J)}p!X5i@q}J}Nf{!p@6Lx{8&0so$t*_qpsOeXjT)7;YG63vTF#QEzaE(eHPygvU5lpB-{rH9bg+uwd2qzwx` z8;74TQwiBRgNSelF1TG`m)a1#7Pm=~kHRB#>0;T{Nyc=0g~5qSV{EKvx&L@krR33OTC(Tlu@ya0mDF4Y8$Q%WdUp*j*T zEL}l+)1ob*Y2rbg&A==VJ5c;1=>o`|5SOtJ2Z3sltiJlArje(H$<@+_>P_RuOWJrr z3GUSPIVd*BB`%}HdyV!SaIrDRW7NJ6m!KNc`nZYyCpSG;=SO}H80ywZ)1!UB_@Lrx z57j+?c~YQ4Q?BERo(}&)#4XNbgpqZ>0nTd9UIhZvNJr>?*A}1$mdAqsdNE>an7rH8 zJO)Ir66dY#&lZM?*u-e84C;qqgiYeje{n|Zj26}rsEH#!et`7&t4+Uj{}6K|66feM^{`rRMY07uo4)j46u28?zUb% zW54!L@BG^>7Zo$ZaCA_;>jfbz(9WM37Vv1~0A)<6R)pY;2>*Mtc2%OspZ3=hp`e9h zAsqo0Sr)lxKBN}43vctpu zRgl_hW7qfo;>s}C$exVDMlsfxGCW|3A%|qGaU5iAzL{_C83>wCS!JM`nXFeN`bca>)+L5dE$+=bv|CsKeQmu-kv`9vX(|%(p8FH-hCr z4PEP+4KBtG1bVWijc+Yv8x*ErBO)bJXk0Z++Q@eHcbxt|n!bW9&a`P3cMT*s>_CFs z;10oqy98%&cXuaP2*KThySqCC3GQxTaF_G!yWjZ*1K0H2-Bn#(6=rtmZp{we4Fs9R zt)k1hA}#LXYu3axlny-q{7wWJs$kZA-f%(@7&B(>?2&V=CAs{xK1lj)y)L5dFU&vm z7@!r9Sg7-{mRhQ5u2ufW^uq(gG-C-J#v8iXwM!p zI?HH0n0oe;%zbaeNY_OT)6BmJZLtRaUS-ohW9uoa%;wQP>ZVSxDasywpOi|~TFtkN z&R@55=(_jB^TM*u;R>mjQ8by2uDgOWR$p$AFy(kO7eBTT{-@9yl4zO#gJzYTqnJMH-#EoEDnX2y+p_U_=9%fjebOx}y?p~h3N)mGKh&!34b6cY2Dp?N(*}&szY#U^6rNDWNJ8cZ3DJR^j{-hl13ECD z?Rd$Jf)Rx=plOv+{bam${EjWKI(0*DS^PHEPn~`Bq-#)D2`Aqz)EFtSJB6>(_A;g@ zKexS-9WpE(Fyy*TUg@l!i%qQv`1h2?Xww&4EI7!KEpRzABZsMJ6|nX9O4;7N)8_4u z1+DBb8i{xm3(uzU%=Xm#*vr3ACM>$-#-(!GZaMb_d*U>Ab~&*;#(Z%AD1BSUtL#Pz zN)21dPZ*7Bm^G|Peo?L%-3=8ujFcCz+A{6wOlVOl<)CoyqH3q%KDX`^b=vhmRg*|4 z;_Guq2mTubLT`l|cZt~K!WRZ^1OQN>R-MjhO3|qOEwv-;-9b65~_%b1INp?m2wkkK8l+w1@N#5A0kSJaFLi5 z!ZkCLbIgkFR*5gHY41mlj*naYS^IWzHkJ77CZc_;>)7YXhAIzQH2BQn*JHeuhv_cs zC-+C8cho~B7aqLnvYlUR{Oq$_I=iY$_v7)YoY@l~)@mPri+saX-`mH;fXEM<^*5DH zUqIudg4r=I7KA?LzZg98w{C<(4pb!7?WwgA%-=yU7te<$@Fo8GLSs-l_lGhkv;_nM z43Prd2Q7l$E~7@f?j0*h`i%Ywf%zn+nCmL_6$63Uc=n}= zkpQct_^$nd@RY{wK25Zy#35p--X-PLCn<4TxEtH@{)_OOqNkxU`&_!~o;q7X8-#q^ z*MMZ|>{fV81|uGP3uLngna&sE0LD)_W@tQZc&|}Fx0#?b0F$jTGKBVnsO_w-Z-7X6 zKv2Xb^@0Oue;ji<4lCi2i1eWZkZ_;urDE@!ccYzvLge|XElext)r5)zh49z2$x~f8 zD+D9);UK3T@1n3|K&)kJeINw8lkpud)7&3oAK_R=17&r_YusCS*Crh%skp)Nub>ZR zB`!S}NXqvXEigqlP-A11R56Z(-nNq5bs9pjW9+;~YQYR5J5lMf)*PG*+kVk1a=MDZ zST7@+T0F($z2?b(&FUy(gW>@>@&a0C+3@~{HM`Xdj!`rhZ>|{XM)jWq;*ex4V}!nyLW0?`{fkkGiJF4m_e8A;{x~zDjLM7T51u~ z+laC-C|TPD<;KCuZA6zif5)Ba*KV^Ce$|m~Y7h+Sa%Jae6Hirq75lXG@;7g7qwOfS zdF)MnT#z<&(`mE`kq=>ho`f5Yf>HP2?i56+ohpZ zmbYQ$Te0Gt^;o7k_R>!5=>8Y1hw$8isj-ujnbH zweDU)aW2zp-|EWkIc zI*i=!0Yi;Sg_pIYQe&6=Z$Lnn{gQv>-~aJL_JkkLt3;o*-D%waG!b8Kc7<6S z1WDL@E3cO5Tk1C`p^eho)un)MqILl}0N2hTas_d}3zS6*WIdlS24RNNnT2pY553Yu z+QJM1OVmHat;3%>z8K0QnSUrJ4<Aiv24Q?WlS8wxgY&iAS7&W9!wsOzpYqb`ZmLDB@Zk+-jM&r^2JEP_kdcRpz7q= zS~LDtNY78zzJ4NJzey_iye+ z&n6|;gnoT%EQS6qd{iCk*##E}AxzD~iorJ`!g`mOy1VWpEcrf78IQXpy2gPKe#*88J86QXLzufDR{OX}mINY|jV?dHeM|LA@YS{L0b!l1(cBWF zr`F2gnr6T8d#1Kh!$m27jm%!I@P%#_Mvn=erlsT`MZtv1{gxb?7+l1Uw2)IG)gg$^ zFrkide4Ar_YhX?d4rTm^FV>l35Zz+`P7l-_fqWDfMXf@hDUm)=f~1(k;r%R#+MRT; z7`r(nhpNl@c>Axc!NX?PLqEa!f1QjjT=?fhd)PTLMf&xf=(*4hg=!v;Yx}W|B#hRy zrXcy`;Tad1y&Z81%Kl~5hns~0N4x~s0z%Ky37RDvr=fl3D>RM}bzp(H1Vub3agocC zWZms8)Chvcm(fF*%PDosFv;f{F1T?MP!OzP)GXBk!RBgANs2drL;U0a%m&-El0|YE z#knDs&}Z;HF@Doqknfj^cfNHV%S?p95s-8QLbKF~mr9-n9cU4fwHArGE1M7?0x(=9 z*wj;mKfZv{6u0tcZ_?LIVQ+mqPM??rLHAwBhl7zIr{UD}A*OY05?>h|=N_N$bK9_) z7d4z1l#IrxFqDrcoyf1_L$%WGr}t(3|3a>k9)OU3IU(r!>#P=Uz}6p&n7>4`Y3jaP z!qm=4HBo0wQ}O|~I0l3-atIkJKtFIYj8CMgF}^V+YAOFf2Wh!#)|THc+85W6;KF?M z6;t^c#g$!TE~Hz9|JLsAoD3m93fj9)Q_gfvfrh@JsoCW7d*pTv%RCT^^_8Jvn-<9I zY17jbulQ@xq-mI$<8Fp}#}cWPa1=FG-|v#zuzRV;>ZT2R${HO$;Y;X$Mx}l~X=ylZ zrGgHl%#reO8;9TCGo{|>{nk$};@%cgk)J|$yWdkxiY%#&2Ye{m`bAMA)fwJznesZ| zu~YK(kJoVwekW21zf;g%hee2z@G0e1@HA1s0s&LAxPs|FA>9+{T<*LQ1Z-lb|8%Ff zK4*4a@OPfK`QLZk6)T$Uo&Cp2m)?gP-^Zf~D+V}E%E=SvdBO;Cg7VVi^$Bcy$(qf( zY3|6`>Q=4s^f3hg&`hI|s_X&@S(D|JI@xLGc??O>_;68Q!9*P*9R$y{@*B2!`gtAM zr9{t>DJ`4w{m(EezCq0$Go#5+OpV z1()ms7?VhqRH$2xE>`LFxV-!ZzCAD(F)4W3*|Nj7uN)K-YFoLs1nDWp z?hpGPqFGz_=j^6PBbaeVbqY@k;J)Kl?vJ{82S3%|bWR=q37@cGgBar&YHFUi-nAl& z7es=$J^>5efZsZUHN*^4jot0`e-i8!zWP!Q&_JE!r)o=N+4PL^Zxup?Z@h;4bb2Tm zGu>(gvj(0t50nfC95^)!(6y4+677jQHT(IFj;?C@IAehzp*?x$#zTN*>;{~1OAcoi{96f1rh0pl~$4= zpNeeXu)BAs-Nl%*oJOPUSana^rDPZ|q{dl*)VyWH#S-b)@3o z%Y`b3Lcmi2+BVfQ68VqVV21?BZ;{0Vd6D+6xh$bS7^xost|y*xv+{NHk30+nMPOd$ z#=5hdf$pQ-gh0TVd|VCwx?CNR=u-t7#9~!g#?sg^+RV)&hB*I;;owK=SJQWGRiCu# z_DnY%@4SDZiH+)JpyE`I_)%&VU^t_N0U@eH38x96lcXP_iu;Ze`KfYpYV|gWL`%2s?I3Na?>sYuXr=a zOAYXW_8l|fy*TCYuVx)iCEUgl?eU=pUUi4O$)s|wTaF!G3woY)0_=n0e?}P@pv~!m zDd}B#1vo7-rV(V;I!9u_DOlXiNi$QOo+|kf#B9I>#-M_Goe5bOM(bidyjEgKt3NK4 zT;Vvjt8$Nff>_tYbT|}3z9u5gDB@6SjaHte5NGN_qJ zotC1VoH?{1Sd9y=;EOycZ=ng4Zt%ITCycFsSVeQcF>PH)Ts-8YRq2mb?9^B<tvxH+Uh)!Dyh~M zX(WERk)e=qJXqA08j${4e<(0SyuB4#{|)2uRYS6PccU=$&SM+@3@N77le@j}XsBYu zh#c-Vb7RW6fm4I!(zzSs13ZU4aRBE(trm<}pNX7`(9L{S0hh@1Of;AOuLVfO^4ry< z6Mh9VP63!dt%e)G{Kx0$ey2mPS5VR^f4DQ@h?rw62bB2ay=u#<@q(&+MJ`8wS}BbN z=~hxX|IWkX8oyq3vq^Az5ycZlPF&Ie|41*vAhHTdLCPsIkIleCX`)#DUCO^}vfh>G zCsp#6O1wl#g~;%^i=y?2MWk1+4i(tX9(HwPFmU546Q8<(rF*qJOI-AHp07;pVJB98 zYh29^dmlUtWQZ=!k-~5SiF>qdZYhm8B^elocf2@}p>_YfB;f9?J zP!VNP;7U8Ik7t7C&H6wz%63usyi9i*O{v_>q3C9dJjU7lhMK2t)#O)Jw6_&_kvyv% zQuZN2?d8#O1{ULsUt|!5)ei5tH6wl)pL%_6Bq@wQLe*CF2W;Wn0RY+pRpckL9lnYD z0gw=J(s%vciBaVudprF4tiI)HtJ_a{YEb3M;9-uNCu|oT2r_@im8!3^5=Ia;1m%2R zsx_|rk+lU#&$P#j(}>GWFC>atLthtTP9nvp6ytIur;Uq}KZ`3AQ;s9T_*M5ltuz(K zDxYQG*BRZFSE@Yu5^-`XPJ~*h1bw79MFo+HlX3JC2FgMx*z}R`5aOm7(BJ2<7=43p zN*fy9Ni)!_d<|hu%Kz1JeenIz$@q3(3}gCJ&be8d)?69_Z+Br!HmuSbL2exDzUW6M z=7CT6T!nFyeuUeK%)wZ6Kc1s&q#-V0;c$8~q;;80#TrC;pU9U|r_o-yqejRy6{VXi zZeAc?-g<{xm4J!Kx*G^U+EI<9c5-JRQAARkpK(eBeUEk?sdVn)L^v(TQ_9S@!q4ZIs z5n9PkIT1Z@xs@PRtgZcZZpVAMwUJb_TRslJsa%9#uc0vz>Q&Ez%Mk)EoI1h)t$W}Sdi zK2(($kO#F8^5;aQ0w<0_)Ln07wm$R$N}@#}!|Egl0?pcS9`)JE2dyb1ADyH#fb883 zX$-*Egtwsf0cJH7YoFpbpknJa_FJDzC8g9GK7;%QqyT?Hj%y=jD8>C%Gz>wnCLp$6sz;#||1 zc5P5SP%9^J74=sTNj#IsXzI;0^0^8;ebiCbsF4(PTJENIYeelKhEFy#FUYa^>h7qR zqrG*w4p%q(%;0e5W!K^~e?va|@XW#}onUcy5U`dZt~2oU6@0M+QDC}Nd&JuQHGM*NE7V%-j;ejr*KuTJ3R zdlNF|xzK@|!UpKl6?-y$af2Vw$|)I-QL#!h9r`7pBp5zX56)dNGU!Pc3gl@k^L=;sRV z+rXz@Nvj&oTZN~XEcc|3t8L=*|Kl*;F#--cLJLo68MUexz-&(nWk%q}k!gg)f`0D4Cx7K~b8Hv`^S_BtuchOIcP6bFld2K6fZ88eE4a zy4_OYztDBd?!WJU-q!iN_ZZ2t{l6{91yiH}?)_he=e%P~E61h0I0?+mDO~833saK}5~+LX6Y=4ER72DpkGAb$h=rbmB4!llb+6B3OZh z6&FtS0x3j!HK=9udJ#eqN-7A~cG)&Z$nwkcKT80JMu+}E_E0_;N4q}^oIo2+_{-1+ z#sJwpHLwP=JTjqgZToitdDOXiC++F-8(Fr@DP{m!_Sfe$AU5yqnEp}`@ecto&w}of zBFl_b&v7jE+YMHZ`D#Qc&=$BJc9%08s$BQ?@voUbeyG66bx3$Gx|w+f04v+&CqNf_ z4EA%US^D;Z?QDsDz~*4d4n~L_&_ZQ!P!O!E{76~Hzas1+H79I-;iIG8Y_)^KUfK)B z+CH(Plm&o1A0*ro=Nyq*B#sF1>7V+LX^ET+8%V$%WBm z84d?FBCSaqK8R-8UqDVMp-}|qgB+|~fnXCeJ>HL3oQI;@IP7AKa>f(kVvMW(>ky4k zzgn)Cty=pgN%-i*!@hgk&AP!H1}P)++h3P+5O2?5U45WEdr_)^U&z~*4`}~{gU|~e zy1$g_ynM~RH_?RtHyCO3$02l<^+K(_51Obwh<$UR^ZLw?A!F`cn?9C^qHg1_0A|~_ z5sRc&!yWv2sA_DrU#EHou_)(h!0;}d(ooas$!y_Df4)1**}!-5DYIs=!(gQK1$+V& zP}?oK@D^o9SHBi|lIAjWvvNqPL70EAkTEe!T1XbHF%W@>3aTBxdy;C{r{_;b8qO0{Ug!FMRL~3BLjTkmvLCPq|kKw z;>GFEQ$5#D;lt`c68g9oRMh-~Z99L4_Od8oQzOQhq8H~BK^9fsKAWwT*5Q_rc=+>H z^%RE@&0dZS)-)+GxewLSc%TbSEP7mpC?nRN5ni2MH9ggb*iTw;$`)LanLf8DX*qC{ z7X!W-ucb_Bd#x)^k|6Q^Vc$P+WSW4c7hxg{g8`l$5=};@|8~zIircCM>^zW8jrTK< zMLMF^Bg7RyRl^6Q5@^2@b)I1$eoY}X4(H>n zK?j^{*2xrrk;gVIkZmwlTT>(P?@ilx;LZyBq@V7+I0^P%^$3+Q3I?do3gUJ&^pdr! zJi4d&j+0^Y^-f0K&Ra`UxZRI{%MUBLTtR}$v;hR zQgExv+kWMOC9IVn3)XydckAh>p{O~!IQHi*>Ie3+?|28~#j+!=^{>!uaMN?6vbgVHavm%bJ|>8Ap(ygS>VOaX(2xHeoReSzCm^E8j2O z?lPNxlSTQE$fVzLwIzopSyLsHDC}m&hjOO8$*fd{MsH6-EE!QPec(;%aP2HhU<O))sy;W41dp(X z{fy^LJRPeDzD4SeRpj)W@-}#o-FSOodCrs7@k=7wC6st$-xC=Dz*eEr>^bE4Nc1B@ zzW+vjXUvne>xx{fMU7(}$2u9g50y-L#mrDI|4X2bo`6xe0Mc1%GmcihlQg`nUS!Yp z1sy{E6mN|Ev`nH5NSnG`@k*Je063-BO(?Q^-ii0rujUSs2o*9>XxW+h5ZmI6)ZbBO zm*CvPl}AdM+Aj~l6s@gOg$D$!xprLr8*{TeWvvEqQEZ4MKDOy5d5_02mO>=^tq+D7 zEzg6QZ*gnpWw)~qmza1%6BSRCk#uNI;1~J{ZCpS^l7TG4sAf*ueu$;J5^|YvNk*TwT8jHgl5pgOP17aVCa5@(Mrpgd02-Ed1YVGvAX8_jV}%APVTyem_R)7 z!1Z&cjDa!RTaZI`rTL?Z#Ws?Mo#|pgc;{VzK(`WF+|{@iV=A4Iazv15v-e;73Y=sU z(Sz2mC(DaSjlvBQ=DTKzCm+^* zahG(X{A;mqw?a9LUU7s7?HAuOz~8sZ5dDrQ!dL#2-^cW1!2!c^93*z5j1_VGcU4sU z-2#K+81S-GJ>o&JuyvPr*X~848wKedMHerGdSxF_tg6-o?xmILZnn7RIB+&G$q&IX-}D+wethN7jAR- zpWiJe#aLB)x*Hk|o@w41vtP!9UjO_DyE@-rgj1OY`ak`oOlbd;dVrInV|tO{aWx8F z@CwkeCI_1*JiddIwI+wysBZo1es7E!0Mfn^YITVZ7+aq))78x5M8pEzosZ+)K141^ ztQ^#NUn2Ndky4dbTR`3ef6p@7FX1U;)IWF9E$px=Md;OyqYc`#!ru5sfD-%U6dH8& zR_t0Ms1cIR4in3Zx8v)_+@4A^_){RXJB)T-I#9x=DyflE!*2UsIDW+!TzRae{*MIe zOvW{!tamts&bf-Iq>g|6ocE0vRf6)KFh0I@PU_X<%F5}s8zp4$vhtkdan*<;y*IZ7 z7$h`bME#$=gjBxuH_GbT&)z8KI{t~Z>{#wTrT?Mz3-q_uxHo(MwXJ9VaqT~718^Cm z(g@_q=s-iYoCAJ4WhuIPLtS!$J=~8)z2UP|LyH$% zUWFkc|Fi?f`t%8CzAhb|V{nx-jeQaFT(x@HYU@W>j5Cd9QqgW3lb*ZZy=vLEC( zbsMF)Bi20or-H}ModuSqTv5;d+6m@vqh)@yVP1YeIXLx?D=!|Z01G>0G>LN82Ckd^ zV4|e!{)9kyYh&jT&Vg|7ua~OytHD#9d~4{tQ{FPQ$v%r|f7ed|1dG!fvuUT{x{V7S z8PwgB&+q0*>88_Af_~(2vW6WFctw<3sRW5d|HSdgAm|r{(#sg2DUON4FrSQ4p4T(Z zul_o5nnNg`{4q^=-`&G!TI8i zPcO}1qn*^7xeCAeBA1;XbZI`=ZhS{eUt2Fzoi?er zad`u|%fOYAz!|dQj%q{%hlYCA0t4a+BZYAh<(V#&ggHJPklOP+iW}ezvU$u!#rI|% z$Aq6r51@@t(#%9vjw^)5Z7J9X6L^XTIG+-(%0V-?#?>`~X5Xl_J?qvzN`bow9aY1` zQP7<@oc%scY>jh8=Y7Gd9L!%oK;_juKMC}5d%G5v@Y$6o;&LJf=+CjDmL_lZkx~Aa z)FQyAFdyT%b*-#ORD!42C`$9$!f!eH8PJ!tLAx2>D}#o_3E*h_U*wE6_Oy^DfeEP} zc`S7Q&OT{S3rR?~vH-^eAdS@SE&LkyB|98#*&(OV?{hyOYK!jY0(TH5KN71duck24 z{H}(Y<=p>wHP-Y`C;t89dp~%s5S_^Q1?v^LHSz@+EF0A~5Y0LxL%7M4Up5<9GJCx1 z&yTKCZVUx~sHP`eOZPKF!5AT1(Xt zY`eXsTnI32*u|B|a82{N6!u8o6B|2@+PYHNy2gth)2={`a^pU#@a&F@EjU^yg3X1} z#&aIjqC;_NEZ+p(s`|xaBi~USObS`6Z+WJs4p3hg|KQ#?C#XmvsJRXUQFC7fGfUWa zYl&@r{VP|)SGUQmJKQs=>oyq#MjVVFg%>`*DE|%2$u<8n(w(2qul!G^n6k;R-%i)K z+~0AxjHCwfkB5m9Fv8!Z$c^a-@o$U);V}A}G{iAcDX50jws?o$HNdPZcI;u_q9naq zaQXUdqEOX|nsGG@{PMAB6ru(e`0k^ypBam}B!x1v1a4_A z8C_49yH>J2sTdMC{sGRs$rsWU9|b9;NmBdAUQg%lx{*&3odHqMjm76JLUhcnwXHyr z&5jci4gYKjVDVyu-|h^w$iP3^u%G~G=e}<5*J`~5#u%|<-|d=r;EC6=GwE(L{~oS| z^mWwm^B$)f56OjN#n#J~v*Gl;-a55d&)uyz6Q~}J*xq0|cMJK51>zj4+W zBhE;Kd;p8O3G+_0_Pbm}21>*~*!+(CJ#NwE5#j;=P~iL%Xo5Ntfx}PM#ivk>&lvpq z9E1?)DFr)Z$aoPOQrHP~zFk*um``(#9SH}QPTHbIMw~e>-+9F(xhg)*m*_Rzs!VYX z-oN?3v0h3pJKCT(8STnm36eL<@{=6~^nSqf;ow1}z+%39bod>ft?$`M%KL??wR^UH zC-dQY>bYU#GF|xPhuv&NiCipeOhRFG5Y`vU=4Yk~5;G@=cglS&THUllc1u`3z4vN+!>53*cR z%7QL6ECraO2Bv#<$?mMdQKIz|?Qmx@4L?Et+8B$yR0!~E>X4`I1j|}#!J40( zyQEPl5SeIw)62BMop@#r%ei?ixmw|S6qno2?@`(W9Jnzsz%vE7@-g>@Vu3WgS3cU7 zlpOK0(y+mwpItmQ9nSK{-oK1DaF(KATZ2u*gzV+k9g`-P7zv$KCskb5DbrnZoRnE3 zel-DkM4-cJL-S+w+ytBI{7B?QO7RM^j;p$jKM$eF{!Mlbe>C;eN!`lgZw+y4%Y1u(p@s->-9=V+-GF(0LrV(*K2{D3>yY3yD9t8Q-Kb z*Y@PFe>2VV0qq%tA(L-0|J5>4XM2^xvdEm|Pki9MS*XxU@!4$YfF80ZD0))MJaQE!10QQI4d_8 zBo5->9caM%>bVe;G)J%wh{wi5n8Xt#)o>9{b4!IoufB1S=*_}UHdK#2+K3)lLDW)e zbye}cb~(JKRQ*QaFUX6K;fw%{T-WIhd^|4(W=!?gt_xgAXvz4?NfAGX6X~oiFBF_uuSRDjFZZS*gO<+|&T-Ou?Y? z;>Po%`ab?Cn~D1U5t{lVSz>te)T_)%@4bF)=5B$B?5>#E#2lRhmD6sg?tBhPnGF9< za#V#DSXmaiNoV#m7>{4(LX0%AuGE{nIs)T>)d>B_@KYH+62+paDe}i+p_V_*Fl2(H zby4HCrip{TybPI5OsoCVSl;SP;QzJPi^M?6ynPIh|}d zW)^|@rP^rq{l>=8@G*E%P5wZ|j6$}tQLBgMxSR1WXiJ6xQl3WWJY%El$Y1RdSZ+^4%S6zxHM_(~ z`N>n!T??JdDEb-~oAiT$Az~Ly|*^3e}(F&~)dtgnX-<>3+98Jd>2EiH95%zOyCqPhgu2qohOFr#6E< z70^i}``uc~%slNMK*xk>L3+)Pgk1b}!Y%hh&={x9OE0_hx2laRC!_ z(LT8v_8Ggn@ka;Oa&c?*SG8>EuF;Wp*q0v?1a8P}JPtMt!EvOQ{WEBAq@rdhs9p!k z^;_yF^5*Agi+Fsp&7HI^1k@tT6J zB~M~HrZNg@ui_6Wdl2oe`dca@P=aRz`&UD`!-ALK=&LZfSCdoQIJPgwJ6JD2Z$JOm&6Pkxj zJh{_(!@uEiH3h_Y-95gI**5;s(6Md!Qx*J}cc934{)EslZb~)CeVvh|bHL0XW^QMY z_ zW1s$@Ms-!CF373gQYS7$SwarOk*2Ge7d;~8jnG>#>3lnAfN|P;I#ro5>)^_B_ka?q z*6t#%z22=88Ex1;tPV%0uZ8M9-~4F-kWh6vE+$N+VVF zsH~!Q1&fQ0#Cdw7BNWlL##pz3J@dEB^uOE)|LbWC^f_DjfC?eEn}??L zdj{tOVJDlD*O)CN?Dj6wmPo2%K6Y6|Wg=~~c>17x_T~uk#Mr|Qi8EJ9D~j*yyh(d9 z+g&0Cv9*``9%IWIb;ygkhXSx#txDb2)bAOA`uYH_y%7jH;hY+Qj*? z<4KH4ZNI(pU8X039bu6uXge;nG_5oZ-VhxKGl!Op=sT|V9r{X`O(^IpoNUHiM}@-r z>zs@cu^bJh;cNKGSH=zL1Jet{;^Ks0cn+Fl@_WNumAp(*BCTRxjbonH&7EY%T}}V? zmN3I)RN?wXx}RX6z)pQZ<97Tw)DO|CYK#;kjzog4JOqY0L{LHhq;g-zN|8LHWOgWK zqtIA12Gq@Il>0pCw(+c~(mSN$@ z1(9bm!-e7Sa2pZiQ~&jF--E>jDOG^0W}=yL+B5q&zy!W-=L(S5w32rmBi%ZbN=-bg zUh<4Q^OBZ{5~tC{C)(QR13we6nls%pM$zxca4OPpm!>Xl4s) zR^^nB!u!CPn^xWS$8}5HydbSX#0fekHKjs59TSsX$k8-{2XueF!YZSn6 zh5v1Zq1m))`M6AD1;(%}PVZ%L1v)SMaN|GbJ*48#-Ja5!y&~ff+u(jSBOX84!UQlU zZ+`iy^%eR?ZpkTv*reMQ?^qHSfPj>be?9bL`Dz*~T-=t`%*IN{d`yn`{<=*%;z@<^ z{Ion*#5B50&FAd1g8wbH!Mli9xUy07rD=12D|y}1cE(INbxw)h z`42X?ET~mPjOS)>Q{c{487bguwJEWo>9ppsJQb&9GtY<+ugP&G~vCC~w?j z)qmEpuG~`cDHb|*Wu^KI^2!qt>U~_p|ki^vzKX8uQ8XJbmo?=MjJzxa;c`HnQ*ZklBx*FCH7 z{Yv%Jc*j338O{sMLW#bv`m%Tx=(1JyaX#cny@xY5zdbc31$4-1JDjY_WL66t8qK_0 zsBvf!4sw~~gnmG{9y_TXx_8-KJbYWXY9?=gZ%t^jlozskCGJO(wP34U@Q{$Ix%q8= zA(R$cKGg?BjI5u`_66E(+|)2dGwT`gD~6MNokeJ8+0j^}(?p5y z=#kVRK6pxfs$}}t1p$l!=h+mqWYF|p9d;|-nM3XA;|TGi9!7a)SvKjlpn_?x8yRu= z46jCFefEATNOCX-+C{iq$pw-~JUfZ*t-;<(W5n^ziTtHf^*%`TeV?fjYtgu+x<;TV z8p{;*@B&tCN!&*LT?m2Qza?TguUGImm(ag$Ys&gvEaZH>jH( z%~6x8)YFRiUYIxr@(h)f9D~9QFQ#svO&VeOhE_H^p{DD_!1G&cMe^^cU2*ty_YrzB z!>|-;E%euRJ>K>mCjH;mD5^hLAF(sMyMhe9ahOrfM0p<9$!h8Z+-2zzp7W5OU|1x0 z_go!5aiPVuap;k3Oq=K5qTTKFQ^ts~R(TO>c@?MHQ@H57_;}9guB~eo91X5}XH7}n z8W}>_rx7P>*&ZxdvrYOie6?Dx9!F&CO$bUlYHvrEOfH?FlJnudq@JDRUzn?A=k-Nv z$2pR?z`dMPpcvsz&fSs<1_wQBbN38-)j<4o7YrNI?aP#{DR*>hN5ri9{K53r05cZG zq$4xBc}ctQ)W(DG^Zdr+1k0Vm{~xebIES*ygR$2v-pGfTk&voLo@G^+)2i#kevhmt zWI{d|U1yQDgW+Aqo-IW9?B#N0orSe_3<|*aA`V$Jyx>A{x7St)-BQv zOlrOSD)I_y-ODWa_SZ_PXFRbv<8;KR(F_R_ECx(upwh2ses7{|rqpd*K5*NL1e^g)v# z=1leLHZ1-3NBGLGK`CpqeO(vc)^C53eae9LEn8v>Q~hVC@ip-6NYn3#`9K)^ zm#zU?6n;|ccIE+XqB8*!fp%f-^=JYIuCL)iMv!f9j*wvf`g&pHh?GLD*1WZ0#c{s9 zo|@4nRdLa@kLvGoodyJstVDJ(X%EBryqeFq&ed-l%z$5$5B*=Bg2*1rEMM@ANPVz9 z6wljwPSX;9#TXk@dlmfU&b2wGJW!;+&PAOa-%^d(S~KbM-{o?D`oCHQwdkqX^ZD-_ z9VNT}oZx0c*lzmh--CHyDfgD=2%=IeVnh<4IJ7NKj{?{WSGi9&nCta zKk(33XeIl6Kj$D;T-AiZpWr59n?NzaPim#v6S=hW&RQ7G& z`9n#Ycu$`Eev0AXnQzet+Uwc;K1(UYCG+tRs{0lcV@=Sng8k!QEUU)&T%d8Vq}*c; zi^n7JDLhK08##>7)nevI&Dk3#T zV%!a)xICJ^lEeV>PW*+XgI@S=_7eXOchdA~lJEiRr8FB3rx|1XR%<)dnE*UbPeL{t~ zMIGz2_yGw@KwCO_92k{vb+LGP)&G4Z(X&3jU@2SQ+;dz|L3+paAN6?MD>Ub zy7-)FImoW`Bxw>qcu3`3rHQixVfc8EDr#mYLGd*ppo^N)gd#>hM{R#56#PqN`^9O0 z;wXt_0-MyQ2*lWtFhNB}x!QDjBYfV{|8>2LczbPclWq3KPt~-dq&S;hLN2O^RY~v->TunqNaKKC8eLP zIy7_5D3~Num+d$VFF_BHi$DSWy6$pq`MM<%em~K;4Uyg@A&@y7Oj}pPR1~Kv*Da%ix_iCij%*C1m2< zR3lk3Quc0TRf+&DoDlowxSv}f7<6^U=?41M{kp+((Q<};;N4sd3E(`>`z>OMs{0db zx>;^91#ys2D~26#dK?UJ=j*1`dz<1V6p)J*zDDNnx}33_t7!BZX~2YOeAHUV_I=8{ zxytt2x#%qKVt*Qjr1$Vr6KGe&`!mG~NbkjPSOWc{d|$JRG|wIbnYCNjWwa%;@LlGC z3Ca10c@anToKxc2RK;(-;)X%W0b;jL{YaauAKINWMn%Q$t1AUi(@ZxeEbnMiOJiq-irPLX` zU<^&qDR^0b81{Dmfo?+aP(3M7vmm~M=&{Kdswm$?adCYVJZzigp<$jm_tZTTD)t*? zV5pIG(Ho(7T}Ga*Speox1g1@BlHBkIwk9V$mV&LiX<%^Xl%a@7z>@~X_qme4+Y)jJ zBvPvdUnoWodH5tAH|~an?{0v!y>-8*T8w}BYJeuf(|A)l2v>2~DEhr5yPBm-@_Gsn z`4;h|C%CQq)kVa?YB@ymAYy&GRrKZu?g_ZX2bVCYE9m)~3l^BQDU5wlmo3E}i+Cy7 zmvt}KJQ|m8fyw8jmoez%F&bPLa>6&aaWD>HTE$AGZ_OI))NIPeWZ8DZ?i+roJ@h=f zcAYWej*dW49c7dc$FgQxSU6Rc^qNIlQS+YmoS&zaT*OY|`kvue*`Pj1_ywfZZrCK* zT9(%XGZAjITcZ{h*H2SPFctudVZCr7BuOT6{R%fu5dA1TIE*7u7Eywg@l_zBjhbBC zehR-wwQ$8-Tk@wf<4nt!c*vg4SIcU*8plcr1VlpKq^JmqWdi@4E0Q23e< za4Z)!eoHAnS61Uy^4g&;t>}hr>dQLV8=q}2cDva(Vg=>a-4^{nrrt6v3at$nra>C1 zkrYrG1nKUOknRR)>2B!;k!}QrZWy|T7Lo24Kw{{jJHFX_zvnyWH*@i0t~JlP*IiFN zj}afb*H$nd$<^}~1)zaju2KF1>83mDMu9bT>y{0ISi_?=;g zN}dTFOY z9D6$_is_N@!1XDgLM$EE^QBv~&~mAv0)n{9z~nCKz`tuZ&*zmrp)LQOAm61cnPI-X z&0#wRW6dY<&jWsY;}67La^5Z_KG0h2W-$7Xq>T>-hn%wW^Is3Em2&2VPB33zvR2nm zyh?IB_Z${wpi-QxcJDcA^5_UWp;XZsae}>rF&b@Q5=WJCg8vk_lMYJMEnwyL^Z)1? z_8>=a7Z1yJ`BGgOg5;puGaup(;;o|Id}|0%DM}E@`;3Vgxur`?wMPNxoSvRIR1La@ zNQ_N?%ebpLClf-MNwmt4iQ3Q8FPHZ{s4TcSibV0gEt|IVGo z1rzU=(C5(nr}mdX+w(OBrfyFW09q~j?62mfXnsgI~ zG=Q?MX+LYyj}~M$yFsvCWio*`EN?ThduVl{Aw(Y;^t^xE2)c`l9D4aoMD7fT#uRvot#dS~byN7lOQE z%EUAbe9qdRBgB~RcBfW=XM(4I0A9t&x>)3i10|3j9J&8`o8ZQhWAM}T6Cx=nqFmII z#_eltJ=!c4ry}tB+3WdN?Mp=dZPJH;{Y|a4j(=!FZG*0?`{Cr5*r_(B^Up00Q@qb$ zdt;@fan@Wdb~|yA1v}D`6kP|J$^9IAx~!hAqA$0gew)CnfKkjphwW1)f0&_FZB$<$ z@RoBtW6N^Y%^kIZcy))mTv0-J&RZ2{tB0p*sr+E#9!`ck=b%@L8Xa4xS$x0&pE;Q@ zgi4=diR`mjL5}v`a-R9Xq(6MFabwCDt(6}2?zv^&P=N(W%q&OuyH$brY3gU`{F2BV zCevsX%SSp%hX@wLoCe{zisQ5t>V@Pc8|DJ1xFrB4E{Y-V6neJNzM>;HJ{{Uu&!snwErma3nDbt4T_l2mQY~A2gP8B9h~6#mvZL3ddl}X ztYua1Zi$MMl7An5~ee6fU z-|HjVn;oSh7sqfdW}Nz{tPwTlqH?4>T=WX=!l%yC{$LHG*|f=UDT=nYPMV0DpF|tv zcdnYY+9aMG3vu6uTr~S6?*2^V9g8~p+iQ`lr)D5=9Smj~F1WP9)5emL&skA`)3Ta< zLCLb( z0SL|&^0!ffMIJweTFI}4KQOET-EZQ99uGxtuuQxka$hd&UGSX#Bb?`|sXfBbUd)Pm zJhS_V7a2%Db@H4nu(j@=CcBO85=Trg1E65m@SC^ChydU~qlNtgJGZn06%Bs-8Lh2MMSSxAnPzW4pS6E7NA zd7Kr*Xi%V~V9yWThbymH_3$c9)M~F1d~Ag(wFoLL9hB0FU>ey3Ag6K5l^rL_Vrf}u zUcvn6hM-K-lvBxAYaL}FVszITlHl^&+$;`#<7M!LTu)@ zb@|JFoZ;W#fVjZ+0%rNW@oBkUGq*MW3y)xpBP|jBo_B3djq2Ng4?*$ISTwJLw>4#h zFz8x*@NA@BxD$k%!rQPW4l~{65W$CPMc;m4p}=ZK!y`W)&UcR^2xmP$gPOK9t*_N0 zDe-oyDo|U8UeS}*1c@q|!LF8#8HeMC?E8aPIL`GUE<#6o9d#|}=pc%+)W5dNoO)y- zvV>?>@x6n#eu$?)YSGiO zi!3(SQnaZ4Cg$+meCucn`loxoF0rThFcYr6{R>lOj>Z?M5r(F_xlc4zrFtT^J|+DLZixd*NF z01j`tT1aE>j*vk-B>MySN|a^mZX^!3p;PJ35j!T$jX5YA?PXJ6fp zZX-!hA7QWieZZNs;v2pzQfs~QYGZ7xOnqb_64}k*O@Mh!=7V5zjw~S8^8G`ZaX^?9 zz@eSJ6Vzc*6TVDbwI{@?{weK`U%Kb~B@6i`Z7hYqB)+3^w+RmYY%%AJq;v3ttNA5C zB=jB26edcvxV!Hh3)jANrgErlScF+Cf?9Iq#cu0a&{i5MU37lR|nj*PVDO`37! zM{2=Pb|p!OBglYo9P{OUMRFh};uHE_SK`wTEMq&Vr zhL|gr<(=i(#2%-XeE>kZIV6a*hxxqS0AHatR*isKcZ zmk6-!YpW2f%Z&5qiSHiWHgk;E64X!{*odauRac@h{jN_CqZv<1via3oi&Hg^7aYIF z(J=C!uGfVR8*_}yDG-w^;L$xxp5|j1yw47Rq(S9vNSgd2ryeD}MJ>N@{8V4ne5P}2 za^614*fk}j=K+&q7TlZcvcb-vpy6$PjW{`hz2^lwelnWh zeL2VZ;31SCJnxx$a8t9O{H<-ju1FrrTj^?ElM1wL@BLbi;WmRPg?XQolEci^7&@Io z>_Nz**K5@K{c{lo>Y~8&s+Y$zG~FW+4!ai$c~N_{^IPxhy&zBI`9X(&dbSJ?*XeDx zd`Ac;4l<8KTKcKhDTf6sx!1F%r^!p(?Mgal%35tW1mRpM8X`(O%$_!9*?b=%{s`%& zdSfbrn}1@7_u0#*^xYGLaY4D6&hPypGi49cQxBUU$6h=Qo!^Ufm+g*1>(zU$9lz_JnWhCgN0CUMAf8j)u*wXCtKETJp)>l)un#P>o8*6pY zP**ExKZw&wcB!@(BG=jRbyMr_fu%7YBYNDU8#)c3N5p37qsA3~DAY)&wdmysTOLGnUp3QZZBHV zQwxMi>|A;BCy2V(VGUw57q~V9y<|ySov>Qez}}5Sy_{aT&weS_L4*uczccNUVc8ru zzTb$_w0%BOxra`M55Bu+qEGPn`H8L&8P!^}eVJTJNrCtzcP~VQXx|xOgVh&;!~V9n zty)KQJlRY|`WOeu_v6CmvGDT@o{zb$OsxkgD)C@2dMu^z{GFh5nGyaa52eU9(EcEJ z`0V+7WDi+xDni6}LXG*A6CQg)N9UHA$W~5ZO8_84Qu|X8w__ybXTd}Vv(w-?(Tmma zgb=uC3I0|b>vCS#wz3U+xCy$h&VTvk1$mhM|GA@kg@YUKK#0IgCeYqO#Q2*lf2SAs zejMlP&aHaPNB|@Kl9-^dcl4dDyywP5-JgZqB!tA8ye7f9n zLAGDbk8`EQDdIb+vD=|zqWf%C^-V+O(*fud=U4{fgp^h8?gcYnH(Z;7$MsNDN-sb2 z3hjI$2&oi5w_*~Hl++SbiTF}yN|SXd<|h|H{TQ12a-s!kfuAYnf3?@>st=t{<7aq5 z_wiw^L8<{Rv3+jS1yljEhsMRO@^oNy(JC&LtpV0>T;XQ(CWGI14#bhx4m$@e9iIph z9?ZDOo<;*R__aagN0Qy6TkHl;b7gn6iRciMM9jbS$Ja(#jD_f1dvtST2YNB0s<$IX zS!M7X36{#aanZSc2k%yT$*qq7*Zl|)x+tsl6<5}2qDvWxif+E;VE@{*> zE_g%9Y7fbcRDnfUVTUnQYGf9oJC?q;$Es`@Clrf)blQg*eEJUNtxejgyx#TeNhcQZ zt5Jr*=ISMQaML@Bp*R+NPo^S7H#d#-fML6X<@{@-piOd)i~^^7)T@ypK7EO@ImYLwT-b`A$d|^W7aH8gI4> zl!L?r4wg28(uMD0S=WT*Eof~R~+a%`uK zrO&2p3$t(cB(mNE~?&Au7{#b zV7(8!x%a9lCuIK;Xe8JP7}>j;0<6oBflY{rN6kk#?fUQpZQ9<6a;xzsyWJmMFNR5} z``I+uyE&4rE!Om7Kd(%9Jum+5P=xc<_B#K#f>gdfZCHywmfz!p%mn;Icy!e)Q%AoO(aToBbf1`&{L#4O7(Y=kaA#ww5tT7`p=1Ok~@F&@^tGZl%iI&0b zXx{zbtdB>v<=HPW==3-veAXa7ZQ`+*|$64QzZI$`wOU_=+ecXbHo@IalbC5 zk2xO5)8JKHaFiDdx3mHm{gNk12~c0pn@c5-{*p+a{RYws!OAfJdP(?N^CgUdI8fpr zMEFuKXV}06zXv5uE94U0@B?~hK(^@#rUbD@L@`mg#;#HPt6G6UJ|&n#oKek7AFdxJ zVZhOWudiM)p>X&Oz;D{Ptf*O@vod4I%2$Mrlp3XAEA7zvxk8_);5MG2vI!J>`#8=9 zZbhBF*{L&1G4yOC=Sr0?4*0#*qV-oRLHj{p^GEw{<{)Lg@hUo~;;!Ickv`i@J)kSV z^UI2~iVNK4>ZXSP;HFKGx#Z}^LZ0}&e_-HOW_{JaVn(JUFLoP+4rhtojMzjc&c5&) z3LxV0$5fz-Gb{}^<9AVIP>&}Hf3VK0W^BzQ43N%HZ}6II7$6w zDvB%DeB1kQCG8mu_1M)U)h=I+6WYrK%jr7_+r!NlV;*yCA|y#$IkYrygBc=##%rB zv&GWx@V{Y$IqG*Kg(}K3Xfr7&4{ju*sJR{wFV&IO1vbtO(YB+qgVZ#ABlv8LxTpOa zN&?`3*e{!_TI$-Dgfy5&y5T~2wj?%O{nS`xgf_lSQ~Vem!h}%k6+T<8N$n8Tq1uGb zurzajq5d7xmP?;p&hq5=SY9A!EcK&gxpWyQr`%R-rYD>O6M_*$sZvdA5o8>knQ3})Y`OQqDPMz7zJzf8Mt%uPs3C&lWcK+g$Y5V zBtm-0=r0X55DMjwt=RRsCA1fV0CW3)tohM3VmIR*FAvlYW7a($H9O>Zw%dlJop*CQ z-EvPG_D-!30R=6mph!J;i>k;fh0WA<>ZtK#9A7W4BlrV7RAT}%9lpYklmJx+hpYpa_694V~=0B3p-cDN}d#-ep#G} z7dX|m{t+vjdo4J3GnMf5&yvUIP0>)CH{4Tig}KF!03l)y^v*538$ zO%-dTl#a!M?M1+_s5~+Q@Y6Q{s6U+wLMBG{p$9>YFI!Fe=>A^r^-&K zG&CfEcU^#y!ZRvZ&K|qSXE>c+Q|;A5fP+#qc!(&XlpY(LtZFr`OjSb=0<28$@r(yl z8AaktT9bV&mui_v?zB!%#-+(Az4|O|0biqxhTr|}z8Bsc**n+3ush==`w{TtJmIsI z(TK6-daleMUt34?{ za=VwE#9t78rEz(44bwmo)`$_Fx5AfR+ntzZRsK|sAyv0%IR%XMz0opruKV+1;~%0Y z_=}~7hMy8rHT``&o#}NB_{|Nti(2UAbfWLP0=5oU(X{?m(FPWv>%LoJ7b1b{7p|Xc zRnVz^j~PLvU{zh`L+*-P0uWSJK5vJFE;l8g?Iqu7`c)e+0~6+)+o?pRz^(N8RG>N3 zIA&-q6Z!-?iI~rY*iEUKR0XywQ-LVLDlq!n)_3~6xlD{4 zzb$;U&ka9vjrkHC1tl?>=54y*kbeoCM2(TQ%T1n6hMA!}PP9(pz2JlcKmat?{A%uR z-t1rJ=B956Wn=?#yYqp+@%&G!drmE2oe!G}d%SQF;2$afrKp?oc^dz5y*YP&+c-QgylGzVEd}aqhj59N8OV2rx_ zyrEm3Ld69;KA1@<=H2@IDvAHs%nFfp9vqaUC=i))M1wUI4|bA#^IK68%t0BU6P;Hx?yP zj<7GHm(G8h(%F?nHKh44sR9-={Qq9;->2N-c&gO2=&DlJh zVN2YRhShnZ;^i&(5uM9vV&)l*>1YSPUEQWM5`5sOyQ~Nc6n3lqLds~g7!Ky8jmJC8 z)^XUcdU5TxMAB6|YDX zCPNmgawOH@EvL*?MvKikxbf(8f93_bEC0Xddp<3(liRMfGX{1}zfiB$MPB}_b>XZq zk$Y!&XmJY1^?bRrMp-igChTT^4ZF0CDjS6rY6=w>pLQ*O{e}ScO91`|Wsr&A5?YcK z3Li1Y@7eALof+A8O&=_1@0)j$7d_)~x|(QGW^{=vx@#tJlnkh2#exwKTM^Erbq(lz zCICo@hYH8J8>S;~7KFh2yZ#tv{ZhiFY zt&spGd3)Zkr16xO32qK)2pgZkjZF%dWTmZ*I9+aaDw%$H`_Y6Rr~U#`TE)}Ja)mf*oN{kb8y9b`eqlKOe} z(B~#dMYIu5VUE&&n?4?Xiz;qD?IdmVz4Sw}2bY_zZj5?rbN}eu!)8|fD(flnzw5ea zl8hwv3jd5kM`s{G{8F_MF%+CJfG_!h=H0tfEMfepzawn*(V<%y%`sR>U}%Wy=>(kn?cL4bT=k^oMU-eR+94>RZIC zjG6QvBQ*vlJ66i@r>D9pOOxyB@9xw=@7&>QEmBAeUA(t^Myy#QrsT#gH~am&?|v&< zqoN$P-PnBy(*2xsxGaF03bM(28n1*m^`Zq}?=7|ahMIDW!AoMoEsXAkw95mX1>btJ zW??uxBU4wauvVLMD$#sg>a`JRQ#|LsS*SZ~Mqe?35q~6#TYh!&II@8hiX)sF4#MLh z%Mzl9gGXf(WWpk^q<{2Tw5>M3Qt>#pZoa=s^Z>u%ZyxI+_Z}r>jd*aGs4Sd*Wc5XE zx}bw4ptC#ds1sfiL_sjhgw@y4$M{5pIlL)H$9Vs82*(O3Q;F-LXfac#hUY#EoSR6O zkD9bm1WlMcUVnIct~~?7!(RV54PJiVH@wGqe85@5SRd|ft7s8h@Lh2?nP|{t0WyEP z7DZ!*!;BrJJx>JX+^OA3a=AK6g@x?x)iA|hImx{1I`0kK6KvuoPf^&F0oD4qu56S) z&_P;zkRYe4=RSeRepg#!WJC2KJw^L?YtD0XzzE14>l^QuJ^O8Ubt&H-tz>!X9`|1- zhj=$8zQ0ct=n70IrD_3gH!tbhE<>D&@V=tj`Bd6bTyH~?uj$A63f*A5t30`CEDTlw zxbCyHqr{Q#FPD5VCi-1)QZ*8N0U)L<;LR%?$5~v`EBd4tZ-hNhaGx(^xr2hb6?LL; z;Z>{-sOkq;Qt?dv6z7OgWYV01HtpdfT|!K!NBZykGO2xvNKdp40&@S&RBF$v1YT7h zM~c0Sn*@yQQ`g#HZ%mImAo~O8sTBL(vT6Z$7w+!0N<^3Bn~YngpJd;H*tcpy3>ali z4wp&HsalvtSe?u)kq}j#Sr+u%5_?Jr^O9F;-x`OS!cMtmNPP>`9>dFQ18(STcpXQl z*foghW09G2|HdGFkq+PCt=Fwln!=uTh_wrS>ezMg|`f8jt59%hcEHfVAY01jU+ljsZARsO{B+Fa5Kb}oh!NOONA05MA+@>r>&SYSu; zC);n1bt+t=WUgsD`d0(%m)+FPolS8gpZpg3TleWLyR$mIlUevxn;h81`eHJja^&`VwgQFc{n9vb+LlMopWppP9qp9SNI?R3@_G$3^Bu zQK$}3+t9rKfN6vR!Z!|v$BvY&k!;9R>lOeKScTa>`-im7#+cud^FwA_A$$r+gdW$&Wp;BC&v^n0@s7 zZJtr5OM)!N1G!Rl_tlQ0hurI|(XkOqB0#JPoLCS|Z#|#<>Lw8mhyhYtvF^ zsVao{IZ?UDe=Cd;UWr@`Jg9`dBs#&Df&K^2_{v|#&XpN9C-kKl3s=m8KSNt|>7ImV zE{60_4|YBe^l)$LVdum;9wl>2&+>A3t1vGEA`J|UcBb9m;^!gwEMH>khZsegqRCSO0$Y>K_3Tok(t|lizH8>vt|s4TxjaN=F(O49DU% zvyHdDP&&!Jn`L&JmHuWMbrd0ijz~MU)tlwka{fwV((R*;C2~g1TP2{4v@u~EBNolv zSX&uz19Og$vpA>et_IbKqJEfpSKqhkgjjObv2Jh*T|Irfls-4plz$-thMdiurR}Vx z>kYM0o*@VU9W0UlM3m_TJV9B$o~e`j)e;2%41&2Ta_vnZ>Uwi1KmU9|8h3g`f}_`3 zDF>2o)sPBb2Bnv=Fa2cIjMP(zYTaGe0W-eoGt2NpNtDFM z_1M-21@lIL-q}^cr;*+;u>AdICOLmh~zH2vE>*G`cLz61g6?Lt$q4R?uZ z_F4F$ucd3!auF()$WMg+VaxqMF|Bv2;pJjA3J-3TEoB8lVgavR-bo%XVWx{}>gjJ< zW;nL>FSx|-ztr1&DSX>$tOCHsvovc+Qk;=c%bD*7n$wAR?J$2#j4ie*#;++r)r0J!3m|vv=gjPAsCfW4aD45fAtxi2uNi9vebdE zr$MvOcVS!HJVbNA+wGBIc9Pgt;%~8{B>Fw}{$S#8bl=r11{tD1&qMG)FQ(sr=&_Tb z-N|Jnpej%j)|fUG`FU<(P}Vtb$-^)Ah6vFKXPn=-_LIo-xG5_c69ykFs4il$r$?Ug13O`so+Xk7>?W^x+_po;7 z+kuOoi#*e}y@>%mr<)6Z?^o?AC(Io%|2b3l(Y1kBbBTdxSg<_BJ3j_e!T6rjk0;*~ zemLy$`asdb2S>DWN;y5#)3liIq2%0?fZydcx)Ai1nmt4rrEyxv6?4+W#6coI)&Ei)5h4bJJ}C$Y>Klzu;L+Oec(i9(gD{htnx3o zF7wCYzfcZ^%3IGglO6AkP~5RxYNDsmWT^r1Me;?3uh6C#{Z%wjMvo^%E{6g<31A}j zb4_8Z?}IE7RXg=*=Cv~%OjG-Wwt{o1y+^R704MkMA_LJn`&G4rsGV+|2jowSX3^!A z^rV@7Uw?MEkPyu`k;{(1_XsNLdaB?)iOluoZ2J)wF4cmV@@(W|G3RMUiDwyKv*>|BvFrx!N%enviRI@}tC1Y1)PF0~`OVI_h7c#W#Qfou@ms(}MZVIJi2* z(}wV=$^bfXYgS6#pKrE$)iE{VMEHz#v+EKiM0TDg5^3r*L3`E8i;?n<_hcdUDUL6S z)vf^awC-He6p?~xE5qXkD&_%c+{>nvjBCj2RB%@q|1%t()vTtk3em8RY!%f7~ry9>mx$2jTa82nilq z(@vj6NaP(w??9HRi_tcpIS~4wyFzPrUV2N@ABWprN$Kac+nUB~j&R1TziF@>^`n$^ zPMR}L7w~~U3?o+(v3tl&W`Ipu9}1NH-HnS*rzO++qy*jSk`xo?v%kK*YNaLsn>oVR zK5gFBL8qCiSyhxIK0PWbvhuLtPw5(C`{j+g#vXQD5NZ*Hv|dMv6Ks~#Q)SDdSe36n zBr)5?FM5r3??bvZl(C1nRtln#T;`>^SKeL{YQZ1^Y&kPA$R~YaTxn7{w>Bc2x@)uE zrFny3K(}r8!UQYBl}pU2pb0(!C6OUr2)?mVLZ=Yb>Co$HN$g>KPJnT>+_g)e+0xz5 z+9vXT-^s{67%)%i)7M%9nf0GYl#Q2aH7c0UC8)5)6Nkd#WzaaK13tR#uDlgaQky#UTPABP9nqN2omVs zmVMO^JLuUHv2a>OuS5>sYEYe`!M4HvtD(PygIuKJ*=IES5dKRjY#yEc)Md0-UjSNc zi)u_tuc$WZT$d1OX^H#nSzA9lYRV=f5aw;6o86sWkB4hVT?g3UBo;K5r#Q}s5^@i~ zjd#TVkQKg%2VO59f&{vv&P)Q0NJ=0nHR?5E`BawHj&K*b!lQ~2@plC?7@DY{*je~m zKo6oSY~^dH)o_QreSG>>Fc?kvR50I%%1hx)&YQ}MPvNEVYNW9&^1vvA$laO;*CthR zzYUbA)pcBiqeSyM-gjBlDD;X|#BtJ{EXY1yYJIv#KXEnulSE_y0P*z9_e;Yj+I7cl z>Myyci|JOp#?LqLI|iONfL`;q*@103A+dwq-UGiE8y{Na&;_aIdr^}(KhGynXOs|j zMAyuAr~t0!#*@3%pXgzCdO-hpB1Rvl2?v0l{Py6E=f>>hTtc7vLhS^e)Jt)q}!mk79b9P(kohlf)99C5FR!Jc2*Hxvs}b{ zCYaKx)~w%D51Pu$wEP{{HO?hnYretpIXP=D-_Hyf&zvvjn@_V)82d~P8|HCQVkyZt z0r8aJlCR<+1Lw&tO;k@eY~~u8n=zFa;nW~#VB<~I=BDdDGLpmC#QWf0f$ArZGEHSN z4VnMr1z6XWI+jX23>c@Wkle%^B+n_;q!f+9C@ofeZB22QwdjE=NusN$hG9mnTdWW0 zgi(Ujt|ZYfay1TOu=*`ViENiUA9|S>3WKe|jq}SBre(@qFQcZaqLVae<87-)sMAY0 zjb28!<^7n*IkEJ3)rFoY62~;5ptyvWbHBz%Jg2=M@GomZ>0fv?df$d8`uJPy{B9y> zK}xU3=NdWms&m|$o>V|9_3zqX=CJ=fc^E&fk!YIT6k89De`lW7_yWV_Cr$G_gZ;M! ztDhdJVmCE)tjNoz`R51J2_Xk5Ph@W}I3kmP#VX))3X3TDN)cK{F8&D$G<|zaim$1| zo%nIGkS}UHk{k(cM)iw%pI|w13yT5B z@?WTGW5%7?NR=*hNnGSiCaMmJD@FL2ad$gReH-6KgYS1}HBRw2}`S zTtYSsBe4$91o`4`4eTrvB!XL~0iDB0ZrC{rmek1hOid*zFB;`U1fHB9y@&M`tZ{TD zPw)AwlVTv&@*01;KAr##u>B}%I{NNYx~X#xqY@*`oI_*}Ai>s1}Hw?xB!mPjeMv!G#dMis@FzLxUNx|7$IjjG8{i z>Qc&rDpZhK8LAm65pG#UyuEqIWt8Rb>9CHyPs&`hjkz3jA zj&Sk0QT-qBxurSq)a^qMNb7lNIwqOQk}qP!MntwrkCXtiBC=Jqv%l+x?yQR$Pv6&Z zfmnVJoNZKG5ZTF>2GT-16j&IbQEI})=v4Rv`#W&h;7UzD4&TqK_(wP`l{Mbl_(W)kGl$wlQsB9MDNThqF^?{cOs2V5aC+|AxC=ye_X4vg&71w#KEK4S83k zz7$SC=euWZ+nR89F<$Rp(+e+Ua@jR}T|dl-W-l-|3;)?Mcype#rKC!3U0hJA_yFZC zu9Fn?1X}u_B|-|-Id-&{G@-cotjSlo0tNZ@9BBbLx{4AU>vt0Yn`>fMT(F?;kiEfw zmFO#y1uycz$3Tpr^|>t4Tvwyd%x~o_rxkeQ&`V zH$@}kp77qf@WxOr(fsGr8+Zynzj!C7u@9kWE?B~rtSdbW!gcI>(7O@v z;?FvNP}lu)0*j2ApZ_;NxO|=}vMjzEhK#uqG=60kwD(S*vX{V91GgNRx9!>9|i9hFQEv^MEvMn`&2T zXtt|_uVe_&Z6g1@_>LuH6t%-(?!0rPUI>ox zPWa6(auJT<=(0$#q%9DScSH1Z@sDVcUmNvRoqSZ>Xbs3Dz*ia+-ns0_oSiop)Pa{Q zPUp-2-hI9XhZAJadG8*F?d`Q&rl?^-Am zlXBKhMJ&@Qh}n}hX=?MDH+_gO$qq0h6$bmIrQoZPgEvIRm)TQEQXMrU6ebq2A&BcMMln_e6o(PxPS zyjqi;@+C59lO(Nve@WA~CtT!JrIr5bQ<&l5v5#D7Vv3L{}4kgmfbbXV$|22 zeCzw#KWBUw(tHG{90LPZO4n2l!%cG(R^j{vIyvLo%t_pw`?u%z^b(;L(iy~1v*v`{ zSl`L{K8@cPedB0WMHAO!D!E(*U!JE9aTE_iW0-I$lJ#jIZ}3`h;D4{s%)|3TxOUmq z%Xwo|tQkO=-T?VZC^fm*B_!rT?WM56m!P36Ts6Gn*W;FCLs3ce*@xy64PbZ~Q;Y0V zi~@Hy8tP}>jd)8j=GoIL*WIG4D1?a!%84!9NQiPS=1TwR8ep{+%&VX>o6TEu(ZhFj=KDyhlY$ zS@7)#Vv7`sjhvDv;p*rPfDeeCUEoF&^}fzzq%f&dg^8d;(v0ou+UTLCNtLJX>R~2W z(GJtfeg449f^ABW@`T&S)!Y6}t_54_ukyKR%-0)oJ+ONjxKY?ZHya6l!L>h+p;PaQ zJ+|E#=i1%nm3G!Os!2HUxSfPOC@S+?EDftMOp3a)GMnWan%bg9ZWVcJW>XK2`hEn{DhRkZfI zrbTWe`CXVP6$ ztq+{)1+023ucKov$of;uCh)kN-e&?-$s;vD5e2pg)G(-uITf@Rfy@ z?g^{R8W^+l;_hW{X}3>SO8L%|!zP0QFQ12a6{@dGOwf5ObQHH;-=DRR;~}6}vz5PE zn0uThOfLj!7O!v@S0ZRW*Y2V|iK0RI=Va|%A3U^db?ry0pZH#iPhp+WhaT`&*vkK2 zd55LzFB?J6KW`v+R0Q4l)vtYPR~(Qj`n-j{#Ap^K%G>AY9}v2AAu^T;cYm^gqVID2 zFxZc5fgoVB*I`+Rh0&175v1-2SiGrv4s;I&GE1};QhD2BclTqG_n8JHGE}fmV?5!H z@vz`5pyeqGef|_lTy*BWHL`}}OzM>)ioiE?YNoe%HS9Nvr_DM@y8jLpOj`VTR3rMyh>!pw@!PkqCMA`90HpQlmHx*HQi9_MP zO?sEQgr;zFV-o!YWWlJ|o9s2uYVU86p@*31+5z2(#bw@KX`r8%+llX-fCW&OqVeO*PH>zK&Yzz6_s}|c z=IK{uuFm-`+d9=I?)H#zHq|j@!hWFxx4eFJ@YInrNOLRqUN#0`t35r34@2p$lW|>7vre90wHJ5v*6SU?VCL%X>*Jvd zvqYI9c!k)=xF9|C-Kzftv#+i!^lsxEZWwP!cpGLaO!8acLkrulL;p=6wTmvCpO-!K zVT~RAD-}ttK&{R{!TBLe$+U|XTK!$d0T(oUL@^Vti3MqQ%sv&79XUTc9}uWs#S^+_ z|JXLNjHozKDw3WC5E_0r=eW&HujG5?{a4rVMd?G3E%-xG)@p+o(a1fHV!RhG_aHd?=cP%5Qyf_Ss*yu=SIZRNGy z9^pC;@vM@>MBm_5ium62P=0pB(kXIT`%l1Cs-W`&1C`vr>sh|5`4fa~*JeFDSqiEw zP)oIgp3~@aC!<%8i@Fk(sWYu(24(r`k3+$xBDEi|6q%$CeR5v|UqMs61@jTL`he=K z+1C0_L$&_Y_65^`kbb>kd}I=N84G;3#Q7B*WUv6$LKyf*gR6hYO|fGt_PBXF8?9hG zt#En_aOG{)X3EuS-1z2Xcr+~S|D)--mqulBqZ~Z&vhoDiK|UIIc>b3G~>gQqWcG~bzQ z{j|(*K777-)5a#l_@@16r*EKKg*f4fNK5|pk5>IkBwbYX7gy6P9_y{Oz$F`52X%Zp zDAp^ZQ)@uqaBiO?JuAaCeo%i7JErT#WX1waV;HlYYN3Z&BEXi7g`V)3s%&6XWujmJxP;D z?|Rb1diM1(8!(W=tF?d>C*zK|wZzV+d#riU?>B7N-?N|-*W8cNEQ^BNb@0Jw)Dha4 z&xgIKJYweqZ#fc&N;F6;N2h3IYyCBsdX$n4l^L5)IciWeM3wqMP_nlCiPMrx96&U+ zaC$%4e*XU8{O>dIv;xTz3Tbd{ZL#K-+=)+=EM0ShqGPH)zsB%Y{M!4CzJyB~6~|PM zA!1i`(sCoFO}pn%vZToh<1m0Q=&khZH5*c}tdpG^Wtc3xbj$@eexthWx?l?u-7 z?HKY2*%Kd1VG$>qGvtrYO<1Ewl5Wg10WOO)JS0!Rnwqf0QkW5Fv4P^CB6*AyJA(fS{-@2-$7T3_A@GE~&-v zg-ul4!-b(3#x0_M@+jm#$#dV*e9*Vq;Ahg+@fGjc=z9&p&}CDSqx_|IYv!_?DI!*U zw6u`XjbB1i$Ek18#Ki<(+n2$5jUS+;2?k`veYv%XNvHgA@LI7kVL2#eF2syR;}hfY zB14C8#*0;6?pI|Stm%>LsZz>{OQs}kZm+-cjn{OTH6leI(Fj)uM53JGxvT6(Ro#w) zu3yk+shy5~PcMtq%K61V@dmctKn&cv<3kghll8=qnvY$^Gk{w?h-11G@0VkxmHg#( zG$t`{*<;8+eet+;V@d^+VKK$q=~ktm8T3d(HbX~ZIeyKQW?wsC;ZNHD)QsxgrA=vL zX~|7wwQURb#TUQJwB*>#Re`A(JHhKdm9!s!vIt`9o&O>Wu;`8ZOQZ`(Y_k^l-I zmpixdjU`EuwTh#84?x9wU+`~di)(vAGct0=08kY>}TP|a7dG+GOKj?t;Weo-xAb`=TNk2#`V==q# z=A`8Qh!Lo&pul`-78Yko{iRUr0E|lh`Gc$_wSb`K?j@G#XXlki}6{~Q@=PEV?a2zhIZsF1_6h5*84kYO==%9 zoWcaqZO$e%%KK6?NFl`_*23&-enFR=ySFOwv_eKqJyr;rJZvO4A9PYmcPF4dAnBcO zlPx)A9=IEbole1-Z~3J=(!9TXn`&+F-T@M;+J)jES)=o6ZY)(JM_RXD^nkRsGNF&A z{bQ|}a(b^Vh=dr5us3Zs4`&AD2&vfoqp9Pa4UrKPOw~(kJ#%>!`FtPQt(UuQxcP37 zjO}lMb|q%{56gth8HFre?+HKXmtU{j2NYE!mGUxVOhe(4C(8AL`hG5*D{Ky*aD2jK zI9)Q?nN9=}L^j<#A9%a`rq$V{bme@u}*o`R`k^ zI>n8AJ%I-#RqUIK-&xYHbVi%Ebu3!&qj^n*eKKA-#*b&2>nBL2&GQ{mnW@`iwLjhS zXAf270237sezV};m;fzMUw_ylKJM4w)PpE4mHW__ql`%d)h2{z)# zHZC<^ja;cD93~!kD%_{ya(4KSae4cXacR6T3_5*zEB`E566y)Dq*I0h)U-a$r(>p}LCc(}J!^?V9oaJ$7qT8- z=0{UM4O$Ouq{ZgOT&!dgf##IOauE1S6sbzfreV85sJW;e=<9cTDa9l$?eEBPJfQHT z2vvI5*-R(8WRWeSk=m^~2Obj0APna|^ zk%mdR!qCY!si!J$X-Req5opP8DnxVj22T{{^xKw%JtoSrB?+^Dr7!YZ_Yz}m>JrkRi73f`zwg3F==D4VfzcqntH`kp$dtHy>UCi4E7H$0L_PX_H%8XqXo;D|P zj6gKgS7aIAXe!*r38CG$I`lT@F`mYJZe{bMa_$xWp|;{9r4JRMnHHZ2>xiNk}=?RM(aF0^yR}u_Nm|vGOgqesHxy8wacbCut(%nZv^)_*w z8ZAFaBTiSdW}}m4@90;Op*}gZ=j@wm{Hfa{$D8;?&e7vPVyowUJ5_zPe@Xvp{o*QB22}-bf4%^oYot7ZJ7vt46dw->LDP+ zTn>l!na^+#rs~u-xJOVwC_+W)lSaF}It=Sz#jfudkVIHAo%7^Mlj*`ir*6HTuB&xv z={}-NHvph_y*;z__&?voMO5`?v38&8!`M57E)N;{O74EDZ_+y8>kY!n{uy4XvT?Zm zdG1*{R}Rd8B&6vs57y{2w`qjf43BwF0opA9w+P`lewQce8;+3tm>=_`8Ky)1i#$o6 zuVrhLa{QBqEm;zDWT$N>=rE7V{t?SuGa)PnUH@5Gk)h0DKxv=6@W}t#R6{7M+CVAW z$AxKR3q#OH)m}kSHNBzrUJ9wRdI9W(8!2MZHr)(H_p~FF8Q10F4vdL5%2A9tI`X9A z4n2O3)8t)}bc(h(wDdpbQFgh4uvS}M2kJYxmUdsa?e_D~@s?nD4#i5UE<;8ih0Qd4 zp4>}o#hWWHYq#&LBe|^kb;G?AXPElsfM7MA|0xz-V&GL;e zPD^iMk8+W?lt@Zp9e*$baJCDn>!4WFdCH&IZT36ciJ4DR+eUt0n`lI^{wPBIxQbhi zO?tG&){26`mb+Un!FL5{5bBs{`!!xmXZJG=ExugIw5Rg!YW$b~)B!*rV|Xx0d07Vn{x1?^`L*g=>H3!N zt2Iv2QJ}zX#%J%K>flPoH}RGAzgRTv!)&#D%dDkimm3}(S8RNfBM_3RBfT{_Y8>Bc zz?k_x8OBzk2*0?_(K}vhNiqM_Chjy?bi~9J&s>cV?GKx0C^6LMm9e5@5w^<-^H7fo zJj~Eg$A!EshdUfTqd`F~N4r7TJPz;32+ZG>%Y5V5IfnFPGYz)sUK<9fQXo!mwLpVc z<`F_yalbjG7ee}UAJj1u7Pvvwj5#Q!dvPOIy?7~Qhq&d52**4boDwe`XN;AUZYamK z4Kp7!i(O$BXH3!GjsXU6Q5C@p;9A=*`trxuL>uyl-$!GYu6xR|nkhMyriOe3NZY=g zceaVxD_Wj5-OAu2*a(1K!g3 zM6u7LW2@I2JPax_8-V@T`SXpp#?j4{PeEq-TfGg@7%rzm(|5ZZFH8I(b20G*L#nsN z8=k#Otx=zh?8OY z%O0YxE3WGQNk13)0JUD{k>RR5F`fA((oE6Fv1O>Il4of}1um<@eXOQ4A>k zjOdsc^}YW5ucti$RrTs;O^v@FyiMQWzd=G298>LwohDhUY?aJJ7a2;QEHw0Y z_kDGjRBShyeqNj47n4CDZlQ1X)y>o!FaHr{D`_ut0AbXC??6{GQdd@TIvWj) z?<`2X=a#;627x})Rb>_NPoxEVk%qsxG|VmddFPG*a5Mcy{u4HTjRO{rFX25;9Svwz z_W0t;xv5m;fnzXUl~IqGt7!$gE+XRPPn3n{fuW1=zjO}ckk|{A&kHvd&##wWuM8n* z-$=Sv@t+3&Nf9Q%_zJ|nnE;MM9NDN8E$900)bQe=ASXfgBCXGDH#w=}SoW$H@hl)* zpksuvAPpS4Y29uwP?q)ARDH7Q(ufak42UiJx-WrN;?}>3uQ5!9aszq)BK$Oo4UaV> zT%pb322{q9JY$gQi2d3e^Yy74T!Dr3j%w3UO>70-T>NvYv8$IQM%`R>08pTyEikp?i>3G%@tF!ooICg6V{b6YWBIpwzG)~`03nD@JylL zIQ*9T?gs0<`iv1rWay+yKCVI!hR?qkB20V>v7>x59}sEf{On7P{9}%VC^8o=J>5|g*_OVWYkW~*;BYFD|y1Nc|hFYlpm7+zKYPawJ3f7{_T-7zU^ zM{>w+6V9nCc<`9WgTGX@#QWTAcM3PFl$J$|H2A`#v0d9X)!~u(LAThAuTuTz@4;%x z4ikq?v9u|YyFKhbf!yNa?8grwl%T@8e;QY-*;&<3g9~2#PGrjB+NxA}>b(zldY@HC zmbJc$zl~pXJfA40ZMbLJ_H+EhvOY~r4`bicl{`-Pqw(w{gCR{Jk!aso0C3MA6X)%Q z5&SXA>zbNhy9x^e9e;dM-M?GDe^6cp6z2~T6@N?P1izk>_!my`6GgZxQ8MHaUoJ0P zr*!VqsdT13+5SFY38 zMZDja=Sx0{AkAg8&RpmQM62>60-UG&jkOK0g!MaDMuV{1++Ef5NB`+RB~@|ywIOG0 zO{^?$RnBAI^a&ev92aDp7(~p%>aDUbdN!7loEI3YY{@z)i~8cHLUMZB z`my;WaJh!fyL|hktD$rPTaBHBH}xJuOj6>}i-G3`F&2)t^tYj80%p+gucFsY4s$;}tk<8NBG&A`Y|Z1cQ_-f{1J{QSGOBfNZTbXPEVXi(pMd{j4t z59G8|gV)VYWhmYq%%lojIZ+ZIs;p?=gjWb(gd)8u-rZWeZ5k9BUTbW#r8{7Mwk7}T zEK|~F8eo?tjsK-~HeP6L_jetp-e8E(0oQF2ce^-z{LUx7KIs%X`MKbJw7Nh4EJ;0S zkWG^bw!Wl7(f6&+p2ITCLab==R}267OWWO4BLa@_t?VrW7XYoz*RKpbWD4HNc5{Mo z)7K#nQ-s>Z_A&zr#0`9F^TzK833|lP=u{#Zq1f)^)*S&Kihn^z1 z`!fYk)4@)nG-x zL8%|RjNscx3n|Iqe*Qzf8+-2Q?y;?;$o!^jy*RZ)>Q>jnG`41j19zker86TUK$M66Sl2v?j!MKryIo9f0rC1?BtjAIx@#4+r z=dWg}EpI;0v4ysILJs7{33WyQZn2rg1f#bUvC`t`0rz6DNMN-9Y3>`6lemhE{#(Z^{^LnsayPY>f3$KW;Ss-%MqDkv68!qUMnlRW?jm#_77q$cL5T> zO=|HKiGm$hv9YfvJzBu7-R);zH$<%FN>zzIvWu=e~hQ&`eGl-@5E%+Vj0YCMbkBV(lWa2UT36T>GBd#J>VXTfaA>1{SmTjHC z5l`{FcP|(>`_Umc=RWPHdkyOsr%FJIcYL~ye!e!ZrqH2V=R^2oFz2AkF zkeUSSriOAg_?HmEJq^a0P^&dLX<5$MSbf<3pWF-Y0uk`24U?ZL=^bAwrZeB5^NkpHJh)?$U8h*{;Y@jHT~d7X$fB*#!ts!jH?RkTsw>s4{ZGQv=;O&}^&*98(!IhzMu5Efi*8OArv7qD>H84-P?O0dN+H12(mWME4lbnBMDa^4Cbmx& zkn4TnL&~m-U|%9{Xa02%KjF>{8Q3KqYrV)(#F&q#U&JUtAlH3j{l-MobTW z>u>iYQh>U4GRheV_kCka_}K@@1D+H7a^zwd9nE3&<>S}TVp{nY(5J`h?{8UXtVtdO zF6u%*@^HO4C!z`2xH*6Yubl>OR8Ls80sqH6Bhf28m?l$B_)7lY6n+g}=F&2Suqaet ziB&Xr$Hg#X4C+hl@V2+mVbKmZ^I$gTLS_sK)#Gcm1}U%A;-|=hGI!Em{9v{Ky_6I5!hfA);8H zr%)lP-xj*?INI%@Z#1^&8T?`=NzvECY#{e^GW5q&^_ti7jy9X44=H&U|IiYG(_ z7w0+k*XRj`h{&mEagRl;ECwv2** zUFYk~7B9tfG|gfBWy-*Z877FY8q2v`ityB_AGo69xRLq~$$x8_RN#bzuk{`-$AWO0VeJzXN9%$~^WMY0J7))G=}-7zT9eKe&Q zxi7U!W0sp~nm^8u59Y(|yON=>%2q?xVeIOZ8nayR_N_z!YzLUQ09oPRrXbvZFQM@3 zApGmUQ)f6BZ2o)qM8LkF?zomz(4@9loE7CTdiHlz%e-}%zbk78hjhSYfV|N{6~@N6 zRl<_ZUkSqclSrs!EK5XM);@a4{&tC4&97;?P?R_P=92VUw16v33bBCvk?Z#1Ju2$BDP{VJT*?#2}AhH9#*2e-7+iP>db{6lNL7`zgU9MN12&hu537U-hT z(O&8H7|cN?8Iqv4XNI-d;+6P{PUCuD2BCn^u^V)Gxmjg%!mt6#>2M?0OvEE7%L$#p zUaq5jAMAVvvhc4iUH*PuKw#R8FY&Brs=L&Ja1n{ugOnj#xm`_$=Or^vO<1DPDUy0} zy!IcY^|8RD?-|gAF>7lR0X(a2pqABmqYvH$ZQV*FHPQGm?PB00NDt3=2=DtBwhAsg z5yvv=yM&MYWm)P_d?ihH-y`43vZqS=jrVnqtGA0PelQe?LU5rU=;SmZPJhFyg<3~* z2uh5B`g-fbX-YP#bSpd_X+n7#m>~qpFO=#xnS8G^8R=6>qhV}qt>6*t^PF;`pF+h# z!#K;uK&5Y!{Z=G7^U614F%PDXs4*4%phXpW@J6a)htdP9(}CWht!`tI%FTmiYkY(x z?Ki8AGk2MkGadL<0P3K?L#+`O-nw)mN!)dqEJnGohriNUT*~zA{0Ow zeWBRsF7=gTUtLnZz4YsZn)W04kd=Sm*zc}zpWQhGhCVNN@LqURh?V_mk%T~rcWf7n zYt6CO1Xg4>uHfl++N(DW?!wrCQ1RvasbWRO!NYZBa=CI8Nprin{^zrT)s$f!lYGCI zn3dikjN8X&JSEAu^C~~vPJ6(b_mpdKc!F>nxDCy+Gs6SuBhw+ylHeQrN97%pa({A4 zP=i$12d^q29rPW$S*Jx@#03jEtUl;F+!EwCPf>3r?zIkjYUEPcv69 z`#2O;1b6x%Bobj*IQhJPBAdF zoZXu{wuH;xu7%BvKJT)Z!##0XfQIe(PNmug?7e{QZ23K$Y+j^3N}C3%=R zhzmJj^ltU_P%3V`M{a7(N}-H>@4>gikUNFi&6cajo<8+I8JD~=d_#i%tn6w!(#}Bf z3nu^SIO9E(gx{QdJ{4;lutp%3R*p04ALYBrv;4}M*cU9FIpocJX|ITR(D9XAz;#1~ zC5@E2`3OH^%lPv6C#n3`RV@yxZobF}aF6raNb)fzy1V{0%ZKoj zcDE`iSV__?mubPIl6wKzUdHHK=(l!4wkvicln_dLeD2C;z=HCW$*+nr9_axABa_%u zt(gkiK#B~fLg;4vPsfT-Kf6watOLViI|%1uGro=&e*VJ1;5I4-hmdtnH#b@_o@2e< z+P|E>Jn#ahthUhgoi6wCLk)FWh~fumdl zYD&NKkpN+Ir!kvc!P}(xa0T&Vh@a3g3QyOZ!&JdZYlVtDd^!s`$1tkY6W+s+*Ys_? zP5X>q{idpt82CgzNMZ;UR`LWabyR~(Lvp*Ug#-PIG#|(2IaB>P;(B28>Sv$J^~6y6 z2IMBM$28oH3D>;q73{w_uW6i7-V9WplIBMNOS4f#0^74n8|Y9e(`UT3tQfL|E?f!%2Qg8v1uc&yiEj zmZn3;X~I{E{A~-Kf4=PZ{OYjMs#|0DHJ!vxB%oZ1!U&(Y3v+VOF$m@5kVjm z73D%yR1@Lk19?#b%?N~XDV1tT?fZGF8@v5a)?{&VUogwA)(el*bJ~iuU)u2+ol7x) zwOgjs;eprDMzXJ06yJCVby(?1o})6%4gU=4t0K^81PWLS_cwdb|6a&Jn1+_=lD-&`yep^y2V5sGqz1$ekZHNsP=vH= zM)Rs-8}-fdk_sn36xaxcCf|FEo2qnwRqeI*P5b#)fm>5@UBq?dcMZ<+<>g_>!7-&= zYaqY`{QshopE~=ie%ZN+T?!(zsy|*>?=oTo@8+@QaH??>QPwmbnFq2mfbn5Qm90vc z6Kr7htXG8efH=lE23?mLE+PFIB%O<9urKuT6$dFS&3hc8KwPZOnKw%BP)FyTlysry zL*4o*cXb_YX=J<8OHnudDbl|~8CQj4bxU3aeA}DzpVZ|F6RVy<9}{R8E5;LM7*k=x%T%($FHyNv8R?_ zjLz@&1VAF#e39BGHE>a19jEvb5(6UAHmhZNe;L=x2~6a)TR}S^vu82iTZB6kJCIG$ zzWWw0y52Zvfb#0W8O#Nf1vIPJ{2;u4&!FDTG7&2(WEs|VRwJ=P*&N*ciR-N_#|He( z1{3hB)>Sd}c0=xIOrz0L3}qsRYONw(-l39?eikJkOVE!`(1B5B*>E{u;`j+Ziawm{ zGYAy^pgeU&#O(n8^ZFe-P3}K1_<}>0-yq{B(s@d;&+A64cw;;MpHM%}d(+FFzNtBp z-)CxkzIW_jZE#rR_7;xj`B6VpBif%dtKvSqYMhCCmNe+0#pDD?%j1e3YRXM8SU7Iy zvku?998^|dr9ve)r^KgEzL(tknagUX@#=#@NV9&0YrEMA^Y|T9h2km{B$Xa7fM}z5 z=d<>f^@rToQ@(dB#Sf6(curTo_LqHuf&lmcDMM^HrT)A_W_9ac=p zyvBty^BhnvJ!20Uxl=Cia$e1NQoVtqTp@gIh@yKI>8t)@H>(8&O zhe_BqbN6&$WTLw2(_!vYTDT?r7!l;Ng9ONNUW<`e0-TehFrf=(9(EnTN(WlFE`wye znz}TMYp~jAqK({9Vl}N~*->G2MPZktqLwshP!>E3LHS037qHmnWEziyi(pU25b8Ty zQd4ds{yI)Cc!6l5C_}y;sT?E8cgh@7wD}YS%yX&{CC11R9nj^*kZyu zz~++53=D)&dcII>QW^7qT7Wwf4CVd2vxS+PZmOt#(L(bDu2h2@P#^$8ZOWu)O_#pB|5Xw)I>9#%Ld=>Y$hJ$- zwQ9>k+K+zZM7?M4WlNBuAI{LRD#xjJ&p77uxkpg083V;s{w5Ne(f#SQ7d@yC3TehX zd>Xg>s|fGG!LV!E6o#DN{0DO_)KvZ79h+tL_dohO&qu(}`~EZU^dq^|a(bWfwSE_t zmCwkU+qaW<-P;R8E!A~N?rW-qyETj$*LJWyQ*7FsA={*_0$;?$ydW}HlBx2*%&h$w~QnodcwVv!w;mnmu>>np{zXb?ERTF#-6XqSq3r2IQt zYTf2p#dTig^tE%|FL&f=p9J{_;u1#!=eVR4(s-l|lbL}r-3u{O*bgI&dy$vqweF7h z#b!>pN6X?S>epA*#t1d1_=HG3yT&~8E>d8_c~ZHWk$tsBL_ zerz27E9>@E4q|MMd>+(FQ-nbBItGj>2oYS)+DZEV-YGCA#1PkYmgix z_Owf8k4KYR7%9c2m(hgoHP?Rixewx2g2A;X5jdRhlKzts+cxG}jT+*ds;;&DDhX3- z5$S39^CVHle=0wh_Sq4F6+pL7+f&GxoZgpIH?uyi{Y!oW$feA=QY8!HC1bK!T_PIs zv&B+R1_a4N^1f*KYQvfgAzWn*YZkO4l3-jvV;|;R5q98aH6?5Fwnj5@G9RdEy8o{K8vREUjsnFDz~>99lU^lx<@76S&j9)=d}UGeURtw-bZ3)A#?Y zCg+R0{fP_!t~C{~(2=+6JD58AG=a^!_NUd*B3_<;DFUfa(zAuDS2+lyD5!6A$&wX* zwVCEETg&>Q*(uelzQ+9QM>J!Z(v4pC;(cfhoMvRGLb#}nsvV(;_AvYO^)3X;e{Ls1 z8RLwCe}Ae`{=zw)Jr#0iqk;1olew2D$>G+~;1If^)>ATU%NnUsPAnZ-hdQtz88=gp0Gvsh4~TH}kI7J2^=FnJ z#h3K?5FtzH7Q|5@N0KD_U6Dh;E+d@dP4urv#a`5%%Ac*C2JOOLp8IxO%}rqs+TZJY zYc1Zcfj>5v6{z8Kqt2apD$aEv0K|IbOCE5=Z?x)BkLZIfoBqOI5k$^?&y4n52IQkI zOg(rd7pptld-v*?a`tvKhX zDK%ZS7*Pl%D*=;*LmO6FVeNJmZh2#2OEneAo_PtzJS>*T^S`ojCE*D)Ft(x)qx36E z`ROs+EVr#he~`gwKUV11qoK8M4f3<1S5LJ1`DAHKx?p08ma2*y)Ncdw8~k_a;E$?! z!X)@_jxm~92S6H>S43;tL0tCn8||;gp1)I;Zh%yH-g9#R1fjT-r*nSsM#9&bqWX62Oww)&~C`<_!~BIMGV&V|Fm=_%CW?BFO> zp*#CQ@&}GA=#$89FDjR~>o@}@;*q}=?p>1Gn3Rg~D9*A@j%bZJP&+!d%&pry+WH_t z&{jy_PfBMBBBjYu;wkjJX@Q#>ehA4ybKxc#myD#G|MaJzLN(0`t*}OW`v+)7K=?@3^GRI9Rf~|Sq1KyUr8%kxhZzj4) zJyf9@3*e|EdYeCb6Xf=5Bp0k8pOvY$_9 z--Dr{bK>rjw*tHl$4S^5K}KN4KNw$oFQ9%Yhzro5k!zLtstyoyG%d6_b&42(nL!&n zRvKChzJC-6cSt)F(hOQDFwde)SJ4KdBEo7|tlSSdTDI_+yNw^aIKF~OgZ*S#-lL9w z9R9=weq$V}%&#Mbk;Amwvl29g^dTkKh_m_UA6RQfWi}#F48nhYHleuVna6-8s`ER% zY3|{eJy<;5%yCUAXZF!4*R3EbvHXO@Qq+$g|K$5t0!b$xQEp%}qR%X3a%I-fTYhKJQbcRpeJUegvL zBuUS>hfvSIwq}ly=QS0-_QNjm!Kpi-ys48^PtUC|R!|o*P&hYmLX;i74Vrtr%p*@rsr*chgTxpu^m`e6rN}zWRGJ0M z6HV+-79aGD6@-j{)Oi=U^LIYCVRVZm+$U$;N%FAu+w`>3_Lsr=)G1-v=PZYlZ?;t4$nkHN0kN=qn!&&b>DQiNKz z1+)BXYCK&y{{7&ykBy6fjx{SmW-V1?mPfyZmGYk@@niOwz2b^z_A>rPVJ_C3#)`~w zye-xv`VMXQU*<=vv&F9aD#dqW?`v|SyRx+X2Pnh1;&~Ory#i}-6uY7;1XIt==A$@E}nbedvha%cT6a zhN+$PjlLimkWcF#!vVyfaG2XY0%9V!dX?^br#RX;r1nSyZB9QjeKJYMzhPw;i8U|x+ zfalEy$iO)3l7oF-6^edr=DENKl4b#|GLPnBfk0?3Aa>Mc6oBO?jd4K242&+|g#Tl&c zqI6v%2=0I@IN*Ux+m4)Ct1Wr z1~E%s9(vOG`x=vKYB)b=qx8Umvd23euf8XJHoAwMF&`m7r-{+7j6xTaY7w~PI#d3` zTC2)DUtLe}<6AB&$P|-Ckk7}9nU63O;e9X^G78zl)RtgpIz1`6$5mJkdo7t-is3Gg z?$7x!y!n@Q!PUWvHg#P?v7yU^ujt}gzmjkN3uO}c)8yZa) z7!fa;@wg!Q*If)m3v`mcnweaK%`Gfr5gv{b`_UoLB@wa)LXD9OEAmY^@~BZC1l`Q` z0bGLwu)Anp&02|)RmnSxxE;Y$iOXtylAt&%*sbw>L3ZDbA4&^QAjZTneQp9^aqs#> zKY&g>VFgm$I$Vkk!OU;mh2mD8ZPiF@s5_B#EI5W$ioM{Q6tQLyABT5Y{PhFcXFre^ zyKzXllFxJ-Hmhz+Msheb(Te{f`V_)h1<0m&R(Oh>2^`{=n9;M>c2ioqPor&yjWTZq zxh7ZkQa4p-#|fl9a+L?{0vm;T&lkhKliLbTq5M6^PM(M6aNAukf4-k57d_+y%w%!@ z?Tw3sxM~3Kk`HRO3=XJ#)q%LL_vaEq(~@GfFSeAk##Ylzh%iFSws)IpRHybh7|#Q6 zs!MyGPL-4g!y9*21_926f!5Jvk)RiyCn!7)f2c87zim8GrKUQxEKBOFU}KE-@W|qx zN)6+Y5lMubY+IQ`1Vzb-w#`E|w#froZg8aPL0$ptPRValq7A8y_#aBByjUHh%8w&n z|8n3FuZ7@;=;Z|VUInv)yW{>&lABAV6ZcDf5Ag949c)~~_)-Bu@>iRu!b>ex7R6M$ zkKJ2E>BUDwcp(yJ?AkHn?H+)T`)MsJZeR%?IB~E=DFj3&)O9?CuybaUH&m$Y!&-9Jl|yVzs|?ymn&%GKWKa9$=H zzeqYyF*)PkwCSnQdEaK+?+SjyoWqa;p?0V;0{hCEIu}L{!<1!CpWsbWCa?z)vv9zS zU9FuQo4O1OP!&W#xOr5!lx>{yr5o<}Z7!#7_KSmchu0dJ^9_y%SvRb?+_CGYqEVpx z`RSF$7f!q!M(?j?=H$wIlf_2907V-Aj3f00@11>Sc7}6Obp5I#mL&ZeuBCe>uzB`LKbbMbnnv)(D^mo=XP0@*X3@h>=vlksyddw8#^8A4}ge z1$myUmL}hSgiqv@9NsSk=6AmQ-=66Gn@y9hlMWqDTyE(iVYK$5C8vj^X3nR37!`~> z{`J(L$Ls;BPJR8mXE&aPS`%N-1S?+@mG20Dk&*cgW@~_xyQ;1|hK5yGr#P6{OL>Ku zwIuZu5SOIGuOe?v9{5#G%yhvKKrS|!Tn-1v2_0$DxaOnPnz;T&c!9!=8Phx&MRJCe zD_d=dM|>*tZ{u;7QBom{lmQr*s$=fY=j1oLFq$}f+I*!Zx^dsanSltgv`e`vQ%;{> zdQdn-<5%nV&Iuj_5Am4kDM>6b_Ly`434AvcrCcAm`6KX!okZrYPqo?o%|;$!Z#VF3pcc zw2L{CyWsHhibaWZV5J)e#~4hQe#q!F#7dFT9+ljZtt_O*@y2YbG_U1^G*lHzVntc? zJgj|W_OsH2OPqqWMy3|urHsTq7&i}Gv?v#V5!W;e@qKLWeX-oKV4$Jfwi4*o?vBmFJqvg3UF ziL1qjVCGOkJLP>)xQR-rf#hdITFL!nVpMnX^#7yjEW@JizOPS%wB*ntf^;`Xh#(A7 zL(kAi4jocbA|OaecL@jxBOnYRT|+kx(j9_yNk3oj-~V~byy2Q_&N+Lpz1C;zZ2fq4 zFS>ocXspEjd~d8vYUk5u(G^kBqSo6s6*&*3wwFFcAs)EI)91JwcqU&}1e}6NqtG`j zPhIb$T+tk^zyA%diOQL<8sO9QYCro9ywPv1Vl2y|de7d8Ma*#d_JYE8rT+GQrvSo{ z9C&l^w=qyM0kuL5fiKvjHoJ7s2JzEgsxTP6C{S=swIg;#@Zn;HRx=lmQvGFct*nsIXxE!YsRy~K;n6=>qV;m0cuI`ieih7yTO|yY z>|Pq^`hMGq>w^dZ?q0+FNIHk}rCXNZvr5AMoB;oPsSS`dGAO+D&0(sN+XD34y*Ul* zh4bMyFS=b>uJBf-P`g!+r(@f)MQnxY?5@sx*S`>ZFB?Umxt5RbM!e-+D2lkG1T+$o z=Ip1aWdOIkUUAW!%n32p?L3|-xOAAYM+o_8rI6k*;iu{!2rU|APmWwqjFzI(nICM= zy|r%0QbDmkQb>4*^w$oC*162lG7AjynF*?}uWvyKS}gm{Z8Np_iJniED(YAqu;RR! zauN7kO%SWH?Qg~Wc$^;nc@!H30<3_RGXw?;KJf7$&*7(@VC5>Nxb4^tDk_ugV#wDY z0RLi^>Jh?_Rl-BZP^I=qI;gn`6VueyP7>8;&kFS>e%tIM5!tsrMS>9v*?1Wo_;thC zq!JJb$Ny&;6#u=;@-{hW)UfsVz59~D<8>Fiw%O%oZ>WPEF(6THV`MS0$}X3Cve2WZ7-9LysQ2k_ zfW`S>(#!rR*$`m&B++PEwqJlc|2(diVI~bKQf+ZIH|TF>ByFDI|4`e+<$M2B?(cHj zW%IYS|GZsqcCsQ4hGSi076Gqc+HCUQ_|C;5F-QE;Tq_H{vp0t|c-hDbeNeU%)O!0N zCN}Q`2(i+r$6<#QuX3GDGx1M;wSYPMx-e_AV-{w1n@tg^BXX)wKS}P6_V^t+E%bg) zQO*k%lse$f=xZdAEyUOxs*WC*?HQm;q*$OVNNxp_RwU1~{t;trXL>KArWn%L@K z0)LB9jRpqlSy+~)eYOzq=dO^)o%4~D2(hoFB2~2!=L{kBo11;wRc^(#s9*Af%Z%_c z;>86h1D@T?*JVVIS=c+;``x99zu5ot;1D7H8w=Iz1h0mX-LI0hom>Gv%HPkzItm^& zSTF(b)24z9uZOH}AnpmX{mlS)Yz%M}a05EM5jw-0uXwOA6S3Lu1`^7bVp6RNHp@qXTThiz37#@n*SdAh z{KOcICsbT!*_bNsn}SZu%`Z_b0r$6C^=~i#Ya8oLVv=2@%GSgw>fBGP4v5S283r(%CY`ueXFSb0;_xWQ^wfG{`sx^SSV20 zSxwiqJLA;(91v2wfC7DPn#m_Qld%X4GQ*I^+6?m=*o`{@Lds;roa1c|){r|@uKV~0 zM+Wju9lOh`^J}+KTS6;vl_(|e(H89!}lqo*?Vn$*JV;5T>XwKAXXTH2@+$HXxlb`Ru=}udFIOhe>OskNyPxsBp?wCG^8V%nt z5RW?GTK8%A`p6}yqp*Csh!IEbX5gOo;Zw(!CihLQO6gzrRZ~Ojj{#Ty6RzgmxFSt+ zaa0IqWDm?|jmSw%#BbqB)m0LJEJOrT zONU%#*!kRYV8M5p%as=*d5VhQ+uh0o+s3i=N+!ZPKn&b_X@R$0WNnvHCaplzID6(i znJ0R~W^^rpy-AqDkZp|s-^Pz5e*IMLbro!Vo=Jv3ob_@`5R|PyC!H3lc&lCe55AVqBSlu4GBk zR3MdjL)7eM*iS$uNlgX}qR!Sat*{&Kzg6hjVB6RSjKHy{4x_lp5tBtQC`}pUb3Z30 zwc``5g^n4M0^VhtaZ82K86|6GJOG<%krE|>a`WWrUPk^zHO(pXzycVUd5w99N0H~* zRVzWcoS#RD7LE8D-9T8(-l`^S_|tXo(X)K536%#;p%#2eJ`$k(Q{PUpOv}@`gcp~j z(8j*D3#HDypmN&%Z>HvMd)R>L)jE7txa5Cdd4G3s$=+s?c`26m{b9x0q}L`$MGl%* z)&7S~ON?imbeOX>TgxA#Tksn50C$xZ!v)TQXB8v%gwC!0X+7AeV_!ek}Lyyo$ z>9qSL*adW&UB2aOC#c8v@rSdBD;QtS<`T$cnQF@5uZeh3l#7_Ok+vyugtG9Wz5WvQ zFCS-uhV|G0ky-=EBnwK%*I!MqRCnFe zlSHz=QR!N~uEOg=6P7)w>}5lUk#@D?&xIFk3G4C^zTDZvRw4j7G|R96Li*_sE@{J! zXR_-=TNMk#3lUfKx9UsRE%ys7ce}y2l|lcf<=-}+Y1n1A=Ly746NbWl;iZAtP64^| z=b&~6P;Ka!uMCb=24n-D^)4rB{)YU(nxg-P^ei5OTJMJ)Z+kxiys5pRNGi>>4?c0* zIR@ynuri4Vy=d{{57K`y01ot$KaavBXDIHbkT41}jCFb<$JYOssb@#6iX+Lx1veR9 zqm6%YyJtNK;U9Qk44V%31r%Z3LA#Mp*J@rgL-Fp+COWZj8CKLZ> z3a_(g`R9IS8guo(*f2Lzze`ZwNe@t2w45nA6dsXX4>xq@u<%QM1z$K{+&cmad=_U~ zvw!2E-;@7*@Ac8Gv+oghtGaCx%(oaCzcMYtb9*Us;kJBHGyZ^;o*J2L$2G6@lDQ5z z4I_TQ;dwV6!!Bw|`vASvcbk^^3H^6sC08r;sWXh>WQ=m!`Tt%3K~x`9xYu%Z_-aVVk+OT6jipG$i@DwVP{;^j4;53f^hjyDjx=k# zDwMxF>fPfr|KELff+jOM5wO}LQyqN|JE8LqjtliHtH%~=NAGPImMA>5{3oV2?o?J9 zpK#$JkV+E$=xy8EY$DH?B1qq3CLDWX4@({Zq2E}#t2u*}IVZuS`f|;n(eARsKtiqO zY@mbp-AF??>(1DGt%}PY8nj$t%`9r>)8eYUWmJQOjepR%{zC9@)opp@yP)r=ENRP+ z=>(4zuk^+uk|lf=WR&#GyH?tKC4B2;2=d)Do%i2i_Ym9kcXs6|2reHB=U82W`i|%i z%l^+S`BNM|?5t5P4D7ZmMm`YOj4DYk5P8rn7C4rV9R{b%7n_~e&Z^@X%fX%o_aF#$ z-HMoX4rw;YeQRvvSWic%$d`R?P_uUvb3beWll}e2|LI_u|E$pHrIVkP$sMHk`I}c~ zDA^zFp5u2Hj@NY912gA2VO~2}iOI(De6+U}x^?V7gvT`XTK=Qan@n?otc&R=I}vBu|dt0@!O$O&&arVt1~DLZ+(OW9bJLS`zHAOO7SO ziucszasm?lNQ~%NZUI|&qhy0dZ|t<@qocRIi3TwfL*i9GpU{luiv7HG&vty0*6oB{ zF^nSgBNYPC1?ingl!~7H|k)) zH~2nQ%lx*{CkXT4U+!%wzbfiE%Lasn7oB-(5H(*3mInDe z#qkC5%#$CVGoL#s^HGPlU&WxOIYQ_6PVx`Yg8uQ22r@z9{Dz`#SzpbpW)=tf3{;K# zCj#tK>^AGzC=&)5Uc&zDSz7ku$pAS# zHLQZ+0m{9ff%C6h>)OU8^QW&eaAMawO3`mKz6)gWSXdO=S`|_A>zfv$xz->ZeV0Rx*TEa2OdR9iG1m)QP3Y~*M=rb*CE zf@-xf2EVR?FqD3ZF4QAbPr>&577i)aw-Ah*$V?LTh)Gb@o4$c9 zxh^Ux5S+DNU%j5nXK_Fs^j@#AN_eS!ChqF9V1%j3zA+4aE@ZD>6*q(1Tqm2{XCR_T z$Irf!vO=z6)oXNQQh~~|hv|udBEJFlNAKdN$Ig*!VLEl^E$^pIHwCV1Cw~0Df z6@Ggmx~^>=kQd;xOf@~+rbB2as$M2b6sMlZck50ty?8nG=pFQa~G@jIKwT17gxkC5a)?CpKg229?U`G@%8GsopD-Sk+JA) zX0|X?u|Wj{$GY0U5wl0XMQ)vltwD~J7(>UoNTc%u6KH2v0GBtNNJ?jK6Jn&KZM?`Cc5zFJ=Hrf5rkuMhox zpzL%o+KYX$tIX|dUkknD<;(VXJ?2sLlhA{bwp|Ho3|ldd#K;9Ft6(B#9IXa3lt9|; z>@VNd0eKI**eYV-6m-2X2E5$r@?h?IR$iQ;x7-&9gutU-$(g_iF>*;nYD-jK0RKYO z7ZPpR^;k7JO}`D}bT#%QbxSTa1wHf;N57|eDKNhxW8zJhPRpM#@}!6iIWe-1Vz;h+ zoZ4+YTdF@x2Q9Bac&q_TwOt9b-1@n27y~DgJm!~pJ%?ba$O|xW16o*iehi7AlNhq2S5N-{+a{};f*dc7N0y&aJua5Na*#H zA#oDA$m?<+z%9?qr08xvh_&qRgn_P9mK>r8i)O!x#WjjY*<-**DwtL!-n_&ospG92 zecgmTk_lJpm~6+!ne`Rj*=;L3Tsv6y03=@Zvj1r#+UCKR$CY=>z%U^*ZTHfUzIzkM zTZ7`?{>X7axSwKg@jg3QG_sW-SKn*_G!ByRZ-#PA+Zi#o_)aQN3O1#>UvH_25?%m$Ia4H-*c7iA7SoEU3Z-FBK3QVDmRn7q8|zMw9$n z@Yt6NdKu=`@cg3Ivh%H)+FWJ5O!laSJCouvjADXWGdALQc8^_^5#?2Usi!XQx+!x_ zFxA!OCClitW4a&5ppWTU89qMU>gd3=8XgMT)+Cv6ib0DswmZPSMFy3Vz||@c$476w z*j?s{+47w2vL}44C3{mWaukTlfl~l)+sV7FtGxSN+4lDGILEyGzg|{-sxngk8U2~Q zYxwyA5@p{bMV-8JdHz^KqRw?SU?Gb!Ghm;2eT#bRH<6x3Nn=YYojHlH@wS!t>D1N@ z+Fjt%g8c8&>bWYByE|+vO$q)!exvI=pGql!Qd)XssImsCSQB!d9{M{G8eCXMJW?1U z_Htq}qOCv6(r;;FVuxIKhDjZafK35KV5 z@-do_Z|kO}x6iWp3}le>H{$V;#;pk++Dg@=bk`iU@9tGh8wBmK_?=ymwa)SU_XY+z zTygb~IUmkZ-yTkr&4amA;Qqt8HzOiD{a;B7krj<55*yD8BFAN##Z5dq9D8Q@wnm`U z-4~-Rboh=5U4FZByL#oswlaQhUi()ywJuAQ8jSd4LMtg_Tlnze{mRK5L}K=BrsvZc zhO!-zOlVODDr}?g^0i&4VrqCydPne75F5z=$#LuQI=}M&N09^)9yM3;YNGGa4Q7w;6S7&Jj zy<&Og#O?{sFnMy4=cvdN1(Vgkd}e%!gdAscOBTmSWb)CXP2}pY>rv~xFgrRE@CJ$~ zq)P0Yd*qa516Ht8+qJ~>`PS0y&nEbE+ROh^B(Bv#ryEbVDg#eL&rtVahz=kMy9dfC zp<=zOl=2vOSV9aeRB%b}SGI4vP_FnJ@5g4}pOZHkJ(-H+02a?e7+hDwX(vN1!ofG6 zXzcpPVr>9WuqSaD?ttbXml6Aa!-VqB|L&bFs8swB4>~$(wWQ6rQEr(#w1BJ0{_Pf9czg%F z#V;^iB^`PE`-6%b)^A;c+Hw0HNKcQ@>1L=pe++s#2IX++_wwm}&+HS@F@O$q9@?mf zJ8m=x`HlC{O+b+90dJsK{op_~buot5r7~NNLq&sv-_6dLIzR+I1;f#4gxM z)AycOZe}obV>!GHUVPsxc-d~3_7a2YeCaRI(q(z1&;Q|5?rEK4m)gV^FX+qsM85l* zC+h7<;o_H9Lu=Nf4`%MN#&~}F=gxCSK}^Ax!rue=Nv!y@!OC!DXF=cVO9{hUYiN>s z3v-++Dr7Q3BXt_bTCpsMK;I;~yGt}0LjR5B5Ys7gPRg#DN%-%SWgF_l&rQM|goejo zFjv_Wc51q0B?BB`C>{@~`q@b#QJWxbg0T73bS7;&Nwy5;LTe}>7mZiQMOWyin0Z80 zuZQgUww&3%42`jd7kbO`5`lR%cO$&U+x=2H?#qC6f$!8=KG+v$yz8aA{83F^v(ZseYZhPbvtuj@1sFX2g9?i*?~{ zBWPtkgrG>Cx2M8M_ZeWI30D;IAz#Wm>s-gm<9qrpVYZo%h~U6^CcU1@c3()TX&PS{j zDH}W*0%oHj&1VxEwU{m3j`X06@&Q&Vp5Kh5eOGP76EU%pWLD|+k8NVz@V7`)N|cHE3wixt2Aa&QYP zhQ*)5wpngPIKQ$HM(z2*t@y7(m!>x@*#A{xiwQx0k_>}4%>TxjA6+~{?YRa7ODFp0 zj_s6~_&@ym?5o|btm_(XCEudaem&7w%H&bytJQiAno}||pYH7NPIKW=lrbX~wDcOL ziFGo_@RGJ_z{@s7Z#EWXHP)Q&+ORYt`(kFDNWbCm&Uc*^F@K3!XhS4?Q5s5q$dJYu zK%J<4nRofHm`WX-JvjiOC?k6q=dyhi(pkY2?`H!K|5bjh>DR@r@i`iqgi@4|OLtW> zt~)C0AN}$vs~!>Uea~o^jeZrByrR0~DEnpO%CAF`*>QD*?0ZL9O20H1pBJIrcONh2 zIb}e)&K_0xoFN-s!G6Nw+#vTskaPbFn-IHM33X+6uJlYhdL>4}@^jt5AMCH<*>yNT zLy}+@v^voiR7JXAaqht;aUCl5l6m4Be%I5sF&DhLbh$ZrEwD=eUsSFwHz;Xbeh+a; zU2G8Hf*t&!G3?cbMH@~3PyE90;R*T?kmhX&B0p@W=$j^wH*P=EJL`VjVw{+#3xUz- znmsL{cMyh?WwpjJM;o3LyOZuj3ZxjFlTWF|fK{HW#`jAo7r4yCqu^{*7yM^mNPS&_ zOS!Cb_9(E+d7OBp;2~{@&jiM&L^sDTLKX0+DmiI+?W<2l2^5yVl1};K7>?>CF$98}) zJ<7}vsLEu<{u_rf?Z8=@FU(ux2B)*@T!yX60(cYV9w|r_l9Ul>7;zPxxAimBRD*1c z9DmGGlA%0lIqDLBK~_y38#qY0ZFVQJRlg=R+rh)#WQ&~Z2h-gB-tEb>VKD2)1J(9; z1r+LLUN;cfv^_y*W+1Hlqx5=aFL8!pjwtTo4qSA$ZxW4t$}jH1x|nZE+rUt;S5^M# z50)9-yE+`6s%Is{qY;1nX}0#32M*(-d02W#P6-KZGU+14x7;dlpD`^b9IaK<{a zuJ%ldY8#F{UnW}FOx+nkDr=0V?f3Z%gy7g;x-5hsQ}u-LX&Xiv5aKFpp%I3O`rfER z+h-!s-WoxoadV=Dzfl?5(MJ4BC}k^Pxw%dNVnrkPnU>#L%kK!jr@(r6D9h38_gX96 z^0up9^On0PAODsA!BjryxnHr!q+E?o`-eB2VHYwI#4mcMQHJQD=nzskqgi~G*O=7X z)a85r%c3o$K5F3|P!+oXgjr=R+=Da6zeZvIP>zIn(K=p^fCZsi{G z+q8Y4bDSD`VKVU_bv{WR&9Jpc)Zc~Dc#;Kwz6n2eC_X|9N&-q4q9>(9CnoGbr!(Sr z_x%L7=wyYtkSc*>)e6h+a8k+z#ep2O9l|d{3EZf2Gio@Z&E6m1-S09 z%e=3xY^_p#L-ethVK7UN<1bD~rxQB~Z7}LhX@Ws?lY6JV zQrUNhYCxIYH*JlW!9^$jg0fE!S-jVil@t&Q8>m7LT~8*M2p?!j*i$v8-ksR1?5ZIT z7rnXyp@6!+Ra-zkWZ-yAQH3N#x|gYCV*J=JV|z1bBwTHq`qt@L>87`^%4w(f=?i>ZeJByNKHv#8guZ zqpb6&AH3+nsCScOOJUG)+;y^XK%9vW&Bv(?E_x3IVuSoQCA{aJ#Z+5-LXy?Tm0Z>6 z2*c64>uI}l z{edgeifRlF6kaA)R)P`k1j&#h#dZ)smk@pR@zOA5T6#KOZz9Es;3o)c9cr03EUn;1 z(${JP>-it-2XGQ~in(AV$9rwJs3@Tio_iE*y$NLFTzD5vlx7E`8+9QEJ|XU z67cAH9OnMwhTY%5WX9;{OO#2o#7=CVxl1tnsuHC=4*3jOu{8C~p{cIjN>OZrKfO(* zOd@ICdYT)aAvS2~uc7lb4g7j6PJLc*z%7cw^?KR|ObKf=*eZrr5!B?51AisJ1{$1wO&mGPH4|v^+RFnI?`$O0JxZ8^hK*ev>mMXkU(H&X(mh6aK~|w4D|`Je?X$5db2PQdB1lEe43#xAV zWUQT&|ABk^_$9{bqZpwzglxq1V7StuB>UXm3B5boZM%!2kP7`_twIcQT`u%8^@&OT ztmD!EgCqXaT+f7bWv+P~9|Ybx)O{rUmxVeO=*`uGU7n*R^??>C%!t=S~yMzhi%FT^9l7O!d7+rS|hk5FYYS23X4rvdH)F?NV^?Om&{Pr9-7Yl7TfX1-E{ zN1U4D-~0{ry2cU7^5T zlL2?;MRR&wb8B{y_$Zjk)H(6tyvM4G?BR_6Dk{D^(~mPj2PT}tApiZ;xz!ExJI2Wm z?*&nIehixl)8om7uIF@s!?em z=Ze40Dl2h?@n?d$1Rt_19M4d%QgC9TB${R-`d+x(Loe_WS4Mn%N0*Wsfif-a}$ z5q^&zA^t&WU_dUs=M8~{8%>E=d=_JsfRraOGf71;Ml>RMG;#uMMz)vaWAbf1XvTju zY^&AyhAPM=0LWwokl{yzRyGEp5FZ?;&sN&35~F=%1s5ka9CeB`zGZCWV+=#_(Hr3%mMhAX4by)v?!7CM|Abiq)wbO=vU?}~J@{zE zOe(aIh_Z%k%0TK@yY*;!8rpY=Vf40pu36Y|&|*PM3$UhC@^ye+f_XeFG;bfZZg}m@ zn}S!-c>M(zVa3y_MigNuyiy(#uOvk~qBdA?4ID=Nk{Cib_ON||+6$yMNGfCWXdPoA z&tGUNlinSS)=wYcdbEGjaJjOg3<%`SD1Ko{Rol5BxDspA4=DdgaMj_ZmxWqFKCu@~ zC|*&&@y$b|7-8(3V50g8E8etiA z=RKs?L}+q&k3)|EeG=u}kdUv{x9N+D2&10ZhfTf}Knw8J5laz8q+`LqV~$5rER>hJ;E=s}Dg;CH;DpUJWg}1E%CTDaA&8V5)c5 zXaGU@^0{M`KWUPe9(*01^C?SIU>$Arv3b9QcMroyX*qfQ?CqH(I$^BB`fnMu3U|NqT*r zvI#8p3u>4Lcb;M~7slvqr@Yra4t6LJEu*R9p8Fj}Jr`W)PRU``Lp0jiJ=gHTRNIH% zjjvgQL2+=nA1V@-5OXz&v4V=9k4R9Dfs~aL^B+QN(htS?5|`xV*3RB2O7z&xWiZGe zk3|xE?`QE9Vp5f=NsZC}0>f+SsqQFqY9anmm4I=1{K+sN~=MD?pL21fv(b5Qt+um3)`$C^H{zO`D0{58hd zw(-!cZx#(Hl$+KyB1(OpK>UVD{;;D){=_=3`AWVmf6y0jT$n!(tcS?MRfpFKj~&kAyGc2HK}QVrJWz^&==nv-7L z{dw~FCzQ=L^cTMrrS7IAUbfhn1tZpOYL&_$>k`z_m9l}9|A*-!eJK6x-BL3Wz>j^W zffHLFPR;)E{}`hv604YP-?!4l+7-9y)1WI)kv1AS;nVZ?iwx_Ku#k|!Ucdl4_bo4#Z4$u zB9z?$?w=<29rN|dV!p2s%Z&6;WgR!ZY4OZg#_ur15ZaT4mM}K{s^bk;Leo)uDbAgb zj3%#?DOdXzqZCwX9hfNe;jOx4M=86!=hy<~KqLVsR|YW3T5S57BjI11QS&nI@^<2K z+m&inoWlFB;Jhnx{J!i+aLq%X4k)FjLu#U*9CIIXIU)N-SHib0|_2XhIZ0bR406W7BS_UX>KTDRI&LEl%qVjSa{ z#N_YZ)aK^A<&COj+}9@L_eOVLYGO9DVvi-3dYe|o_m7Xuw zT*Jvnb74~Sjo;VZ(wK*|Vs8uc$jW&=SqECcfFp2V17Dg*Gst+}Za+*j`Ln|tYKU5v zD)HK&vS&kvafV;4V%a0Ad*0V%M)G|QH6fxqRPm_`u~mCO4@|^wFQIiL+VAf?eeI&S zYF^W27I7=|Aeu1rTyx4=s7t3*+%};dzInp<0vh64F*rZV@$v1{Zg;1+Nl4VoIO!Mr z@(LaXh^_u%Y8+V7sX8xhH{kg?PHJ%bMc(XkY3ZmK(96Dk``=|&%Hnqk#Fu1K_SZFw@T)+9gE1683M%cYal$y?bz zl`q&|_@%z5dLez{7L#NotEL86V_uDTGT=FuN!r+p%1O^3{ZOL8-|Iu%)t;XFN+cfD#nE@;~dv738;;vG;}a zwES;sYit!ez(f0nbP{pj2~Q{p(fQ?$Kti(O}Ufz=q?Pn7Ra2gb(9Sl8_B{ z2AaHveIu5i#(aOV8{Q;14&+hI1fJ5nMeS~dC`ypSI_$f8CrLPc#fLa?^Rij!0xfD4 z+h6vv&d@bhH+IL4`#Is`w}5O-O4g=ZsUhtPq9UAKP5@QmObNNp<;nAWw&eKt#zWls zE6mD`Ef@p`r>-b@HEiXPL_o-HLYU{GJ+RxVD)qg7MBCM2CfOF-mh%QH|JWn+yXgp+ zyl^CNsJ9x-qDGR=in4v1a{WvUnclH`nA~wx4}MBoh;Xmo#CIK-l1*E$-rm96YM$lfhOGx*>qoRR{dz z!mEBnYf;Fb?pLbG>#+iPfFkW$Mi8vPLzj&$n=6oQ^_m{Lx{us~9@zJitHlO?wR+0% zGIjB4j1J=V9|c$$ifpsLp6Z}fWka)pYr12n=#)c(*2!eQtq z7dJw#uGG=rdGJ=k5U~C<1quBR+(NgFFZcGKZCvAidBIDtGWBI`V%I_A3oy@+CU1lp zY=7;!5+1ez11Y}+c~jxfOwTy1PlE||IpE-EhGH7g#4g5KcJz=nv@+*h!m_9?LIoID zZq1Lnr$nOS43g(InS{6bOtNch{h(&~5 zV#s~OYW^rj3A5zEf=<|vuqQ%o#B&kU@5BKCPyE92b-#zM*W4Udk(^fPu+|)d>l3Yv za=iyG%F_AXu8X7o_Z^-R8w@zL{NSTveOn7-N7>M8iQ5F+Wv-QGb*hu0}En_GN zo>t82H%}GS>rR@|g;0#IWKGn<$*NK!IHRerKL*D=xS&!ijdeK08X{VSeB`o5hR}GX z7-Xjl!?+lq&KwgB%bSO`K(0#wR1$gjL-H8QZSR;cPb|h)QAQg`xTx)ck<}CZ(48-{ zpx6$FnY(F@W_&x*;(@KU*o?xOY|n&_50L4LrILt9=w;v%zWg<*003-xbL;)zMBvIM zU;eber1CC`%JB)0SWeO* z&9C$QJyJ}QUD&?MdRUeP$3#MAlr?5OTdE-x7$^dRNs$_NQTlqzRm&D&2a`i=v8^Qq z2orD9KytR-q6aTnVx-xG+3%t4A_}*J7^>#lvhy?4sw;W^?WG?}RvfJ9k445V{%0uMUFH$>h#4(5K6t@O}7I9d|54dTvh>8nMfLMV} zrsr@Gv(N{2PX?8dIW$Z8mTkIkO>XsVl|1O>PFKv+t6BrF@Ga7fm=drMHH3~!Qyn^( zG*mO&5d-qg#bEeU_pJ2!)*`%X9iQLb#yJDbhVHC!PvzfAKFoQRugw?{DzNCfNdNVJcNPSvLcmFo6!{8ckhXPTFas#m+fuKh{x+6}HuxoKi2 zayI*OlF|XaTtIhtmeFt=RKfz`{&H=72U}wO`4(y!gp7Dv<-+ZJ#j6#>@abgjXA$HT zJ)q%l;q_NCaR08DZp!3$Yl3dZ zY$Z<)p~X~V>nF(P7hsj2^z`B4#|L1Rk##x2%7qPb8DAl4I!St=U#bNU=}VecW#S#sDMefTUmdUw(CUvu>7oB&|zwY_>Bd-v>s?4(2!Zstl0 zxFVcr>mI+piu7jv2_O+*650hYauSx|`g!w_oB54UKIItn^J;-89l98CBfzNJbe3b5 zp_e(u;%6h+zHY;24ri}_t{tHWGkc#gt9FJl+WBIiYRww-fy>>xsCYT-DB}4B2+x+b z3iUnty+Njq@um50`qh>?f3^G6Hx@_B*sNbNu$3OSBG#-NJIht)sJC^d7~OmYRPcG< z8lWv)HKAa6pAp937Rc$b`?68-XXf(TNDcj5GJLC~ETbi!3##YEamK4yVeT>fhr<9Y z*cB@IBXr!)<+eRGP2SGYhn=`6(WbnJsfMMYTf@IJB_w};t@HlqZ>9;+XcuN736u-X zAwNC1kJ-Bt%Q{}r0egG}$a;=S0)tEv5uKW|Cng|l!( zG3j}<txaW_$$`P#^R$ZL}vPP>Y=k=8(8-x%RShii~(WOHMmT^0D~M=2g9YZllgk z6BT^9Z>E~2`VUIAexaz<^8Trl(nRcYPuYPor$#q43k9>>TmPWKyYQ7`|8@I}_a@wD ziv5$wrdT1lq#cxturri3>Tcm}$Fu54WAHNL7e0I$Ml$h1Cyt3*8o{3DH%&JE18y5L zlj6%X6nx zKk_o3{(CBv&o% z>+IAD3FMz*u}YSeS+qwJ@W7vvJ;2e)xu>W4ZD~N1;dy_UZ{njIilUrnlDsK1-1Nr*SS)??m95gbWcs@rVTpX>6$G-l85AnE3fgAWc` z;#oiyV)cgR2`X^OW#O;iO?br3HEW(YlEktd)7f^Lq$FDYz!i5aG{0u!Mc z9a&7(^dBu%S+4+CoX>sxGgNkZpp5*8BV1jfpil#zr&GGD|#5ct?`rL8v+Was{I_xBug9}8 zet6tl^?yiTfCDxuUQyke*A~#m?`%bPZoze7+E?q%JPg=>{Rd?FKEU(~YCW0>*%>;A zY5Wd37eP)t^=E^`oVv5?_NJsdm#L{6(T1`0_W8ZlO{%EZCAxY(E5y&ta`D25UP}=_ z%sFW9Nuzxtf3W}7UOYiUMVE)WA&-rcUE)BYz3hui*$i&Hs?== zG}4jobWHIe)6=dpVaAGv#%pkta+nuDsCmZCe~O)*Lih4LS@Cm_*{}TdblnmNz?eH( zkO|cKex>Y$PXOtU{=j;J98EF&?Y!fyBl_{T^ZF=hF{Komf&(#@Nfm6_n`HUU_$egj z5eWKQrtXjKrw&;#eVt9}MVqPobLhCC3ABqhek`rE@7Ma`Eh^70XBbp`JzDWv6V%OPfz5qB%yp1y;dz1~61T00+`n7M9PVidEXCqGk^ zoixlZKXlAKfVJ|y{1$J<@=2m&8sxVCyNWa;nKxD zT?#{^wHR$kRVU$`bU_Mv4>GMRgy*0M1ogyx{Ei-Civ}EeHd9`0&cDn^%O_d=p+JfK zMZ%vfyly_Xpbi#iWO}noXgGCIi9mH3t}f=v3bkZ9q!YOyMBnV?jl>pbR6Sj-a3X|K zpD47mEaCO+=7#J}O5W_5Hv9f-({TT@P%)_EmTG?)NHkr&)HsE_U}Uw#i%d0Of^9WrY7t!nNJWqr$`p5S2tA#B|^^e z^)L9d#$#P3<89tNKoq4~dgYFQ$|M87ziyz<8#=>B-Ems*TW955Fj<5B;ymCYZNf#Y z+z0qsHjJ4oQDadNZ12)6_#$H`4QqCR+4yY)Hv(XAP3cBORoJdYXGNFy^;w!foP2NCLyOy56p+U-G%2#dp`|HNMT_6J&TokUhywx2* zM-5-$DQBcpRJ|TG6wT{m8nd1Hp4u^Y?8ALNI!;}*K#peO90{sbw>L=^az`nJ3H5xQ9{3ovLtIroC#s4@O zCgZU4(CwXc<^X+A9&z4I3-KH>cy=j9-FPAH%e7mc_}brf&WZV}hU6+wf5G){$vm$w zfRb$mg~^cebBChtz$JUbfK+v!u;m<>K*h$}FKsk_qydg*BxnyalWEEGC@|?h{ zWB}>0OYC;tLxX^wvAe^~EZ;oW{h3W!_p|9YxB+vz2Jr;{uq#+IMTk80*-cs3F#D>p z7@`&nNP^0~>-aQz?dJS>ZxCe$a1bojlA_U8%w`vF&G=qAdudB?*QC_#6PTO+ez7`G z->3b!w$HM|I;14?w2PLQVHj|Xj`VmG@fiOIRCl=@v7RiY%uv^n3Y#bCGYi?7W;SPH zh4gfFnct`GrXB_tvY@90tylhroR}TkR?iCG&v6jE|NX0YqSL=HJ;BIsnkw*8ioTR; zWQulSd@{wbjoDX(7qhaydyu2JGS=ji_#;3pP?>!9+E*IJ!d1K|`*VLOI%IL%qUm2r zXsRghJR#p?82qsI3*C46H2t5{xId`@Sy+uG1?^hh%XMQP*`HRwBF7f%U8>k__=;N# zrw_4XE=`){m_EnO6N?MQ*L=_`1v5tBj& zRqw!PH%YXZFj?2$NFnIb-Fn?ytVkS@m0iNc9ovc^!RW<`ZJ}-vZI-r&8xflh4CdQ% zIn%t+R*P^N1nnu5jrT*L)~qE8=Yp8vgHt0i8UqyhB7Ycyh=Ly8{XpJ?(j|0oYRU3q zyX9Cj*uzv-FG^%aOhJjEhSfu^*lePv_b`@pz8s!E$Ej{erO(p2l<<&?i*xkU8BiZm zM-9GXsT;CAhJ}OwmJiKQ|66M*Tn|UHxh`BzRPbX{g8jZB+;i#Om(^cV;v$X-Ii&DW z66?tq_AMSq1x5>i-lZ#~TsPfkQ;HeFqP)wLgYCL4=*e;FI@mo!juQ3ntz&~XyFIq1 zmo1oxLf-ph?-twmM?si!;^F#0D^>Cb^`u5&PccnhRIMvoldrH!&V{Uy_G16q*Y_k^pW`+R#72_kzgZ|Vb<(cPRg1~D6r}*{1R4`V2`bTY|REpcyzK}JPxlKkWPlM zkJfpHbpx9Ar6}bUrfS2*!dDB)vxR^Wb(E=?af9Jj`EAab!Ed#zh83X zxr_JHy={=GBaDI~cgtLGc`+vqjWf!-Cdzq zsq)O826p-ts~Yb`))cEF#kznfQ6fy#Sa|&hg^Jj7+%eo#VTbp)>@^y25H@r4oX(DAF~{coq1P-qd#%mX666>J(iLG zMB!xLEPO(6*evH}uE`7Yt$PRQD@m68roDjdK8_D%xYNCoS`*Tn#3HZ#?G0Bq#Mt@n zh6VKb@h%h2jMTj*(}vL(_1dG)LrOCR?%8!foerHF&7w7Dna|erwK36Myvd9 zjTiwHbM+_E=(nTa-Mre1QOkwrF(~?ZmV^#?{kS?$=7@jpa-Xy-d5$9BmF--5rM|4- zcx`_uA~5^z?u0E=lv%AbzG80n&^{*<{v_>D`}-TC^2mDoH^8o;qs@dS!m|VZ6=|J< zf6RnB)4g5hgDrMbxsM;lkgK*j*B&Fm8D3?i+%iJ?e$N|HfVye2-RUBz2VW`(X8KS{ zRYOdimvZ(=dMfRanLboffui#N!t=ZyNqD-$Kh!;hSNXaEy)KJ#{k@cRxD?Q}py|2u zb-`;8T#|*i&qcukZu8fC;|B77M9Ac2eD>#6X3E%h=nO0Woy-{dim(Kq}hxE zWC&L~NcOSa#yNi4@=aQ|fOU$?O@vrS%6?ZR0#GpTel;?hzR4nsZne>WvZPXyioE|| zN@m!NFS574MF!NqcqJkDRyMvUr57>mY?GjRv8~oAtFl3H3sSnn*h9!M{BvfkzsjKx!ih>BP+JfIzCBrl{fnjjQAkuuxqu=8`{*- zWBU}il#0CpG#7W<1K(2*r9M%>TRo>G$JTRWs*yrKmK*Ij`my=M7SB z`4U!qcn9A9#G%}%gF$YOHe;GX&lE`H-AVz#m)T@4492Bogg-iZ-{}}xzXy=yM@h|@ zNaKCb` z+8;#hM~k}}e_&nCqe>VWKj-^f(VG{5^iM=5D|r{6y4RcM;9t>A!Jms5SEb29>tFQ# zUQp*i8fvbz(0nq+Cas5Nkj=*+EslqzZI}gf1tP@W_>sD5FHf0YxC@}t<|#QS2?`|; z@{g6CLD@sGx>jNFLN602tK5gx26l!K}>2`Cqy5ng^CRYC=5)#WLEz;;p?>IbPc z5`>f{O59BZ;`tnV6oLjLl`2JF47WLD=vh^I$$4~DriL5je+&yB`FJs$FClZK#YlFG zx=bB%t?cFJ-kls5N^YGs~Pe=rG7h%Fsee1?VR{8HXK9 z)@KaOA0&8<{gL~0=!@Ocl*AD~(I6~59_7uaI1Nrg>JW1c@_a_M6#`q5QPK!eV;es( zpH}HONg9O7d$+JW^02{ckKl>CM|D`MYJY21Kh;&H#g)%OU_$yo81v5;;60!lul33& zsE8!Qxv-omWboqGR52`MGZqlB?V{Hl{7Q^FSJ_N-)3 z(|Tn4VkA{QU9nE+*Zwq;d8?G=QL$@N^<usYd8pjwx9-EiSvT?Hj=7EcKJku9ehM7pAf0Q8>;VX3h z9~61Ud9_~_)Acj+pu9|^yzbr`$o6ADu~KnGu}z5?_E)w^x0l)}K|5(a$)&&CJj2^j zW}NeM&Lv)5+fyj)JX&S2u29 z>paK_^132rS-v9oZ~UILP%5 zJN5VhFfRetCkW&u&cOO^=u)d+xTiMX>2;{QZ+Cl5<@y_MZ5K|FW5r)e5YTy&QNPKz z%<3f`B%uut($k6QgE-%+As{FHrs$EIB35i=8Iv>u;3yvd$q+<(3C8sLGi8Ai4YQCq zw9i<a4+^6F7<5ccPQ(%>j+xpQ@yDbhL}29PcJRG-itc6~ zH#%5prIa47`y(&(z@9AVx(CcnRSM4RdGv8#QZAl0%hbx_YRZ5Adotsp|A7E+Pf`V6 zwy#+P;pR^KU*GWjzSqNY{R?zpB$S8-kjI{#P9LPwMnIY?Ju8Pba-8Ab@G<4jpP2(a z>ey4;7Uk4pUG&L4WLbr|0d8Zv|I%DLGBh^j2#*ipyX&D;?xjal1|mv1TN?p;R)!T< z>weQ0QRG0y>f8eP(;A@xrh#89pU7!kATedZ;_5B6qQJ^F!<` zd&Ez9<(f;;S3RrneE=bu+Bd6Mb2zlCN8w2^0Q4JnzaLf60s9P-d6m2poSNXc*`9J? z^`1lMGtga3sPeRbwJsdCv~hp<6-t#*i6;$S5q;TP5A9t3fwEC$s*tEsVnuXS$t#h@ zP=VC`>bx(673tm#RJdMYYTvd3{>H6h#DAK%bcF?PpUY-OWC&8{0x=N|%_0y%cEZ zu+D&S%VV&q$2j;kG{%Z3;hsoBbI2F%Fi> zj251;(0O$VA{=L`0%(vlM zx6rrm`K%UFnD>v0IdolP`aP#-42wm5eYZFWLPe1~igC9V7}6M{C=qqhn8IHkFvkC# z@XLvVQRQ3aIKDQ8A6N+Gv86INS74da#`7!rk(Pv~_;THQgf_(L%>xIQXd!wGynM)H z;MTZkSLNypL4!QAGva@fjD;&!!3E$S4Z3s0p?uAk-sk>JgK`&E;#)Ws8;YmlPB07> zk?CH^+EZSs=BmYpDnyIAcE6rB!hOb^NXa1#BHh~Q-aRn3E0^3unvN!}yh6s$SiL~KMSWCZCbnU^4s}{Y zsuY$JZ_Mns_?b3zF6^a9?X=@l_+mTF^CZ6r0}u;~r}Dy%^}m7Q+St@;^W$^JuS7m$ zz=%^#72srb@KYB8A!yaPQl70&e$p}*NxEd8*tA>bgbQ?<9%&KXRW1px)B-{@y1m!A z;us6h-(*W04;6b_4Mzb*oTiF15}1E=KtQ)khyzi%`_s!Q_qf?bL3JrT@c`fKW7uLP3;_Rg85S<(cGdI5Q*q?+mtoh8e@td4QVdL!rRv_#;JP)d z93UA+mTgnw(vOV`0+lODWoT*uXi*lT7Q^P2XN4|QY=2M zi4Xxnobrpp8K&v`YC9k6NJMI1AnoGw@CkK9&jt}5j07nCgs1)=V?ivKNA37Xx>m@B zVAO1jsZx35jMMV38qdvb;)euSiI`6=JgM0Ljjx^MSJsEXpw-iFD7gyEdlrs~F3SL@ zLf#)jsDD$QGT-|qrsp?2?IaWnSI2wp|3%?JC$&^$2|a+RPfYM1i!*uLYVU8{j%DXT z4x|cKr}@^py*+JZWCuhNWL zV;Xp|3GE2YCh>#uT0yy(+ga*&?a1bwhOU+SSgHz4U40=8;L;Am^OLI{X;+}b_;enV zB6b?QY|rS&8#8-&0OVKC8|T)fPWHca#*bFoO-j?yagComWI~GP4QR7u{lC$f_k4vWmxa+aPKop_iy55l5$;hg3VRPRqF20!D`0d^oqAFzF zG_~=%H&feP=mQwakDI+2GK^QDsi&W?ayLA_l=J%@CL~XxA-$Q=cArjCQ@+kAYC}1` z#9?l2G`eK77A;MK^L5lC9FUQIzX_LRKw)IO!jS#VKm2Y&P-${}EI)=ZO~N|9i+QXy zBnm}m-N#z27*&T_j$iv_*UIhfz4%m+WhTwBdW-1B(;N?Ihi(dFkE(Uix#?T5qXW=V zToO?hqqI!_qN@Wvqm#DwEkk%*E0do^9AYu$IaC;&fTw(CvBKc1xH7c9qhpa=^Ml$p z50`kXK3N2en{#2zvWQC?b>gXgmF<4n@UJ1)()!OXDwGs_-QOs07k~N7RP`oe>^{W= z)%zqxCburY;z5{}{#iR7+5H(56(-<-;eQ-}d>jCHaJe9-v}phtEv(yZ*PR{=m2)GD zNQ&6q2337|O#i}w_Yd9jL%_uH+iRT%Ih&|blRN_cF&?LvB`g2xwM7h2-7OU!lN(s7 zmQUVCN_(t@xhC4iqXqCZP16|tVh>Fwm~BO5cx(*^KJp>#+d$B)n^OnsM`ziYnrUIJ%d6po4O z6mF7e@Y&?FTj|NHN=dcYtxpoyD|&*%RJ;02q%46FOr zZ=BzZuP5AC&vq}@`-3BLgxf2Peammoqf5BEXBt18sMC;CkEBebZ}NE-88G{Wt_`yg zmtp26IBP=ODUhlY^L`&3IdQ=Qxh4AT{$v9^mtkKEw1WL8`!bZ%j2<+qr4|E%}(J z1D3Nv0Kek_4DAM_>Q!>uDg`sY8_k1uLiHWPCMs_S>jt>Ef^!Uiyu%j%U|^lQ)}8Gf7;&}KR#K#P_R zYz%rIX;^fF^Fq#&Ctyq&h&U2M)`_GGMVQI2+!qYab8G!a{*`GC|MT#rt2b|NSzP8l zs5zE$MDC^~=S$3IYIc|-i&Gyl_UoC|u_f|Lf5*hV6qyWvX{7_Q5uO&FeBq{RQ%voD zFzsGclkA7{P!dd?I@~sm0SUYv7?FvSG$I+vr})Ii*n~xwcO?2O5eG*8R6m8`&sbc)Z@=JCH+IFx9Gj3ZJanuHUSp1y&!; zFwYQEl+ew~&H+XZc%kt*>Pt1R?X}hCl)iq?&zLUTr;1K+@3_H2)GEKq6GNOnS$bAL zS9P-K-SY5bv?>`yHM`A~5fD&*BMpeIF+N%IYpQx+L+*|tL)@Ex3gX=!xYf?^MIe{Bi+rr zhdKV#JtLGALqXH8-6t!l9Rm z*T}Rv_^sfZgixslg4=5?=FSBtOWR_PbV{M-51_XljT=5_;F}7vMk^3l4|b$f%=j_g z%P1viKV68FI54}Vo0lO*&!lb4VHV!oulQ4+o%lCp{x77bl&>`pA=Hod6T0HftCi#q z3Q=P|0jh_oFss&i!-Bz~ofPab!SuFYco*xuoH?T5Pi+;!dH`C|V z7(M@X=WGX)IC~Z*@$!$Pq4L+k>#dxwB=@1joRo^o_;kBBUbHLK>{7jXUv!H)A98ZB z0e1Vd(nuY4re2tta36I*3Sd#M{&~CLkb1t1p-j}e^+j0$=}VVL7M9BC#Q;5kV+Zyz z$bT&5C$9-?0q7Z&SL$*oja{`hZsby*^S8-QfxjHHlYreG+Gy%Zyw{QUlzghqxN~%ZP=G6Wn$E>;E8;X2*rhrgS)@ z?&dRt-eC9oN(^}GIHaoDO>c5_de2Y{)IY|{a<&4fXd!B8DVcgd+fJ0RRs#)zg3%?}soT{nN~ zGa*?rL#v&~W$(n$gIJu?im~;65~>U4y=<76Gvz?N-sISA&ai6=}4efiqYFsn@mo^zo1Fw`Lp{Hz-rT|}AzDO|!$b{hWqMhz0f8+-$UqF9@5)N9 z<}Ik?k7_FB{>{(^{v^03w0OGS;@1)psQ5wRe+I`lRVu!uL0&Ou84>98@~@WDassDX(g%xDF#_Ho((6@h_Svu%<5R4;N>24$wsrRyV{kS z1RK`7rZ6{QGN7MR{So`}DLo^78q4)AK~VN=_8E5tV(k8xg)@_a7wM3S%=62o7EV_< z(fPADU4Ci&TVJx!F32)bvjZ+E6jH=kXvPQY@1|#kvj6qHDKBo#o-s!H&{;yo%q)xJ7ciNs8|1Ny;v@1x;PThB^^S?RsCQ-e%+=$$89 zpp!a$ln&%lC1gc*_hxbTeDzb;Ke<^*u?2R33O%@OG%9dC|0-v}k{sM>=^CQ($+WXo zmC#a?sR99Z(_n&%r)Si&5kLi>jtPZImr8*7)-!~`RqFsC|2-`)RG{gGfdWBV#;%_Afs#wO${i}TwWylHx!s(TC(aP2dOVRcfg4~ea}D~C4&dM$ zgYW(CtR-&-$e%50@wo>7nCQY_w%@lb%4u!2!Eb-BU41temA_3-aXx1&)&)?0JIX=Z%IrtV?1r+(@IR7B5W^A`lbGE&NDgKodE}( zchkHPhbg+g()UG_CAZXK`L7R)c%u)pJ%N>cl24)(I_3j8kN>VWPo$C4l3NDIcj(E? zKrfl2aO%L~8uZm%?mCFvtOm6JlGgUnWRYrrQJyNT5{DV387AhesU>jal=@25|FXtV zK`^p7%WtF6wQd)o;x*(FlD;xK#j=nXT-ziRQW9l6ub_l3xqKRWS zW;$q?7hzM|QCe?1vH%_rjnd{BR`0qs;j2`zrM6-KFJ9KAAL`iVzKRJTlj_{cbiyQ^d zXUCC)oL$SLVVfc!9Hw#XAg`n$_gt#oxweqKG)^OM$vaB#aqzx~8biKQ` zZTwC>tOZ0nYmJ0;xY7nLS7ML`^8*ZS)l(0k$g9;g#jghjl0&UB%<-Hr1wGe5}3_tVhH78 z13{#H18hVfV%>m_1HJeiiI!sJX%Qi3y#oFVqN>Q7jx_*eOSq^J1B7AeJ+A44(6q|b zq}YbuHA>M9Z1@E%!p7F_xC^wt7h8GDSl^L40`fGVE6{4r6e1r4ZXc)qMLDUi{W<|| z0A;YxD_F#f!Oi?pxUFfmICczBfilE=R>G85I^Vp&$IiEXl;L}nHXyyWi&G213Hj~+ z@0ketd4RcEbll#D+bVobby?btR=~%Yl@o7wYZ&wI1va|l2tP%A|`Z}c^J1Gfdz=en^D=^}(|9fwcK%r3_Tuy>a1nr5An`=EbT;wU?ily_yxc?S-gii_e=(lXi7PR6TY3Xu2k@iOv-QG$LEx>l^L#q7r^#ioaB??*N?nEZR?=IXMqm zvi`;XR)Hw1nWu@hguRzh;LG!ctFuqIO<2Rbe}!<`aiQV$ z3e**+S)y?#EE0NSN*RY2pWRwzFP+~7x#Wn%;K(TkP=I9*KlgI8DJ|lw?QrL}9c0t= zzIVvA9#~Z(pXOoi1T&VLYAZ_2T16=5z0k0p@h9U=dR;2V>a;BMGx4veWN16H^hbHN znmh}=KM&;&$;*!uAOSE%;xlPv)%}|+@XkISVDL1Qjtd*(MrTh$DbnEp(vndQE1?T* zJt_+-p?61K`S{!i+4$SUSf~&0*8&gmfV;#Y9A-6Why0-xFANqeZ0g^J2DA+ zbi}QKT*Yv=OwBPD`aC_Y5CI!g!cvARQt@!S9|NFxEv+W&W|iTB8+Zx^P}k;ll_i{C zfYE=?rzGkhQSyRj3(&b&-=tB^U-GdD9r`dMj4$l%Hll~dd6T-33~FoJ!c}V>8;^H= z$T_IC_)UG0epC0nKDjn3yJY~Qf(JD(A-7VJHPDB02#s%7S#QQBbEUL63oqEECI;ZN zwN?$`N#Wj2A$1<41h80sPMC-F)~T}FcdlJ&cNR8QcWrLzo^HSgpAc**nl`;iV-%-5 ztD%CgnmUjLL^+nc{|4w6Z;Abl-b2`m9yHAabEnC4008&l7+m3TU_-76%o-Z`2`Ev8 zWrpCYvZhnvH$#p$f|_m$%$f%0-Se#Bq5HxO-3GVP%`YT}w=^=EY}WzFZ% zxoK~ln5mGec~7e8^dJ%U1X!zo%G;gzCrYa=uoyXWFT?CY1yVicNk+$6G`<0-kKRjc ztd#Qmy(ZE@=Xj|s=^#U!CjvZPJ(@57URNotmFbr804+nd-3sXJ=P#piuBPJ>`IAgr z_tZ8MC8Ve@EaT-pixK0-?1FLLN>ikCLRh!mmJ8n3p7mx_*T$TLIlDoy=xim}lRQD6 zx7FkUrpHnM3&6B!U|me@7!$HvZsPp)-^aUJA%i(j_x{%LAw?LEIhwyzFPztuPyXu; z{{C;n&N(Ud>}o^k`Xul2@TS*zYpC&%qd}GO?rlvbAkZ8`C;cugah(`&udf^r=!(K> z^N0KjM?AnVW-h>)tfz`5q0QrWFpEBmf2Iw#Kk^!`K#IJ{{yF>b{q5%J=Vh4%*k!gc z-r+~CUs2yvMXX>{=}K`^;rF{V}bO2R~f4n*Hl+=HGdL1See;kIrfmTWj8O*CnkGPn56< z>#p{sRnA=o|Eb1JGgqbMuIK|8Hj8FI_OxNRUmpwcR)t>(Pf&Ovm0S@80lclF-5(eM z7FRC^=mA^@m>KH%wGv=lE?zkM-&VBgB~Zf!*sNG&K^sRev1a=ao$NQASYGK1y}bdE zQ*z%m`@JIF%gUC@)ix1d_=v90WRd4;{lU>&a+(F_9A6xKPKaVxeP~SWG!T%)at}A? z^462g3Z1r>_;_VdKBb}7ThBEAc40AZqP+XtIvL-02u-Cp#ob0u{WW*nvQf9wIq$-- z>G)eyhNuX3h?m-QWnH+cjAc-jPQZ62hn&Xr{aofA?)&N6sv4vp1cNZHqJ1g4kp+!U!ETdiHxXQZ5uRs+p5cVi|?;n+$eJ^q)hj3nbl5Zo00>y znHRg`u`&31TYd)AJkRGd_C;d>>`D--k^SuUMXmhhU`RS|$RZs>AxgQxZ-7sYuZ(Bj z(hI_qJK7hN;cGLcYGApWKG{_6h9%H61)4=Cpp)ERf0IAn*Y5vE!Y8R+IJuPih~5iU zWgqrr0#iifG_`Mq7UouR(N(NF<-ok+%(>>0y&Ci`WRq)KiZvnP%b>g2@xjGsTLv~qr{7w(vD^VU-y_#FLe2PVT$?y0XJ3NqiznfwU z1v2ehL0(9}|6|K8*DQVi{erklJ(~|46Pe>FCn#32+_oxr+TKy>O~gDeqm0djV_6O} zNds@r;N5F&8@Gfe#e5{+Uyp&Td&`hC)z<@Tfmme4_3EV%u3PH_JYVfJR;wnDtb;0F z!^ixmrC}?Tao56Tz$%O={?T!}N7*(}8Xrmoq{f>n!Jy2S->6o5QOngH!6$LU^F=d0 zy?g#O9|3BaW~E!ZY3r^zkITXp6!{);ln~&jr8Q}RZKIQWm zUxPmC+Oyt*f=NUy02DO;8@94dO|-kl4V+6eOvG1E$FdZmA16ZIT2-z(ZF1pZw1J~p zXh=RX{p50uy4x!IVImf?HXM?mV@9s__=M=b&FVS1A*+v?dJ3;cCl5%I2iDADqHB*t zQVFacN}Ze#AnBqPg&~{HHuN*N<3llePxJg$F@uAf)J8?3!Q#ge9)*u7J-rnNP@cjU zTG#!EYool5<3^{kFYY-t_$^;F@@@sK&H2b*weO-20k!MRdVXi@x%Ko)3U%~k@|`a0 zlyOWa+}Nw(H(8VCfb;=oDbr;JD<(pQ@)b^y?x&cqf1De6cjoY9(0IN2b9EQD6A9=n zOihz@R>3J%QC5@FYxha@D8JL1+qB4=?{CT@9aeII^mO)m(SRTF90K zZQPb&(2ySbp^5KrIwv|RyUs%C1P)E6s_WvO!NfUDix`8b2N5u{WDis){G>zerA|B)1{ngrEXOLjNKgO38;1oOg z_A`QNKziS0$_i7nmH1xqG}4 zQ*brg-6Fxg?LR$I7%>(38?lhpoT0$A?)dBoRHZJZ+C%a@X4C`qr5lStLIYsWNe%fh%?#~RZ_0dF6ZNtsxSn)Np}_W4 zC0FnEX~B(W%92kBP3_kf$4;FQ=Gq#(auF&-57G+daa-Bu68q|MX2EE>`QoB{N+Vk` zsIi&?Ptx@wWuxD{X`c*nKoD&;6;f>mIEXb>=VnbCIk45q&g>J<3%vG5J7*yrX)hFa zh}Iw19~vX3cXVmG=U-Ylw?51~?ktGbbZkWpcf#SEBnf6&s&@{m7nRm{rI^ zGVcj-07F4`ptaQL+86cux|;A&aDi3otlTrMm;gq^oud;joQ*7g5V!@4*mQ;quoYc6*bMx_Hhp zGS%`jkJw77y7%ku<)1#>e(&qfY~Vl{I+fzjR{zoayl_-|lfL%so%{_TEFS^Q|DMC& z!`vjm%zmGT0hv|cm0xprL$8JL?(Y?@5h>#X%Lm~arPcpK)mJ_=`M&YeC@q~LA~CvK zQb8CX9ivNP(jqB{2uOEIDGWpyJwm#BNDU-LGmsXf<2-(U=e#)o058V<-1l{TlKO`` z-+tE%zAO__^5Jl4H33hhvK_OiRlUb4q!ZYSMEUb93>F2CpRHOiz~h#C_d8niO`;V~ z(H7d}9JR!uVdKp(h{kfUF z+VEkzsoO%|Q^A+yloneFFBLSIINnVbd=fw2+*ic)g4iLXO ziRmGR7~?~eXmhVe&o7NGPXmt{(}Mm{#s%~t7NvL1H?whU`qm#by#k}HV7(2!BB5dON6-epKqk zR6*TPwut*~iJ6vFY1-n?dBe;1Ukz=@W^Y%L0})F;FMv2Fe@p3jTXUc9B%9NR zT0E$ql-0V=9&VzzV~){o>$ClIb2r>XfN-S0j|4Ks5bt)cf7HtWjC3ZutYFxQfrANU zz?9G6)PKZU06dGX*olCbhQz7-f4O-?7-)JtCAllV0k zvhO!G`6ChHa6qWK)6^4g&385#ZxE9h=>imVG`v+iwG=rpUS7@?g+`I|d6H1635JC& zd^bSDUbuQFXc;hiYvb9Ox_lM$f9=jv;sdMen(ucE+WYg#kZ773IL3u^wsinKg8%6a z3~=k(V{lZnES)(j!*)6pD@hY}Vo{fU5{;X(2a!>cra81>KFl{*aABz?8@YZ2#z>Tw zO+?o2aLzh*r9}X5r;&*hsq6N^YB!;!Mb0M8zzl!q6UUcSA(l5`O;6b5na1Qv%5zhy zh*#G5Kc}2`Ex&UQ5=w%_qLg@^m6^&;f#MC;}o9%#m<7wC{Z1Q1BQ zek5|X;$cIQcl-kfE=pV^;;t~X=6B~g|9&5igCP1TNK-0R{!~JU;HB$w`78BsJt8z? zIv-h#bsR|>xEnHfDr*;D_=Q9TWzIbl-St>NVp8(j*zc=RGzXnt0>c*BhdM?SA9KF$ z?c$g(@;XMraBgzv$E$z0{NA(#)+}(L6)RkZkKL`>#ahXi7-BdVM-qVWtw7m(5|9IS zqnx!Nh-e_In?!E`DJCyQfh2`l+m|qJbp&`w4JB~=T#q0kr(Yb2fcUkwAz_kO`GtB( z@-q9sT&zWfZR&1E6sj(1*HtEuHPUz)%G==lN| z66u=RGa9`VG|NH9wsYRvWv{QVy!{4vWx2Z3CS2T~c&zLFD0>7()~SatqX`8-x|f`v zP<}Lyo7ARWw-q5fwFlQP6^?ERL#}+Rn*V*RsNX=XVd?2{hl~D z<_(H-YSBWfGh@EAT;En#m`}f9AsfmQ`b4{Kz3WCysQ-!4J4RTTW2l+dvN_Qen~ugx za5gu5vF4=DsazP((Z7Wc2R?aeL@q_3#$VIvi>fN_5&LLXA;0l>>e|rdr5UumDM;>{ zj%}d$+?#^@pmyGcG1*}U(|}W}_aOay=ILo{ezr-du*@Eom0S)ONt{^=aL$(HN}CzW zlisGM5;qGA3FWvs5Nl-Q2uiSVtqxMA=oUtf_okb!&-` zP~(BV%^U54c+2;nI)9;U3xCvV-|oZ;c>?1$Oo;wMbH|yW>1DyqRXcA(e#krHy<)A; zZHsdRSin#=bmG{4`u8m4aIc{aTD2m%3GtKA+~RzD2iR9JZmj1saXuiL!Z#(q$xU3{ ze7xXBzoMsx)^QSsHDBxXXF#`gxVzEhcsnNEEel&3V&4n9z_pVf$|fcK?Z<&p4V=gtkkh6 z-OsZwcK-~9lQOtolW!$8MFxb^2LrX8)AC<3soDXfAdHjb2QD$qmC%c{bcrHJ$PRhi zF)HB}STE=A=6&jco6PFs@@c$+#d!O>z3M`f4IMvtythXE8QR5bZ#vNM$5z;QP>Y~y zj-KHvPN|VOE4LZ{L8~y`rXEB2N(<4mQ<>M68a5{tVJ|kUD=KN;kfKnt{s|}z?mLwZ zdKFg$?r-~f1wT*8w1;NgFx|4CxN(9)ae!k;v+;zdnTzNz^<+SFvSg z2~>l*Z-wBYn1)TjbiVMVQDx)lmk*8HJcV?YUCG21A*ymcBW#Ukt;;~oTQsi?D|2a` zC1@D_w}a7BmAMsnIhC04xln}l$&w)=c)JAutP1#DUPk?IwN$=|5)59U6KJ{)H5fXf zpc63UyAym%n4S|5q3hP;;);in>J69lCDh3_K4u7iUEwPac#V?_e_ltjFdm`+SK6<} zLbi)z-x=aF8|AalRpZ1-zfN*iw_5Gj!5>jlWK^mkZBHAJX?ZGYseT)X7&?<_D={Qn z%Se{`7&;hWxiJvq*)LszL-0B-VRZzJugVn`wkB0m+;eQxFAZ-&$o`?AdyBM_F+Xt2 z)FB0Kt;vZ`9*1GfRo=xuCac!`h2%G?o}_5I<}b)y2VS1!>Sw(p5%VK zZm}x&7UBX0bQ`gdCOJt9ZFS6_#M@7EZs!&WfzU+pV^kf6+L+8{{TKJoZ_S>&5@UVq zPZMgTxGFvi!alMjI?FL}wjks_a|-MXH9d22B|$Cim5ctH{rQiZoF^5&UV_dI)sN|X z(K2OX=%LO^2@@y>Dq43UXEk>F?a9z0y~jIm_qr~ZF!5|}B(tnyMahP##&d+~uBq=} zYz&RXj$7ryvr9c5So1{r__{fV<3683=>;#$YY;zMh(M#EmypXRRGKcE^7D0zYK}18 zfr-AYmX{)#rR}|AN~KQv4?(QFxGT4uv*f>4`KtO3-_J^M(PjZlEGcm=nxX^JY&{~1eAy`;d5x*a!+UV>7{aE_?30tb&O1* zsTU2WqHCRA$Xy2G-F(BD#(#4w>VL21H-)RezH8(m`?chl)OHNLYMvJ!Bf@}8iL447 zf76FNtU{i*rLpF&fdouore424Z}Azv{bp(PWNHP9Kbi9wIwYN{xD1pW5H>7JF=FGwEJ)FR^Ot0EUVUo@OWX^OrvwuELmX3H( zM$@!pQ1R66ejE@ET|Iw{_If3jT%Ga#sh{0~=*3T7TtZvqZe*xFwPQ~X6(B>O_@E#0 zvka$FJSL+qnxkGEDAxTThdTD`Uz72!U?>x-zBjz=nRVeLT^LAMkBSy2w)e#Rd+W=5 zXlGN&<$Cem?XN<#mSo_jdZ|uo(44R9I$U&a1>b)X(rJMFgKxx7=79L?GYl1>c!0?$akqWRj&1OSjz7i zOIc&Ek~8+5znv6Ul!!&`ZRz}P!6u=Hw)~6`jxF!~p{LQq^sI?ALaM_;3d;0P{ z0lsiV#8ehmlQ>_rc@K+q9Vcdl!d&6JvZe_oVm)eVYX~TmefgkIHW%0}zTf@$lxr~< z_!_@OcmztF-DZODKqh*_d?M3uB9?oKmn^7Y)Gf8KN}1S1*o>BXUr(HRvqiw5hJl&C zXAn4mgpvZl&&EY?e>P5rG(Uv~EbRPd+On1ET(FeoqD;r>xO3(S! z>h+Uh6a;S(f7nRug+-1MF==`r+Z!&ex?jQqXIq@$5H;;b%dau8v?5#+>20 z5EWi6I1RB0+*$E;S}U=+t^NLP7})$5IeSy8_8%+v7TnU-WYR1n<>U4up!AQRS4FD_ z!rfHmJ<*xtva&_s1(y`cZ%6U_bO63xezUUo@93pJuFw(|+~w`>lYDU5xqgs3 z*ElsKZ}J8+@Zz~#YQ*eq;5#c7m&5Y(vE*^$kuk=&$yIJ4jke88d8fFZvA)iv+A5p! zw1b!rnMCNO`B(B%!yryo_h!=euXVqFU3CSMm_Y9#ePY^4sNbqAx0KVvG6}|7WM%g-BLX-df6cyn`$Y1;w7&Ov5R^D<*R4?q(8b4 ze)Jai{B=&IKHe77t1pTTC@F%$zae#&e)9XXk)e}6k^}kCkr^q9`E@oYd3wYpPttv% zV<Hk}J!UvvHZ@M0Q5MYS~0+%1B@;q-~kGT8%VESW}7b3VsrHB1GZ6Dzz&F0k0{R zyof{+y?7@jVnPmokVB#;AbyP82jEd+!acXNs2>>V+x{NRg;_=YIIwywN;sp( zk<7Q{M@|%Q^2e?+)HG6NF6O8U5UU$r7yAmg7h`u_#!G>B2k(?K4uKK(b_EvPpnnIG zxQs&>sj^ESnJ|S_k2EQ&Y~3|^+=~bE;5oWI^P6tCL~hM*D!gJ)jz?ksS!djXA7STYxE0!D_;req%wn%Tk=gq3!?$h2t<2jJeVK z`N*Wi2zmDSV0Fqy31OYg@EJRUbUxLtIGcK~nEP2$rPS6qyTgqSqHDE%=}cU2{#H}A zzF;YCWPOTuvE*Of64e;oag){WANt8o`)fHrxeD7B#JyhKyEQhtdJre2^cx}kh$d&; z>0H$6`}HpLhvmN&GaK4{f~A|{w^?l7mc@;fDH4+`jYJXHhy&F^(ES9Wr6;uA~EG7wzy zs>$CR{OS+3OY_FA8s`2|-FC?LtZ@@tq+GF~;ydRr(;9rHZa*}dSYG*}Y!C}#{&uoo zWGKO}s`3oHmhvR?0$f*A;z^2UG}5}Vsa|q;Y4eWtvlNo&%%n~5xN8R5BKbIuP)9)e zk&vo6KKc``&7ZsypLFz2PS4FxCQ@n-g*{Vm%1nKlsxJ-ocdg+~PgW?gQiz>;NvXf3 zDvM{9b2SVBe<;4$%+;o_dMEzBdU~;EbvMg;zG_SExKMft^nAL_N&adnFVx z_ZiFfj_>DygaXYk#u z3gJ~<`i0$^rU(i*%08~~nkl;hG`zfuozRVC2TNU50!E;u6uSGt(KNZSvaWc#&xYjY z&@F3bmfe`>Kn(F|hfivl!{Gyp-u1Er$F_2%)jSKgX~9yV3~{|C?gaN@#_`r%joq^D zog%*^!j^v6@|?F92@dPj``BA$!0<$JnVz}YK(90wX`S|JbR#3zXz5Bc?vJ{8`+tZT z^nb7B4C~DTCfJ5#jXckmS-?3hyDj;LL+#P#t#-f`cvC1=YE7yiztBOJDrSlq#jQ`cou|X4 zq>(oE{0xpg&VUk_KdO~AYYo_=1;-M&dt`o0#k$0;Nr?bN&18Cs6aa?sgc}USTv1s5 zW8^}P{#v#E`z<5Kt)^REKyG5iT@L34R@x>v&(QbFD+iU{04w^mD{c_{0=$?^?v)tS z#w0tS?XvTF47Q`4*B%TlHjUYqfnT?0}Yf zScR%8zwgKoR?s;p2}Q_Kn7gwoifZOPuXq<^vDy61)_EyYEhudVNc?jsGL>ewogF}-fKnhyjN4p;p(5jXYV(H7x-6TK&T z40RkEd?y=5KnYo~N=9n(>k{!|vvV4lxBz@vr{^)kA8BIA@2J^O`cJkI%Wn7}F$hG*T+K;h#NOr3W?#l}bHs^VvEeGLkkQ8g^G_DMzH$A`MR{Ce&SP7seWGXa zaC3j4`KCi&I$0GQ10tCql-~!fZArS_0d8ch{St$|v%dK#hyjSYn|CKys#loLtN9hm zPiX=}QrSAxgsZ*Nn^Mt)OD_<5Wr`OrtXL+lnc!C*v~XC0yb%NKr^ThJH=pnRE>}?J zq)Y*=tkpNi-SpMB3CO=e&Fhn6!qrSgY>I4$tvg?W6$TD>46!P7&lp2VRp`sd z^2zUpq>1dI9*9|1zd*wCl@#>jwL9r|@s^}|I;a>lAsA?!QLQbC)f0k*?RAIE+PqhS zzU{kiSo*xfdW9#j2>d{;s{iRT+Hg-`&1)g;-nUmR^@7?vk6RA}mdl@iG12I8?zU$h z>-%S2r%=s9Wtn^|j$PnA{B^?u9dv>T_YHK`8PDAJvBc8x?S6)gda(x3F#yKuNy4tm zly^4wblTlRzhJE~&*KZ&X+P|t8ygg4u)8bW@V_8)r+W?dtd4s+*1NT{xa ze!ka_*I_F3Tn7@cbcn8mfT%zIDBwtLc&TD5nU(g{!~^$`Q@;~4QSVJ%;nWbQQp#cd zsBVRxz*$NsdqL^F5rf_!NuS7t($+LeNX^r)oWmw7G_#SF(^>3Zs5IBZwUHbn zbPib%J>CBGr|E3+8PV^ZPJBO_d1EEy1dhLn=idyHTizmG{$~8A&{(okaJ?8cExS`J ztP_neAVv()nt zVKrxDiydbwo>1j4?uZ&dZ@!MzQrxp82Qsz=-AmSPOYlwScon;6w#%7*tH%@RSs*s} zXLtpo0*_rf$+2~92(2a2XKL%l2fNDg^XgYpOx@hf{l)bMr6r3N6vY)ZQ@?h6T!*%= zD*jko%(~v|I!)2>nh(DWzrNu(kAu^h_*wzvD;}X0PS*# zt7U|XXYjd_Jjx1zeO%I?@2^sD#y_8hC7H3O57l}$J}1(sRBj;-_o)2wCLf4AHc_GJuf@YL<0toz*rs4wK*p;Bv;f>%5-QLkOLKh)P= zJE`f_!__WFa=@{<$QxTOv3sq!lNDSj4pe>9eoGdg3Eu8o$1U`h8=*tb?lIJDBtIq; zY#nH#_9~gNk^~A=g*qaiXJZ3Hoy+L)VrZ&p@w_0pB^e5wey@|`Ia+2R32HN#RRVMd zl8D>0a}e*KpeHTWIW>v-P5|ikzD{ zL#~~6*8@Poo-M}rq5*J&AUSMPIZeiH>egBW`~XWYZ8&U3fwt%>a4<^A%yv|cN!1`F zwp=$mmprHrxtmi`8ew=kbMq5}zJw~=?h_ug-}3qaI;*=!?F+{MdNATlP?`!b=W02}iPI54WpxtB2tLm380MzM1(i=cJ%|kw zSkPjJ4oMXr^@;Rz6P{>yu(lK$u3*(!`uYOvU_okEFylAGI7Lb5zE3fXx*X9}u3J^a zH~k@*+)fgY;NTm=Gm}93_`{C1@;^*h*_Shi_F{w~Qc;ruW530ivr@zPmMsyi1YL~8 z$`a>vN!fij^n8I#!xI#4;VT>4S>4x`ONfN_Fvj_cy!a$-hiJMn2hn|~m83&UbC%;2 ziUFLOXwFPfjLgp_vJwM(znyT4{}46VX8@so6>_lTch#%Vw0RB9m1r0}n)7<$SId}ZCUTyLj1{5B9h3SjZNeCU`rPT(>uQHt0re>9pQ3W`T)M2{L^UercfA)R<~$e|D?Q9w=H4VG+2&! z00t~l`sSrT_8O>XfVF>d1BY)kRT@<}e4aXT0=ykuk=_!k; zf}E~B!u^;dtSXAv&%sHA!ztG10Wtc}Kxz7^)EJwSF*>KVj)MCWsBpX|bpUoc5Js>r zp7f2X2a@$61MWYxu*b>MCBY4Q(85tXLN5maJ+xc>CFW(i*8ewBgP>-{wWT2hy384Ndvyo%>#_?0>aI_jiQLmZRQxoPAG5A(7N*lVqmud zj@x?s30csU3%8}g^K_U!5Jk26K{pyfOD2&B<&doY7~$s7evC`nn_al@lYUqrR{7{9 zulpXk6Gs(jW~T`2tw0rVs5>8KzF6Te=X(@Sg7{JITB{r(^%aGQS2EKRz^TMuYOqf* zcw$`f8n3a)Z`ck3KU8&_*rb-8{TazS;iMBtej2$G?r5{#TSu6PTMt}3ISl2`?rGdv zRit%Fk%dixEZuMGP(8_}Wnv5@D0L$%5WxF0sVeGTG1d=6D1lmHMd7KI8GfzKZhGYO#^pZwcYo%`?#}Z44mOr9?JJi5O>0YkkI}o?lXs@h zyT!L{!Q0|K8X$qDooswv9gKUS%t&it{&E^;Fk*V5|{P40_P})WZQk z*7C7Ir*yiTO*@V{KF&9wH}3@d**;chDqfJJAJ;fH7r&q|v1~8!&t&wMAZ?ApvZb6c z9?Dw$xnuUl9l3Gl{Kt}m_I<Q71$Ce0j zTlhlU#PZiP%D|AMfHrYDuOtj@`L>RUr&-R|pnYw$P2lbth^R%H0~<(!WtH_U!-VR2v{ci++TFAR;Wmdz=A>8EX#LMh_%r3JHK|R5ax~@1G1dwEH)9J5NgP zLdtI--iNpdLnOz_Wt=Y5tHYf%of?EV%<4`uA%X(wbgWztH)9qeeA+qyEPn=>;O79O z+7BiT62`j$*Jd6mRiw~i&!CJU$33$!nwq9zu(hVo41;JzfYH^EOnJ>hk>zys5AF0XL6|4reAF* zyi>{+DN-WEEjZZTJ$Lp!_!S9|@&jJdWZiNHc@Ioso9iFf zIa9V)b$4bP72;SIAL+A6X&l`&qXSOutZA&%-m;puW5he^LDy+@xHgcf~|>Ytp5NS4k-Z{0cfKI+df zh7c|L6tJj`F0Ry%OOs4!doXXjGQ=KRc;7(#o&)@DsjO~6;>6dj(e;m(JG&GKZnYXC z+DednIqae|ESm=|-gbKapflhhbk!y+aM63s`f0v2TGznN18D9IDEe|5`r>ssIJzj= zKgUtNB}Z}Gj)FJAbMrYpL(rpBm%d{W(UJiWzaAAmL7vMi%+pTDshhP|rq3?z|I-4j z(Ejz9JFMg5wjSNc9si+lBUAb7-}R=jQ%Uf5vBbRX4&bWwGQkfBlf)ND^=4$Gfj;GT zP+E`t@$b#WV)`BijD>VU)*}q=4)Z|@_+AOYp+OQRh4)ckl>&e&u|){`+l#!)O092~ z#gl(>#drGw-C~UW4VVq3d`MPE?M;1d;TAW`80p-@Gb-T6pzSnT!wFQ1)Y;-`L(4nO z!catoV%{3c*E8A9_kd?`xSy)Y=rm<&KtTA&FC7~$o;@`e_Y_M9yiudtA|B-TLnux} zY%y*^&)YPmXZlj-iI3ZR(QN0#T$BP4vkp{munsi<`x93pijdF-+pAL&X4-YpkJc`} z*zV4pn;T_&giD3ZE8pAW9JPofXB|zNjg(vB1DYmzbv%Z`!4)kn<(eqU@;;YCRgSXr z&Ka-jf6Re#tOOw-p7rKT%f}kvmHP@r*&T$0$nTkWckybK z0?ilB2;J=0H?4@=$9$5m{Wj~Zlk8Z8JH4dV8$xKvn)%~ECLl*Xy({JhcpV+ZgrxfA zNrK);t22cjw=^ldy?y^W55+mQ;xgWlYuU<5paGJDgB!py>3oKoGu;8Mao|`n4ztW= z=j4?h8Db|QKn}jaDC!Y^{G%pa^P-q+3tEZTTW;@nP-Xv&$YmWcbaUe30!j$2)yyjo znvZc=k&f|h)}v7++++TAdFI50v_IN)voTk=FvEwN#M%t$LHO6w8Z_YVy4iqYZt8D` z6_vXdp$oKJTAlbqwd7WTUNnb##wkc37Nsu7X6YxY`Hjq46DR@1t+sFRQMB6|$BkPR z?p-?P3i%6kyc*w*ojsI~g8o|#zJ~Oc=J#%eysnBcU@Dd9YcthGXb`j*k)glTF~4Ox ztm2f;;eB2~iaR2G_AP0Zvete$&c0vYF<}Ixud#C={Z~S$)oo)Yb(TET{IkiO_m}BD z0O*|t_qG33sgP8)RqqsaWZ(`2NXU(vil=9JM@V5munJjUGlkSBFIXdY?D6Ll^L2l7 z$wO%B0bLFZ)#v}9IF2NRw^?{$&b}CEzg6$}`1ChW-=}}LZVq}*P_NNlF?n^y<^eIz z5%1wkRLr*BKB^LTAhWZM+D6eAr1kv`=z%bG-e0lejncf=SXHyGTos{msE0A;X?`>v z6*;bdso2TXZB2^FtcFw*lg*UTLc>Z75)ZHfxn>5fVMDqNr*v%w7svSvH2<`FFO}!9 ztS5^P6ak3x>~^2^s*5goxVz?>v9yB?yE2tC7ar9z&zlK`0q}0VtQh zrg{bT)yVBbvzcz*N5xlZM)PDc|&(_6{qDr!$G&zM#-cEWQ+Q&`>L_g@Z= zAj9WJ1v_AB966|Uh;+QHOWBBU%{mY^(K$%)T06;(UaFF}Fl`UL7O>)A&J90oT4b?m zms?Hc{Cf`I?}!)8VKWg|iRa!?|TB~u>u-94?m0>uK7Iq!7wAxjX^o~wopjpvY?RKxeW9iD;&j`&Ru`e zeDgv8N7Dy6WYZp^1quiGS>_5fsD7AY$0_coslZ(VNh$m4P0f93s$#`?IXtw?0v%c; zb8-=Z?T zH-bqdoA1zO=}#)3|6bY=@^gW&6BItc%);(Da+=&s@ctFNNt;9cUyz8a@IP|Y_gmXt z+|^wl{Z&|}bVl@RxETrn19noe53Yq1t#*>9g*WvJD`O2E|zD z4Lp`jXWFcUr9XHPM>ddeh*No|NM%usJa^T;>ID$ui+{zRDr4q=jNI z4VDxs@}i9n#?YBo@5VLHTzCJF_a)36wM&iSkNG$jcdr1<|7Q8=3F%YC@~nQtdWdV1bq6gq%z@#U3?je1gYE0njSFIp~c~Pa^VW8?&^in*zwPYyEBLSw5NLw z`^&=J{oSd}^*HHOoa1sE8uomJY(H1hXrwSIlsR}?1}rX4quVyS>^H2-WX*b=v;^-p z8BRIFqd?yD1lq#30p`m#c?^N^ZHA%8AnLvsdzM34dQ2Hl9(4d_F8A0?xyn=?m8eYn zZg}0MjU_{lO&lfLA+qKfgKEhe9}=nbR1;fTc_j>mIqH*dL=LR2vcAt#lCt?m0mEZh z^M_KzFUBZ)@-b#z?{X~R8KO=PkIPE5q-S}LbPM9LGiEC6A6Z+hf3rcc9}w8}_bwkb zz(KRG3ivhjm{ffAoM6BFhZiDAiQJ0rBE@x^+1Eh#&#UU)y`Ir)mE+jdfT z;s4J4oto_lL`;vv=+P4btxwtX)UP`fg@}^8k!WaTD=No3_DI})r}r3HO&tE!cGWa+ z=Hix%tJ%~$R=PiP>9+4mZtU*jrnG&ntm(F4=_KYKPT+1~h6`M#?z%VVjQpJ79m0b> zg)D))0vu&+eq0FWGjQ~7j%HT=*JJrv7VhUFVJ~-5!WZiLlvZa+7xqqp=z0oTJ`n^U zGvlmi5#2o(8wnC(9+;6*P=PJSZKT?FEt+x(eE9y;l@;x*0>UrR3KLMbk$*0o`m~ea z<<7`r71w$X>S_yF&ust2{dQE>8qyAvfXRl-BDO#d9wfVK7L3%4p$39 z1eCEW`*Eo~Dzyqye%#O6dlvTso6MVlb(hg@F5H?5xnqxBH_VH z)K6*Kk#hB0OOU!l)5mA-q~4m$m+|5b8fJR~qq(|_3xG^0nZs+mgJ$dZ=V6;c{1RY* zgG^pCi-sZmV7J$m$iI^9Y5(6jv$@&y&AVf8-&L?!=>t^J=0_T28c6zL?q7cbG19OO z>nEZYlj>i0$SlU%m`wE7Ti5s#j^E@8&7`Zn8%k>3^ON6gEQi*{YgkXTvC`-EJFD<9 z=+i)>>0LeeHGT_M?3Qy$T0hh{Li#n*P9E%&Ik7`=g&kFwjv6)jgR6eZ3Lb?ZYx@Rr za9DTUGgpTUD9L(jbs6#WiA-SI@HLyJg0xj;BlIuU(vCF|Dn@b}vd~VSqU6Spk2K#K zzCN~+S$N=Wd&rVOaSvW_X7ud_D}%XOW85^sB}rfMWN0w}NP zJnKo05(h$-JT;EJBoGH1F*6D`e= zb<3m@{_6Q%EVfX8$yZCh+m~l&`Wk+P47qDI_bIWz+0$7#hpXORQY-PKxH>b-IiKvi z^dFQr))aHpYz83?GxRvJo`GZzDi}^)76tp4r&wI^gx^fY-!2BX&~EZ)engx0wb@zX zYVe-6Vr)zYNt0*%$C~!`1EH1So)|vNbEi9LM<--t;&IwJ5b*7&-ee88Ax-M$E`y#O zM`OR9KdXejh|QzzSgFoPlPVNv;}BpQx(%i_o|$bc^JDCY-|o2T$*XjhVNDM})M)Yf%?0#ryC`YT-2fna7qpzZSOtBJ;XG1E3&h*lGtDrL7__ki)O)s7H9w!_wri(*m&T&+A68Op@G8ckMxg7jcIQ8u@AtAf zM3s62Q=-&qs|Ws9#jQ>1Z8H*QIE~#$-sRX{u}tVl)}&Y=&l}6=(VR# z*(Aizo9o+S@Uu@SKnxB>m#aOeD-&`Ma z*GOOh^26ZSoA8F!j+DaLxO->88d?YZ$Be>83&d>2r@^JP?X$L2#G{A;kZBH0iFnmP zQ0eH?$6n~e&1^q!Hn$#ozWqMS+YX$vmLyS zT2H|T8t(+3pUI>^dzprpWwmYGfJD$8Ap|{WID7q(0d#}XxZGuZjVR0;2=8jw9L;mm zC9-_!s5;~6!Q!1tQvCj2ZM%VgTpJ6P^K$iF^E(BUk@Aru} z^@D48(==mTmrJ)aVNgtK*~_V`oNopXXR;jE;!|roYUi%r$i|)^y$66r8t`XCfiu`*dCH1~pW-&#r+S*Zl_;qx=3U%5)UGO69p+xKG^O*30_*j7 zz$Wv3j#_UEV{WV4qy&YFv9@0``v8pKa(Se5(>*sAa<&B@!Y|h=pq4$5>VGz*95Vx{ zO^sEKJ_9|oDO(@$JhY8+zq&ReelLhvyiHt}KePXQhWZ&e=5NX%o8V@xl$w0rn%{-* z5hr=L{%rQFZLhwPIYOHgW2gtt+j|QXY#}N1ZMJfr-47V^htT&|bl!aIaEE`Pjxt9W zac%>)Dnog$I#iBINBvFE0Uy#7`y_2Bc&m{QJ5`aS5{lgfP_p9@nNQNkriS){`x*7Y z42hXtolTqO+&xc7!%{`RJhc+|BjaVJJ78#zgQTxZAN!`)#L4DFt*?24(wNgq_Uld* zY3i!|(#?3W=m*WRdxA)6n~r|Rm2bDo=&gKeOf+rl?Jm<4B3SA*FDE9BM;kWf(@0c~y7ckNZG^b$LsS$8enj6hKMest^xl*2 zI^d!1#YT2^=I~BmKZkQanQ^nRT2D0r92GH$_kH3LsY%)DTLmqWvkLZ^p$3+G&o@uA zhI0N?Uc`iqE#1T{O-utJ1P!s#Ctv72T!zhyUI#NkY%jsQZmpq|g;qZIg3bgB)6SFy_XJ&@=)SRNfzBeJNZz5=+QCyq! zE*}jA1VP_l%;6@2+0wgqo*Vt`xvw*>F~J&8=5N07w6dd2JX!BH_s)+ezbzd6Z%@c) zcP0Brn;nI*h6LR)xseM3O8z>dFSjpszi!6A11JKUy3L=Ynl=uj>Ry>M`0%=C9H441 z%hhPnba3rif`xBf`IU7@H#1NgsdyA1#`{RDgI!7D9q(oB7K<_X$e{Bj>4LwxqOgH1&Q>ex z-3;+om4RF21NWZd?eetyA+gDC6N{uhW)2`kN-J!dIy4#7Q6Nh$?F5?#JRxg1Bf7J= zrL-~_DOiVv->0p|K`lA{z6*9byH`rV zm24hcgxWh@i&gBHkv*YAD9{N9hycr}fOII1TS3Pbn#+b-FB1zrdo8c`qa1drC`{e$ zP5a&S4(r9{KP}xm=oZ)8O)J%%rA$C1$&X2-&zapQ$*Ia#y^aKNvUtD9BKLd7?q8!%o2(vErGgx$O+?snYICcD8#)i6uj9>(snVfFWBq-2~0-B}hhqH3P3 z1_ib8b~DGny#TDy_INr%a`;IZo%GjIkYM zHJnQ5^ce?nC$qq*6fp9Jt^E}6Q~}5A9kLD%tO@}9d2n8PXZK0` z?rl_Z4d3XiyGyUlX|EM#qxkwY>hvA#gT)R{Z9tmY>z*ovfvnBYGR=)Brg~46~12Kav=y_cM3YNQ-Ko%+3r4C3>gJZKdjH*dc5@;uEPP z;pb=koTceQ<8N{1MI*#H@(xbvVW#>;GwHc|joXCE(cA`WRCSHT1iQr2-n#*y_&5hl zpbYe)?*e8`oe}h?|8X`v&M7}3NKrpD3uvenXwqpmyRqN zyydju?|RBZdHU-}FrJNTZ-ZVm`u>9Z2}{^95J2GQ+t@}ZE4YJGxha|yJ01~Pl~}`E zPi`@mjgvtwby^b-IFSGHwSx;e9X(2{CZ$j^K&y-*qAw}A`Qua z$`K-Ul8hX1jxzrsrix$3vwjV({aY!W@_%@G>!>K(u6>wRkrDw(5v03eKmh@f28V6| z$&nno8)-pWQW%gJ8Ug9;jZJ@rzk%Km%?2=P%%HJKl2Gv~&*hJFPBR@}3W2-tE2c+IHm@mlK`!MIOH|c zaOLhx%;dVtlQEV$8rf>%{^Q+^_GG&Qk+6_O(U}k2n%Nr7Th&~7X=UeLmhipg&CX_B zWK+#vnz=E1h0uZC#3JR|I76DA2_nwGDVJM2pL)h?7*sM(@ER?-&NLxDUkXS@A{_i^ zdXq#lssI&>@^<#f7LKuOcx#`}pOKg!w6~#uuoXV);tqKNT1zx3&t&}2&HS?k1$$>= zTXnC$xJ3IzcNH&@%(JD)A!e1s-AN}ezJAB^WrtKAkDh8_2xEk~t6s8D+sPD)4m6oR zTKtxdxnvAzC>xEqgA;%Q&t}JIQkC^@zvU4COl`K{6(7%<^>Gd%R9d!$0QAZ9=<*3j#G1r#0W<8nZeXvDEI)v;1fpZwwM#w-nEL{ zy~4)`NgQ|uIXqlGT{-AtRgL@)jv}cL(2pf_(aDB*;3ssy9(g>^BWRT^m5xm(Bl(f? zrpre8i~6ZKJ`0S}N{yNb^uiOBlB{*89deQ6@uX6*tVR+?F>( z(D;tL$37)dstxR%l=Z%VfuGd!AwjSmuohy3WYG97EM8c|B@ zNbV%XD#C2N4p^U;t3-c$rQXcZbdLLfTmW?gU`gU&WRXua-9V7rneQbZpMFS=`A2IL zeE6&Ec2{Cc(eE;>`RMp;t|0=D;>_gwIgLn&Cth#l+IL>CVMAC#ejxY@=8-=-EA69K z^4-#m1*XhkNc3Y52c>DFPEYYyPeX=a1$5%6lR9(&;aX}C;@A@U|lyk z5VOL;p0kfi1|O+t^(t_sYzD?#wQDX1)iV|b_nHt9?dG6Tk`f#4VnA3<{m0W_ao-X!kh%;2YK4`cX zHOw?)5SY7X#TFNt=Lu`w7g^N|VmremG`ZryC31HZanjXBUx&St{AC9iu`Wt10_2kt zUW`gAHvm-K0?^wWe>3I-YsQ8{a}z<mHeWl&>toe){3V*>1(A@l1`6pZwi-O~%~M7Y(&TzFGXk-2n-TidaOrx638| z%T8@JLnP*$*=UR8QKJTw?xhn7Q>}rjA#VCv1!{A+i2_i!MMvBK1Vz%f;DaU8V*<0V znj}G)P(+oiwUQ3x5TRB{GEK*1ilFX!xw{Swtv%Ly+fBJ_sjJ$b08n>C-CugC$|AL& z3i69Hb-9q|qR zVOHPa94c2j#%>$~&n<^wFpxE~Hjb%9<+^xcBRarPNPBtewCPHIIlB?31e*eHHou~ZrUN7m8|9jO&?r2_0K)r+mY40}tiuz)XWK3;PL<6%6+PguAU^$U1ZZ>tqjq~Lu>~$yUtrI0OzPPRgf^I;r86!7y_xI`s zCWRqkbz=~yGB9?RSF@qpp${B6VUltdD7Ic(~6`|^Aq8x5G zg_+pk+etn|Oj(r;*P3?+offdE!@}x*CQeH|TGIi)nryFKcw-iUUciaa!C`(SIYs_N zi#>_O*{@h|vDZXAX^?p!7&7tj!*2OEE$IC97UX&U&*;@sT2Rm7#?t*h?d@>O78Uoy znEpTGx9j%5617EMxC$;EfZk`6kuO;u9?h)I=d)qJrl&vbS zONI~!Xf472%ph`TH={1BS}Tu1{lowOVB6*1{Zt$-u4@ersB$rIaqJQ(VmOHwCg~b&WZZZdCjVk&d>Rl=usue=NPA~>h5n&CIl-o$d>d64pMu;qRDH$kDRyaz}3dS)L85a46DXiwnVMKp}T82b-UyCGr0%#tX=HL znj4uiBn=w-xGVREOeOOJPJimS>BvMNEG+?CADmda-|5Hma{HU;siQ=D%e**rI?7LXvQL#!tk}*~H8o^r{4egRQe$v`<2sP(aUkaQo_Nc$SB` zuSl!4p#ih0;!S5r2CA%8#$UyHlRim5SMa9P-eb(^l@a(eiXpySI7v{J%jA7-aH}yx z?HoIPWoW;xgwr_TUX)pRv!EbfLurq7$L>!1;>9JIbxmHdeKeZMbGdOCxa>A8LaAzz z>O4;`Rly=WAcY?SrI;UnZlCltx!i$(9YOA-`Ox-o6nVc0?*sTe41Fo-_A_Y zipEx@nQYsB)Bibl4&tj&k1bn6# zvS7&?a)q~jD1|fCrvJfjP}{JoeHoW!GicQhc}iS7rVjZvP{Z=#AC(x50S!V zm;0xsGb?N$eh9b(M?IpD0PM#Z9<+-l!u=E)vDesRJ;aD}(4EnU1dr_VSW!uv*ey5f z@a69F;IolNkWAc^7SzT_OUQLQ_qFq}gl~0Z>hQU(0rjwk1iK?4aO4^quZabr$mp?& zw)Ge-Yzq6m-<{dvG;~71>KO4fthC@Fv&VeXj$Bure*LlBn^!ds4m9c!dp^SjF(?tRRcGg=N(uQ}aDWV)1v1BQR@ zMvEKrupGzJQYUX4Qh$`#kh4b20Ai3y-IgGY1FZd|TCJfWa~=>mUGN8~LA*si!L-{n zVwU=X4f+sk6g75us=l7a(sZ~>^F;rMNA*v*;rsNsK#^{5a{_0?N_t!{nk{(~5U-0w zq^mt#7(yu5J016)u=2aUgqX*pGCEkgOty8+;-zMFMeQqOCByQYR00So!SPeth2&;m zdE?>*-l}u)b;X7=GF^ETaXj-^+R37R(v*C|tQoH+3s~m-Bs=6Rd{=uGsSzQ-qxagv zF8nF652W{NhM#p*&^#sAVn18!7&X)_qlYDcZ}HSAD@$F9jw>Rgc0w;-Z@26+H?gPI zjr5;M#!1nC+pGK*nD4F^>R#7!ULs3zG#8~~3XBDv9KY`jr@m9sF1wQFXdxWUzld$9 zjyh{mRfN&sY(SHm?m<`;t6NGe#Aj~0+9v2MddMaj%p@|2v(F*QjI~d6R-5iBXjm|Z zyD@2pEE~u6v-R8`9H7cfkAXE1jS3YG3j4Tr&bUqE(NvrQ(`Qh6-)YHO?f_=(LN7ID zljDdhS5~WM)7d&3#T2cf%DgKkL?sFVM6VDNV0$f%Q-tv+fv}DC&fD=vh(Eg1S~UKS z=3mxTU;n)nr?3jl4+fShyMawibfu|a!SEfQNn#xf?(NT|<9%-5 zrC8s!$qieh9$?-YJ%v2FGs}QV*l*!ym5v3gv!s7aM^1dXOJuc_g}jqTw<*cT0HCUB zS-^#Paiv73KkMt&t0ceNja~Ry9(cxuuS+%(_A>94tL(uCR!b*+%%`BI&&?A9C6Iwr zd~~R$wMGsr&X_{xBtXkhsBx`=%qvOkwt{C@3#1ij#v3G!EC<#6tY!+8DRg?FsaH`F z2WEpP4Rzat%GjGOf`OnE8B50Bs;s{h1poTwpTvxMArxD6JF0QKBMF?fD4!*}Z14~y zbgMhaR$;Ls6tUs>ieKlWFssRRb87_X6yCFX7{bVq2HPcjsAT%SN2>^;FwI2cLzfWc znk7rNGtoOgUS|vo{BW2?x)uj(1rz$2Yj<{y_i(~V%f&)SmOJ}@x5 zn;(PBOmvem)4AdBj~yvg6Qw+#>TXZ_tl4TKZHIcO zrOi6B$jD>iPW`v0SC~geE(8cZNq{H=?!R0n=E`xZNl%Aw?p!oitwY+abQ$ZiHaq|a zaaLld)#)>!|Lc}lG~JF`yIY6@$*PzzcxA`8KBGLi|JA!OB@{+wHRC?2&d|CTG&3le1g~1T%Esu1q zh`@OEdXku+R(-R6XM~!fxk|X@+AE$v^vFvyZo7gMK7?M0R>{YoXxznh5o*h64ci6X zD;|*E-X?c~kGT5qoWQEk6kH_vAFWo2wg<#+gw1FWL}{wYcP`>IJ%siXd_{!7Ac>S@ z9`CiDbdiG?!RoADF3EapDe5t+Su|5;yW?JX^a7Zw5mk+&5O)J7If3nOMJf(aEM$jL znpF$Ls;`Qi=IM#lCZH|&2S`G&3p;GXh3HWqG&4CxBQLAQeL*dqg=gaBtV00CArE z?@Pw_VAk~Uh{?2zo?MwXg3N6VH?INfh6!9T?nj;VZBOC600+;r9xzN@ek7j3)d)nc z4$XQ_MVEk_g!eA5cQU^{htY_0n~)aOj(JRE0Us#m=9J znX+0fPdaV-#+d-yXW{V>1HRe?Ir}aGyz<(&0I{i!(CW187HWLu@z5%KQM6FK^ORjP z;U3*KQBPqroi90MM!#bbH>A0i(q2%*5uLixJSO!UUj+#lok^mpokME^f2{4;g1&SK zeA)h@#IP!ko%3+`gjmPS%zvVvQ5O8H`1TX0q5?oJV`Pl_=&WLZQ-j;mspp7K0oy6r zt7`!n>wuxSGWEh!dK6wCl)^y_V8oQp+aOb6XDj%2g(s^a1vQ|DTH(Pn0}BwT6e>{G$Jun>7j0cvwq(Q`SN77g9P^}=by6?kad1S zgr-8oEj_QFx?Ftw$UTTOphfRfb=v#*XZJP6>hkux82|L;TgE^Bkt~%Z;P-cBem-}c zsOGw0gXVFz9g4-%*Q}O^j|4_AnsP8%ATR!yHe_S^{M>5``%SXhK%DS=PYCx?^RjQI zNP)j*+NdX5BPsZ`p>*OJ%r1NJ_! zh?MIoKO|cJbm=|+U2(;3-tkH0B48PsMsNCEaNp(45q7J9$m;AEn#%6rjV->3d_ct! zx+in}0!#})ZZ!`Z0p8qTJhma9*VGZ);5Hk6#3BgbY7!0%R{5?kV`J5I_9}C;Kh8xt zbRR}v$&{I6RA11g;&%L0f47FEgsGh@*V@Xn^e6p3!cpUxP{s3?BYqYm>C`KL_ptAq)V60i4 z)D5a4UaCDYD#5f3o2MAEep1FdcryT$cy{OWPj-Ga>db$f$repLvKW6sf5r?|vQmxZ zWR^Z3gXzW1;0Gs779@tQ#1qpoA%E{0OXg48`uSK9wKC#3ulYJ7OcDhAwpbu?2+Eis zk><{iFcAV8s~N{K(w7Ak_So|US#?;l-@zCptc;X*!!aqCMA>BdrOxG;DM#lJeKya2g?7#Z9PfBneMU9$#}2-LGk59ZI&(bR=N!vHR9eG9!(O_TL3o zP2Zl2ZRvtdd6H5(XT=Lzi%;bJ-G-hx=xS%idI1a~7|*_F3G&SnYUb)?E(tHQ-F1Kt z@l9zDdq>-a?axh$Xr;MWAJs1(-4`jvGZ4XP(>F07W+U$B4JxK+sI`dEB zEmoi$dsG>Sl=0{8))7|iZPlIyGQNMA^aO&pRecQmA@(v^*MP9U9Q8;8Q^{}@mnFt9 zmY*zo)o#8bueE;WHVZdETG*k13LWcO+eVR;#d#{+k66mR4-Xl+ugNOPUZLG>#pP&@ z5pzWk zGaBFZqbM!mdPg~MT)0Z~`gI@`hL%Ob)VrGFGJ~a^G#w> zqcTODW(d$O8ZbGEkViu?r1vsp1`a;dyNSR5GkY%BuxNb6q#8@%$sLWEJ1s0MH5l@Z zn3Pvphq>&-EB|9}-1jK+A&7%&nQnxqCDvx`MU48_5y{_L><3$k-=30&S8~D}w(~qH z^T~R27&DJ3)qic=DD(FHC10hyan|LfH05a7Y5?CYEiGdIlZA0d{3k*D;VXNc6Km|d zFFa4<_YUj_vTDytdhDk+CKK zz*$hhhB+G)cLIEbtmy7_BnQ%k@T5vJ;~9ub7{>+KeOOX2+&%z=ON@l{BYa=?7K9SW zK{#sN;x#<_F}#LpQ^t}rzr7h1^2?YVupysA)n#!3tFuCMGH#QVnev8N-z-zYDTbj? z#;?#;ih!##L{1HGS@x~FR$`Q&0P$3UI6RxdyrH9>j_=i`uZ-gH^nSMO^Bmix@~{}~ z5h~GTt2H)AIM6b%l~3SeoA&)9yp-}oAE%E+e8f;RP;aLYJ8il<3~RnWqV*d{J_cAg zVt2{Ix7;9h-(+FEWL#NmS!Rf+>N95uF;nX{p4hH?Rq64PQ=y3MHr#v&ga7R*I0~tk z3bj5e#<3R!4KU2<4^;Q7+)@&UwN=T;YxmU{)rahP&M4#=P$I_&LEEpT6QJ+T;}eUfMUC(_5X^kgxMX{(mhPV zCw#{^3gm`5IG?Y+JH638Z=%ip_5;EW&L#0}^lDr&NiD_4sQ58pwCxr4I?D(V&uVB+ z?{r9mJ;UBaBg;kSFh|&_wJaWt9o$=kvinB<`@2`>>nX2}sC`e_k7;BVSO*Co(K{6XZay zuv$t5uvq}IyqaEii2N%*macTE+<*ar9c%0y;iM=5=5(P-B=GKhpTNl#XmVUJ(QB>ici(bti+boJ^gHbvsP$$A z$maE9+Fobh-xqVIk`2EAdLz?|vRqW}c!`DN=^|ELqrOzz&&XVed%#&riJ)cMeP9?hX?#>0@9%_0$`7cY$9O_E$uZIPH z*90xbc`q_Kn7};2s0UFT$?_`Yl^?V-!cFx^EaKiGb=A7fFp%TKS&oQ z0));@vgxx-M%<$oaNp4AdPYH~aLg}f1FakckR z!Iw4yDZ?Au_VkTlUNDA~5(jG}dmE*c5@-D6C;ePWc6l5fDBaeW5h1nclsGdRG>QaE zgdF+~eTxN6QWsqAaU_Cyp({hs=yM?+t?1*2X zW!2>Pp(-h6kB5r#LVVmts6OBEnL0Vzi5q>AXWbX)=IRBM)gMM2f5nz6EUd|ZmI+Wf z;Ra|#lGNiPro=vQ0aKrrwdYbr^<#+&cii%_s!|XpL*+rn-8+swg%gX^a=me_3!pNL zp#}Ryc7GQN*5QXFY6Q&r%0Pw+#wiNAHa(sIUV8*Sv07=W`5$K1VgoWzS9xuxQqWgQ ztmPMPdrG%Rv~YIS6dc0?gm;61yh$%d&h_BY2*Cm}T3`pgm#JraIeXgM=F%v3FdOp= zaA))u%_wyY?eYC0k7uy*wIYCWX=zC^M6gG8i_2Ret56>K@;~huK&ZyUxKvpE!ZTZ* zFNu1S!JFREU**ePj4t@RQiP5RbadeGN#!F!HzQT<>C#0mZOgTf{R+@2??4MBe9ip$ zwfs~Ivu0LEsGZ#Zj|%{Oz32tUK160aRCM)3BUYj}l^?@%SlBVapUA3{R@EV0R}U7b z1ydKri|W!Pua{=tVIDfgTAKR@3;JVUN9Df%YGdK0 zp0YR%Dz|aDQc|2BburrZBG%8^PM)dqATIvFW#|n4#r~yDm774j1 zDlWaADcnU{{`X+LqrJFzJ8ZOT90&1nJ?OkCklrydeB50@furz@thGZFDs1OolQ|O> z0OYlPEGh1(J`ZE*5W=HQqL}2*#qBy7N>VpSP4CeSr%Yh`L1f8MQ0FA3ve)iEE~S$n zo*_}|S&``LD*El&kG;qt0=Ph|qe*3^dU%wckS0uXlU}m*qSgxt3PHgZ6@ESfk7~;U zwoZ({+fS?koq6vrRg8zc9~?DKSgRh{P`@o0*ATCEh7jqdVcci^l;%( zuk{N>EnV#b&g)%ee#gfTz6CFiHMQ@QvmgG3WboZ4Lu`L27xa4B%GEeZU?n8obT zy9~m3bUNY{X+JCE&s#fUuFsb1RXxq@prpuvu?B~)a^kbpI7QL876`)WtGyw4xFeJ^ zMFqnTcxYxMa4D`3kOg`c#*KBWejy>A*5i8x$G*y`^UuX)dFi9F&l+VCqmtYu2Se^e z9Q5exNs>}@)swuw2~uyqH+_tY)ZnUq5KL4#d||byp8o2^)x)BmY2RLVYZ?~y9A*58 zU*+0G5(b3fyr#2r_Q`|#p-wJi63^w+?P?9{ce?TOyAZdx6I$G153_C2l#J38pMDi=p;6P{6ZVLS7D%0g8eDCzz^Ba=>9h+wMN6Ivi1eI| zncvxno7QZ)7>IB@#l8GA>!=W?8UwgGxJvn$noVjDZSFR(P7P7MQlsuZS=T^u0*(c{K8i!Kc1(IPG*@Kn+8Q)|>o9VaIN^ zj(WNK+A)!-_I)1Hgw1_E#Vl79YW3?3iKDj?OMXY%DBxWkf|1X)P*HJ5X^lmgau#Y{ z-87fHDT#y0`Q8E;0~EAZ+iztXcG5P6id3Nc>;VUIc!%?!CFq>%2LXT~@sI0xegV+3 zelEHrF_vY8Y!xjAE@RValiu*$q9_=*hw)sgQD@Rjq7{Ozs4xMRXB|jd(haNu#?sE- zMuqC^nr;)L3N4O%;c@>RyCAKN>`WlgyUes66?Re6KZW)W$L8kK_>F`5OfClEMg{p6 z4SrA4rFg|)u+*!_E-AbFl6%~A%FmSY%_Qc_7asUw9_I48MVI~^lwNNn>Fws0kF?T> zU}6Fa@OiwKxjSX@Mqak%XGzigSP-lOD?u~bN-OMG$g1nNX({VyX{E*&gS5Bz?OA@0 zk`FYs4bQ4{v>&zpdl*NeZh(OBi`x72tdk30PtjW%SbvVMKuntmXawU2k3$SQ?SXwb zBa~l>hKV$|l2+FA8C&&VXS4U1leM}=FGZ8n>!_HK>Gk-pvj#s1A)l3KoOZ>7y0Kv- zTWRGAC*Je%Hcj)(Ow7Np;e=6$fqd#NU-Fd39+r;vCTh<}wJeDrs*S>SGX$?`CBz;2 z<%O;Va}y?X7lOl8lb(Cgx>qa!nxW4xWV0P(;^rJz)9Hh1(!rn0W9A$?sT|qK;T~dH z^|Zo`hL#ix>z&(JMVBd`e^spa7BZy&&SNti>8S%-2NTqJ>R29x;Y0fN8mT89v1{yT zZ($;)39Ovo(7L_p#x98ZRv+oDQY<*f!)KXu3xS0*J`MUr?hys06(Tp{`f$+0S+@CF zg$no=XmCy0UGkDJEl`i=P!+)DW$xB&BDwK~K z6TK!san^??`Z<7Ei))x(gj2dhbiYr)f8*x5Wq%3}ZxdrXohtDn83fYkjOcHyKgGo9 zQ8hSzIwS@d4kVVkmU-)1pmgp0Wp^=d_t&JstjRAkzUgT8V<(tC28z97cGM~mp;CpX z@|R4#P-)RA<_f$}KnV`AQgTz3uQ35wECM|#Gmm=WpHiVj-mi#uA_!SDZO~!Z?Un28 ztlGa-E_vWJsu|#=n-360)arH9-=bpQ^S^)5>i#{*9?L#l>l_yfrFd`GIGFyZ?yk)L zy00bYpJF~fX2Uq<-jEJ_&hCDI$VWR6`o(+M;PU`|5`rG-WJXc4Mxxyear;ny>`+h3 zbkNNy+|g@CUVru)+UM;lo(;Hm(NK)cRy%6?7 zupTEbHMiRgd5woc!{@I9K2gKx)+MOjz8EZ*z3J__TR-B|qXJ^5SfNvd7kt5TKzqwQ z{2_^nW~6paRx2 z3lOdSU0Db!x*HPxF#^hlaRR{z_xicv1@njsuLlBYQjDGgzhoI=61h5S{)nVmw}i*x zJU3&38K$A7#lufXI2zjA>>g{dA#D!4LaYj-0oQ*6U5Y{^t|AFoTwxUx*ZqxR9^^0v z#I%`Gw-A+QkQ(@Wt`W?^TJ;pY0LI1b-m^_Je6Algwmz!^ZrA-#!uKu1tWkhsVpdJh zton}taY^B6?11}LN@%O25q9A0O4y=kIGz5+#ZUjcgCG`e4RA^fo(_Z10px3A%8KTeSamAfQ z>%(E{WV;VO4j2Sq%BwbOeeH`1xc378oxNs(KcZ-*fBqI_#raMNw|Lx6wOnhqBpt_6 zBSS7~g~y&P?XOq#VyYGeYaCxhgOxd8pK3u?Qk0?+i za{?@M?9m@1&Wqak?Q>6+{Pqt(HPuF(b)Ra0kE@3c2<0in?QXw3lDRk27u>GS^i1dY-{Mos1(zuwSk!m4xUO%dkW_`z?i5v^00#m2aF~ zwVn`|d5ib$zJ>M1zjj*#1R>Vsy{T=YlnT^1Ab|G&b3{~xh=fYwo6V;0tV7F8MSdq8 z7kR=V^;mxgA3x>r$$pHwftOccw@pA`S3E_DC0F>Uh;@+FJy&r?4Tu)`%wt%6_?M zOCz8onHp*AKb+E)W{dBsmHl#6BcuRH0Eah`!V>6%+EToz-?`JXS~k`s^Lg zOZW1Rjvs6w^C+fjtuH;2O1kdoA6pCOcpYNmQedHiB91acyg4=3knP)gfh$4Vlt6(F zD|l-9T6O-wFulFtKeB66T&n@WWg8`vJ*(ycth56?3kL)3J3n=<2$QMxmqwGmF|-l? zHcEnzzD9e2yC`%%9jn{2mXhkF!}rFu+J-KZ$aN!$V`GE{XR~^Y$ls_agt2P?Gj%uYZWNS2 z?4O_kAzl38`i6tQf+15`|D_i^2Z=jL`Gd!xq-raz{tU=l7oT4^oVfxllztEeo7%4D zoToqMwfyb7Y?3Np6K`YToC(}#_1ahR1A%7MKpAe&7Fq9w)82R$0hyYc={gDrztsmo z;dj2f7^wa8M)3?Q0U+D_`kU-^5luGN$B5K^ZMRw$l8RcQMqQP+zAGLT4Q*^Q8t7QI z*_OdjIM&uz7|)+qY8;*NuVTWUyikh2| z*oQaUD*wKOu=>;g?*F9hw!ZGmqDgILx+mmNc>BG>71K`Y)nvpt#l7kq1RDoW&e!y2{`7KN^e_2iMojMDgp~yV?%VBi$=QDLS4%TPs z_5PUF(I;u2FLUq_HUwKC)( z>1T&kCLQpmqq2z4lZqA3rk`5v+FxOn_@!00;`ZjLP?Ncq`%(RuNuA^F*>towr~J;o z5n4LZxf}AusGHmxpW80urvFacyZ`^Rq3$lfpF=Vi?%zY^CFVSvy*N|*9xaygt56Bi z@qoC6l&~9sonjs+hZPQWDWF28zz}gkU5lt?}dzROakpzs7p46JU6>6 zhAp>V2a$%Mc@sDsBi}&=Sxsw9PG6QWU@S*%j*pv_E!#+J@Ytt78AcELQUqZ7e|;Gl+4|CXL)01-rxof_*T%)&S0kzSqoz*V^@#F!guIXX>2P;|kVe zM;SINGj--v7_bJx82MSsz?yZv>6?I&H5%eOqFsm54GatK-!qSEU!4u5jV+v^wTT#I z#;$`!lD}%{dDz|u_^q7Zf4C3QE}Jz*i8lN#;{NX2W%Tg7XcAC7njFm8hnSj0Yk1l? zV(c|6-1KcO`A^sG!0j&|vD2Kwro6isvwzmiOQ!|GFQ}(Ff76zthg)ta?A5yxp{;=P zs_u8zn6^ONAB~1ZK1DGxQQ+FZzxpVu2JOFkbknAv#4(y@V7~v>4JW`YsiRU&Zy%p0 zzRC^ZP&?ybpk)P4--1803y&L42Jd*>clz@8zrrZo7=Q%^rbJ2-WB`u&U=~es9M;H= z6>6^z`K5U=c$Z}?koT~S?dxvvy1{Y#V`%_X=>?zPj${jnadA#1nXXd!&)hY>B7%6l zJXBDdLhqEWJ?&|3&6`rwyBC}9)_n0Mn3jfNxFS5~Ll5eSxi zT~LbYUkubydpYBW(+%V6WMKS?Oam|3MnC&%1j(=~yIl8vMb132L+#joT#jgzXP17| z@wOAYY$${=JHg^OK#;L8CD-wJUcn4ij$9CZ;@}!zF3G=M#{{$nHkq_wqONhl9rEIE8c&VYm;;rt@3rByw zJ6V)%`5vS5%pNXU^u#}hX$py#KRx~?>|(Y0oSGduSYuo)bMB`>Ih#$BLs@@q}1MUSccQO zb7SqLr!?%YoHpGBRN7 zB1nB;O2Xr6t9kn$0M%@DmwGWDHv8kDE^tbL_tfAm?n{7CPF*h)YFBKpT(aDg@>muJ z>-Yh#fwkGsYJT44R+sLr#1o4A{M`Vu8)it6*}wH`FH~~Z&r-ec1=xp+bc(P?)zM$! zIXNvpOrYW8C7mi9O@STB&=FN|J+a*Iyw^*i@Jrx}c=6SgF$F_{zqxMdNYZuHR~u1d zX``Ucbn)0{r~S<-ojW9rjIj1?8bu{6W>jP?V6UjZVJ8-^VB&CaQr7ZtVWRu%&ZkmV z-RrXn?y=7_kUbWy1Np3Gg14Avl=JJWuIaNea`ir7Sfc(&YSzi9Rg8xbDENj8+@^DV zxKbExvc|c^AiX%ZTnINw^ybb$mcUj8%3QS`)*e1u4~W%y|98^D|L>&fwA^=9J*=dU z**nN0KSIg+@0Y1zBvE8?3H)S4Vh!p=Qv-djVpIJ?Jms5%tfgauhQz`hG7u*sL*M=! z(eq5tTtR4G{1w(F$wr&J;=>6t%ubaQvYY<=`&lx200zz$Fl}sH&=W3bL7L6{m`gEe z>nNCvvSL|a^%Mn>yOK_)(H(qM6Ju=#WW`h41cNTWzC~cdl(@DAnQFtm|IG;sN$IASyZX7`P44@bX>9z?5Q*YjbPr$4CP-cV0_{?~S+VPGLvkigJ>! zd>U~0lb%=ZQDjv29nk@KFzghjjVBv0oX@YIa=xhN_0h%mk=t zuR|3@QBAF176LP}TQ1tGS{061dcwuD{oA(pCmt(Lf6SBD_rD z3!7Yzmcdk71MeC}gR62hT2PZu**`gHrJx~r&zJoErasJ@N%AUEOG3K3Jpy_X+lP2~ z+**wbozx7!&le~bTftfGUczgB3OeGh&raV+?hSFYfufKcyz(l>eRp2rW2>tf8;uGa zHZk!%OLzgx)F+gEu*%a2$zs7Cb91K{IuQy=oZ+>~uZpexcXkeAo|RNUE~RL~$a$6^ zwygkRf$qT#+!2uHuCi(6x*uqYAa`aE3wVQ9JV_X6`!b58{tc?VXYs%}s6Rg9}iw zZmJ85mjXiA^h#7!pnCxl8c5P^hQmX^83k~68;;LgqU6A<(~KYRo#A=$qISzCc$R< zV=bWr;f_vTE-c7Q-EdVSp<|q_FDD-I?81Xdr|+;p?`zGWX>EaEp)r=np(E9PjVMCFXg z-IX&x^CX76b0Sa%j1rL#x433tzp(Fan325kv zI;iN;l6oHOo;40z_BMqBqt!?G(irBStZV3A)DJ&aePt3|KXIa)8(z;RGO+E#d@SZn zORmoub_7*$H{){gc3OIoq-c$QUEW1$t^5lP7%0~CcZ(4ye&%=Iq zCicr==>0q~to&)F;0ubMSXWm7DW|({_$5R@Wp|*(t60}5XHs?#6Qfx3ZO<|0!;{-d zH|UuWaK4P03?HHWyX?cvZ#X%txW^Br%o~jcN_dDC@1{2dY%!&>?MfiMF zE*|LR2!v-@}i!Q_%{gY&ss2+ zSv}1M+1DK;kSLvtaUj;L)@0ok;K(3=V`|*w)6IpKz(zO$mG_<+dJByp=ah~Gb*WHj zcLEm2$6w|TkG(&yJu;P7Qqg;2^F4ZP?GI6;45wYevq&9hVJ?H_(ov73#VT!UmHwgq;P{-PL+BDg_zR1<6n%lpWc?*$hc2x1*}1^-x0Jvx zYe&%6lk5uTYk*-r!hbV2>h@c5?-}OMa%Y@C!CCx_+bjTc?7#Y$$7mi7b;%6aLFCVd zdn)$hso=b*5ll}E4S)_NKbAf`H$BM;%oSW=%-%*7;<+g%l81~&)Sj^MXu+mEd+3-N z|38|}JDSb@{o_{cRn?ZF+Nw>h8nMe$+Ss8%j2bm#Cbeo;QEKlIR7q8d9eeK?Efs3l ziq;;XD1LXJ-}61kf6l>?`@TNc=X$@d*Lwnzm~jx!Rk1Sqe8KQ2xFP~6R5=}hZvZfBRJ_3wP`pGL-i=WFhnLBEsrF8{cn?w&t4=XgF7-i~4cT)vUAKB#4I z84i^uoeANio#dUci5VD-kfyX zM$i6M^|)Qa%4871+*gB;)h^|&wr7_sbZcni;A4&Ey;9& z&QEd^F^~6bnlz<7>0@wGa(5Bj1@&~S(dXj3_Da=6azko8jV; z>mYQh1shwbx}YaQv5d})TfconE_in*;CS?8ZRcdI@A9mvJ_zW9XY#oL#Mp4wrHG?c zFN_9ei|rK%myIynu{$W0hGQhzKE?79v{VA&;(kL`qRGgXXB0N6XHnZDcPj&w{}I4i z*RUXhz>d7xM;cwXLZ`8O-+wn5RPA7lzp-N)wy4g(m5`0jDW$@H)JOY&CaPD|MF63? zW<93r#jzcx`ZnicIuQk7KEoxm%eh$wrO7(4is!Hi?_37L9QNakBc(L7u(>{z4(K2; zpUC$0`!ZIs)^U)H#_sNqOpG;OV~!3^TPf4?yQWGmDBT6o5K>!;X&(g!l(BV|96w`( zbeh(jQ*r9?5r@r#S^CgOkmQkE$BO8@BMwweOzY#7@C%)!J$}E0vGe#iDL;6uBOE&c z4uU#IRm4J7w5R`!U5Za0G&M#4dt*pGYziX%x2e-IBLs=ou)%rLI=AHZVr9z<`%ItS zB=!|)qOhA??dLV5g*f+tD4-fWRI5=Hf`LM>vKvS zUiaB{n{phHpnZ0V0;Z{y4)hC^O?C?OkUj@d_*R~j#ymRdmpkTafZXZtC#vga-g6yqD_`X5?Rx)kSBXbiDsth}OjP$B!TQCfD`}*hPh`l_$jKX71a+qQ``ST4jqR`iZrfrCTY%Mn zQj>Z&6yI#2JNCN*_g5E5?F8{r!u^_Z|LVCFk2ua|f|s~2nu>h9&Vl2j4#tH3v;BVS z9P*BH8^%*RXii%RWdv0p29G}b@CYs#gIhWaahnvDwVN^b)Q~XM6bf%(qP->gsT!87 z)y3Yke2>bjpl59-hR=KO(mR0~x55=!Py3a-htHSK0Z@6u1Kd}?Y{jmeOd4gNn%Xyj z6+~P6-MFzIq3FRMoG;$1hSd==Ws9VALV@nu^yQ4Yku+XBiK;J`0sec?DN9e*S_-L-7Y5Oe$092 znNz30NR|{Kg7fVY&m(H=j;y0$$;PXM^UC~5##MXhP+%RNA#4hnAt7c z?@Z(kthuhWe^!>sId;&r9u@}Ry{|tL3CkZ9=vbnncNRO<&40(u_#=t|ZQZJL#rQ{( zbdFYIH;INY)UV)S{1AJ%%K|@v1}`Z;%1;0&o>aP~`zIRkTz78)Kke0?ZOYgs=9`qu z_1v=QaXVI=@Bzht@(;PryGouB4Jf&>c+qyWT?$0m|5zyYnb_8(WMO{0y ze(^NXm0I!0y4S)%?7dY@95C{BpOxefb;&X8<`_cpzz3RhM#sN(p2r6q4#c-~4gI}R z@SYkTZxi$i&Jyp*G4sI(FqJ8tj-@XrkL7R>tv1>AKsGyOePL{vHX+5d%49rUq%v#3 z)JE`TUAq;1@24RdKhf}STbp`SIZc&mok`cU1hw%NRxkIDC8}vGw~;W(vo9I1dF1$? z1(>2pv9zic(8_8BIFM*(ZY&dav){^+{_czZ`=oLO8Wd4K$d zxjB6w+jok1wWTZX*7jvq+Zp(%LqY?E-=rMPLVpxjo}iXze|1l`Jr}h4*{xA;uar0kcAC31S@%Ve27$SxNoQef`+n~9UlbUDX+y9L6 zR&KnxuwdKk-M48Xte*7a!r$?>*7hp*M+CjB>cFMbH*~SxT~(RDVH)~1ES~aOZE?85 zW4;s^-b?M=szJZBT1-YOy|h#Xf}jhBlqbMn)O>OS>c5=jT-{ioIq5!d(lU(BkU9=* zy6nCyhfX~gf4X@GQ-k+ab`>M({33f6=C$rN8v9HL4J%RN7h}fo@-u=ey>g8Yq6R4A z3?kT$mm@$JlLA1`52!_b!b*Ywx1Y)Ws@DXRy|jq5crdJao2lDhCBf`R$`bEK2?tM; zPalEbgN!57i4i7P7XRkM`Pu?uRMB*@)}2lx>L`^3^NC&%rj%FfnJr+Ompr5lExJ33 zA36r&&;7HN7UWvbue6;L&SzCFnl66nwFb!=?*jii_@Um;R^H3$fUR#k)+AbjWHIBn zN?2vj?__}O2qNg@)kiMJP&mSMHdZ};>zKi_#0bq+YQ5a@<(z{}wCnZ9!hJoIGIQzG zTS44~gG#u{gWQi4H2SYroz)qS=GiPq2t)QWhi7YqG_S|-L)Tyj2G)l7Y{qZM^LCe- zQ@G?wu~EdL93~NJ7SABZNVrmBr{9oA##EF!ptboF%y^d83XS;+NiAw;*HO3Nv|wOo zoz>;(dxoNjC*5=6#6QhbkI^jp2NxGqOFtz9I@(e<6q_N=khN5+j=)}jb% z?i^d((^h8mgY3*{p`un#6~YrZWSr;?GBjKEo?66oa12S5Y2(RbE!aTaK!|!|y@v|0 zzZ(jfb+1jimv;X3bUs>ts{Rp-%n?ZLOx-{6Zgn8)2@W!MQM>H+)GXU)m_U9=1w26Dwe0 z_QUD!s$Roz`nMDJz z7e_#{{8-TX+?)1|anp!W^rHOxmmW*7FEGQV@JpJ{vZi`yPs`KuK@j7+f2y5RN2_UW zyA}@aRESVG(BP`N|9Oy_J@Suh(uiJzhvl^nv*v1+g27anFdR^7^@^*)Ct(tPnp+b= zdI2d`I#ee#REt|xRdk_aZ_IYjfPKUo_qvy{L2;KT<$qx|5z_5h|{xNWlMXZNXOB4%=H+G4G4sf1 zx}6<&KR@{=dESC*F%L2^bP%{>p^nu^gJJnZxV1PaUZXMvz%%orEsZCBTlr{h-sv={ z`sOIu;~L4Le0~9<^Br{l3213R&p#yNrt}nQIS|@f+-vpb$}U2=iufob4D0b0(t%D@ zEPSV+YT#Qu< z&@Q2w%V4Igq_ms5wY3WOGf!7e@mR^q)LQDPPY%__HCy0ZEEb7~SD)|ynF%^DR{5h6 zbXcHw+1b>Z2DBwbB`U6|3UR;sYVlk1Yj1Y02O>pd=A7M9*UV{`4Fge-(#O9@`sQ!$ z#H^u&lPkM{OKk-*7aM)$Vu18|j3O=-udjlJR@@cu7T zCjpkR6b7e~|8z^qe_ocFA=PBP$ZX7>)_Vxr;*PRUg%t;8=b72*bY5ldw_Nxk>&Z74 z6J|u8d*#nGv&_)70I};Q=KCOP`ZO4at;fp!fW!>Ddibl<++SO~1=lpx9?vr29dDloo>Y|iRv+*Ahof_|(xOF9x;J{8UTNuyBd*<0rn~BuEU}&Mac(1P*cAU7GE&db{LQK{-gF_>R&ZW@P88$9hJi$RF^}t zaq(?sSo`^fZG%siwv%#2{nfEAIt-%w09mxt6!a&!qG@#`VEZN9uRVshniy)rdvIwF z@@$(+Lj1@#T*sts?I%eA<1swV`e!_NqwKW>n`f{2tCBFR98XNhS3hNizaQ(C+*rzz zHyeA*at7Yg)&x}44R|j`aa7GRF6!$c{3hUEkt@A0spBq6_xl8S!W)C=0V+nP2E|8s z{1fcUq#!F5?V4#5Et}2Si=ww1L2M6dXZW}xQAzaIoJ(#hcUp0sS+8s%C@IW;J1bTH z1<1fR} z-UA*)HLtbybs_P-Zh{_+V}h3gczX>O)t zXEyAbl#XG=9iXG25H~XngEELaqmuGq5yR&?2eOby$r+z$WSYshjA zqZ%lv=lYzib=`7z7QiXAsoSayu@V7hb0aA->C~asNxWR8*JhN-s{Dl-p*nz(+tH3E zeZ$b9PVo**l=C(AzRifEkw~{zuNC$G#g3uN2_Hr zOD!|CvTzJt{dNE~Zmv1j;hY%%{KDt|jf8no7sEMc(*wJaX;9R7mU<*^&UZAMwbBs} zt@q6RB75F2fDghvZV$qK_{xb;EAZfYX`E>N!%;-*YJILm^dMpHJ&eO_M+|h%%g>tB zr=rPjB@drsU=jO_m6XfhP$A-m0ws}Bao=Fy=|dyvuOLfICai-2kzkouwp`(NjZATX zQW@Du(_D;*U~;q(C4TxrLhDxroI%wwd`OrN#wkuZ6;D}IYvUjVhWW1Tp8 zA}$ulHoc466)#W0DSX-WTeW!rdG_`junW-3q4Stc0`mwBzTM{egXG(o0E2f2aUtD9 z9LTB+t>DD(~MJ@8ii^ z?SE_9j8F-Y>6NQxiz4@G#Q4UsK4i3RrMu)H5p1iQ*d>49Qr#mIZV-K}FrYly`ED*( zS`!cTlgblM2P+H|<>FJbJwJ?Ppd9u?k82emSDV&mPKG1TIBKo$KuO28B16*&>I=X1 z0!>I}?(Y8FyUI`svT)uD;$DInQWe-KTDo0*^OcLIj`C~Z^aISE?oG?$V(A^-pt4e4PZI~ezc$YlC{TXIf;E6w$Y6%| z-C4(WTI9(vhTBxD*QrZFv@i*HU=S*9rjV7@S1Y~oE6V?P^oqm!R>)z3x$bp`$qLA5 zIa|N$dXf~XpYT}e+smD{DE8Pcr>?srN!My~rFolcfsm zf`GvfvaNn)D*m>EGz?k%G*!EGd3Eo}aCo2Hw#(H%&qTHmndlC>VRzlcNy0VSaA``o zwCs3$QRK4S!NtS=`UPzk2FKJ61)Blwj(jX){2pAN%WqIbG zj;no+H3Vy0PLs);V@+&XN%Om9?M+C(v%KtXR7}Qhd+z%lfGDF z0zR<0e^(s>zk7N8zp@2AU+2&J$bC9U#RPw_ppZW728d_7uWp#xxyByLDBS(#!*L4I z*zRS}0;86Nq)lK$ZL*+Q6}#V>8yRYV8yDD6RE>c&Op=DW6Qqi-hmr|ZcGT&9YGp5s zeNX(+W$5lzkp|K*_aR3iH@~?!vH4E-mgl^Ep1tz;xoA=DJD)f6E$0b9Ogjh8iT9UXJ31)8Z{^AlrRe9x%RhE_K4HT)y{4A!CM3 zPiw9N*4*lE+MfaOY`e~E9>-tn7Q&}5QN#ckqHGj{f(o*TjgFh+qAGH$2!D{awy83jPsXvf3m%|_~Ia|5}NLQ{9 z99w!l4j~J&FWuS~MUaD2%2OFAdTNXW3IL1Mv^-vnGW0NC4Qk+~n$IP?8@Cz>U!+F^ za33A!{h;CS0R@{fIpOURXg0lY$u27&p+dvz9r~Hklwm)fa5KwD8J!64?B(Al60v z=M|YL;|3tums^vq^_Lgf)~6)ORQyU_&)cRp--j(J&n{FELnjG5qqQ86=Y~aK3Vq*Dv$;iNuLJ>jGt}r8nzP2;ZG?x>bd%~7@)3C7_8a8fW7o{QX^e> z8*l*JH@Im>pT9U)Uh^(Yi?VHR-fh%sC4blaikg6aTmONz`seVkPlGXG&u|#m{1~hwUwD; zR=qQPn{olvR>L#5h|q7`j?&TYx97!8Dr=^7OM^_(EwR5ZtB=bnYs;Kqc;fB0aW-3A z10&XF>+>)2!YgFA9!gMaJbe-~HN|}GHUHAxS};efMh7Jm1;bus^h2HG2);k6SE&tl zNw~Tx9zMFpa_xh`JwDaOMxQMhRu_qr==Jf9zwn(0(N^-_4Q&}L^W;~+`d}K?P`0J+ z0MRXv&|0oA=-q1i2Z>pF3Ix{fs;P_G4Zq#=K zE46JLOF0RiwX%1KXxQiXCztyVPVD^W!U@*+`FV5s9MBwbB%EprK{=@aUb za1mfF@QRry-S^Zx7AqjG5(1t^<$qt&8LFHV+7dBKum_SX;x~G;?%!rc-k=Pbc$0E# zw!Et6zPk2{j4!4`o~9gD&+ZxYvUy$&s&XKG)I`WTQ zO$38|Ug8px=&gZZ6`H3X$f5VhflGA)cxZQ$E~Wy$*yRxD6Xk~b(C&eG)fhjhlF!-< z)uJJ$Utab7U6H=`_j#AHn56#KSlqC#HGYxjFPnGprT+_)HJWVnEEK@o90X-H{M1wq zYWY~%_kOq6Wn31WMBKa{57pLfUU+c zbdKP=PrBgMTh$#awlM3-$N}y+uqLyL3x#xto~&@Pl`f;YGj|cwhR%<$xSx5=!F_tU z+k#jteH6s%L6o{Iuz1EPlg6gsUNX)D9oQst$~bagL!T zSNkCv_`?DuJ$}z6!=!q}xXt^Om6kz0HRwaC4I^_VdYk zE)qPv6(lVyKHH@8-W}%%)Gmx~o9hc9q;|JZSN#kQxvZ2KZ5T0s>tT|CEcmH~Ww8;! zOE*nYUE7`v854gcR?&{~HLpukPqz>0FqOCUGr6gTlN07R8_p&sg`7& zIz}w$gG1i5AYZ~ho!+4$&V4c9P+b&kY~_$oNsFFHkRrG)TU+YWzT)2#W;Skb-RUQ0 zcl{tVT_wT%2r?A_R~_E!sk(x{L#%>FbN1$1>}1Lod(A;QVXLe0 zXe)O)&}h++*Tz{nJM0#H*(XoR)GBr`%fyrZ)(slLAAmbFbRL8=DE$eKmJ{VTpO=pr z+dR$1Hjp4h1FU_ls^ZREf2`IQqz%5abUdvZF=j|3tdL4;+9Zljttc=Cj%L|~-2nFr zbV)q%o`-9dL6RM@`7&A+U!#|6$4vVKOCj8B>i$;gTNkck-d4p+$&n_u4@XZE$3C;4 z8_srQ3>{mg9$MttCQOShR04QyGTFI)odvq<&G!LfOr-1Gl;NQuz2T@C&lOJ#-#Xy6 zLeL9~^(nywah@&Bm)K{VLW?6OPaMMOD$o?*uN;1DIWo`J?L7IfzYY2*WgZHch-#t9I`_khXQ z5?M1&R`XAE)!pqYiV_Y&kcM=)b?lFQaS?V?KQ@ZDya&I2hm7s} z)usI2^wsMR*Io_E%%hArj5b;4L^F}Ft27)y;*M_ zBa?TEhvcsB@09835?BA{1$dL~W;N-x*%;UHN=L454s@TmKAjL%xm~heym+QvA#3N`qT0ua@{mr;8C`&5QZGwo9|1 zeBgavcwagOG!cO&h+eqocU1D9fIf&RA}uVim@Qm?bv0uI0#W$Kd7z5thG#u<{PyI?if^@uMlpJOyDdJqi;zjNzz&l`F zSu7gJVqNL}+Gd}$d3Myt#KY8wjRI`IUGw%QIa|^wDb9%70K3OPy{xc-xM(4Ye^@yDNY@49Iz@Fv@$CALhBZhYfjteRe%J# ztfN4F1`3drFBPr4Z9SJfpBMv`1{|L;<*$dSLA4rP%~g956(w!w7N{wbnLY10OI9Ey z$Fum@mK7&}l2NSXdoutIxSRHx60q8@k^1Gd{V;yEr04JX{|}xHeuDI0;mz%o>v=Ht z++StGp~sN=a&g6f`%pd(}|awK564S@c3ZGu&eE6Lk(Mw@53eH|mWy2L9eoP;t^MyF1^Okc=&Un;uL zRoYt~Dj?uAs!wCApF-@smZ{QMd!AZzh9OfR0F_8c-x)|B;#K;7sQ?ib5CRL=qNcvb z;eg;i7eP;j7=af^8M#qT9>US|D;}mFX3p6;mKr`?5o%Spo5M?ik zR1G12htyw#j}4CBLA%_|kY6o1)KdLTic@oZh7Blv!9UZ8_)bd4FZB?i)@R>c1(#FW zHT~Vu?yX!V{QMqGhH){_%|FFANCyRDeX@A@bj=E-B9Jk@qS9Wn>Z3QV!QUOHz`md5 zRw5DoP;f+OZ$jVJe|g@iLawAjx>MdhwPA(0 zo5)cW^UvCJnii`b_9jHr3p8K%j)BVrVR{6uULuWaVlS=iN-qB)F8KA`lFEO7ORU|YZm9VYFMb<*mi4)Z84~`5vbrJVa?kj|s%*ga|LeW3CI3ls=EPCu;61|a zrBk>XSd3J;p=G)L!7LHj-Gb;UqRCdQ!Jml;L9*I+S4$WG|COg1=#U2{IPWfH2G(xn zNgtjQ-}6=mLeKW5o}7fGx@@i}_0=z7jnqvsz!Ye`)W~2J;wz(rDqtomz)v>+^aJJ0 z!~x7Rz3@;OsE#Vc-?xh%5-8hCu+)cx^2!SHZGJ?72nEi z%(a+A(wESivra)ROu*K{K*FbA#e>Wa5DLiC^;kbUs(2m_+s;wFbm<#94;^n@ioO2M z6Kujmu@7?TPI@F5XWb(-vaZ<67B>^^Y}Tpv_}R&VktF(t^7Nc|pX%rEj03UX7?tCu z%XRm&Uk>Fe{Z{}_ZgA{H6vx?uZqC_1qy^X%@K9pisVsZ|Eh(=h6b{g%*zQ#tt)bX4 z^pEQ9EA2|Qr;x8CUDFVBJB{lTt(X|Lm>dU9Dr?Ldv{ZMCWOlDDF|8JRp-!*P! zAZ$u&c$|aRISDqy@7)8^!+Qx<==`oO}wF75vjvObti^kU0 z1b^?=n_4B1&Bk0k?giN?^Y`!@0hot%VMd!R8ApmPQUuoUVGH4Ku!F@=w3{Pq*fq!@ zf25f+rqmR%lapGy>1PItj0FPoOlY6C2KJ^E975|hG5Ax6lpLX-|4adFOClmzRoLFj z@ph}-U!h<8{tb4=6d1^zW1ek|t^TK`#==#^+K7xTaADX#z5bc>jN*3vN;eQl5F@N1 zc+|RVZLuEn!`ci;7xlInU9NqvUzP7%ZYIsKpH#STM%NT3Cj~n?X1x)Urt_jKywA&? zK=p&_P3pXNsUX0=Duh%{CxJiW2iN7*bknVgfKEhqOXibp!RtM7zhqB%GH+mN_HTf&|QeI6& zxaeKxO++l$el^p&qCqe*1Y8n+*JS!frVO=QIa%fydsvzf8p|`3EjN5kjQMt`C6b-j zK7u|%9%$zf#Od{~)ZD6W-*--xAt{iCvJ0h3gB5^Y{C-y9v=zBu{Pk$a;1$Mft<{e^ zp3~xf@jjI3!dmR{(Gq=f8#ipr`5nK$^`^+(>o3Xo8Su;qO4ed~N(a!D;%Bo}H55;z zOO~w}R`6;EtQ)jwk4XiWvs=YC0IUZ`<2mS!`TD0e!WsTFFR&jDG{t47N8|t5T((d3 zf__z(Uz~~s@6&PDZ4vxz{ix{{yw1!;vhmgE{zcR`Yj$L1u@4^W{K@@DM-QJ2U*i~} zc?Q7zquf#Pirjp+@!?qeTn3(A$L+fCoDJWmky7g}_MsI;7@b5Tl7-#3a^_mKiQ@B^ zWD|mx26hW;L=DxQ=^XmmKBE(HGkN1hn6a=Hcr#%19I@6@Ur-m<gHskKv537}NA>u;=87oGcBfjgjWwoyQU0L^Z8izZ z^*1t4zEa0jj01&{hkJ{$JE?(7LrwQhl7JE8xop8LBw#H)`1v!GnY@MQGE(Wu3R8j8 zE>%oH87(x0)YzXJQV1odH&*qDFUKUb>sg~e$o0g=`_zqO-r0HnAhoYT^*XE;;MwTuh8!95Z<_JolQ&`_ms=>vI_kc?En7Gc+{81DW(IuV<-H$zaujVjyey z`0f{{u&*q}9|oDnWSf5DoxwY&aT&r5MmDJ;7k~>-v|_b;UC9i$32?vmJz;{qWt9&< z8x4ns$YRnQRUb?41Ef~`QcjN(kG|4Qu|UJ|y@_DBu1jo!3W(_wogoFQY*)J-^gY)379rs1w`6-ez_dZi-fO!-k3 zmN03Qx*7l)AS8VSQ*xW#P~BLhkgrFksS`YaYz6zu6FuC%8=Y@KCCnhCW8K(qq-}}m z+1UU0DWBcV{?m%7zP6q2X$G$anFZgOTvD0e5<~-6+v?+-sreG-+&qSBJLyu9y1!5w zJK+yuO#iQQ6ij<9)}#Af#esmH|low)My@ zS8gVEY?y67aEkoQjIbX9hB1J*WUjWKAOd-Y{V1@j=4xW-^8UfdoVX?9uhCwv$e=|} zi~65voW~@wFln#1YPB0EwbnE-3YMe%eEd5^D_$voS>0xNrCo%N<8%I{n(Mc`{Z()0 zXsRPc3MnZMQIK}0FzA@nZG^oJnUEHCckco~amCL7oF0$3(0RFeOs5B1EaBq@$AtN( zpCs|tSMGoCh~dye%IXuvPA-}pRQ}kOFPIm9Hx6Vz&&oOZagCUMLumAswVeon1LEvH z&gpXMNs@#L+k{Ko#R*E72GKy@B7!$f+d%|>%Ne5$`2Db4WvNNzK4p?GrXVZghbyq3s6X0a zou)%Ku7w>JCeZ*?fcjczQO@4mpUAG!)3`It51tnw5h@Iz>W#*C7AiNtuel4-kVt67 zwaLhirM)R)MtnSmn$v8xRF2^C-aX`G6U{y^ZIrm$m0&|gfoHl>l}D0xlpF794}WpWI$wSFE1%=NDVVew4>Wu8^0#YK2Yi6-B>HVxikhU6WsuHv3>X?Fl|+$jJ_8s8@D1NUNz&?X*RTR zP{|;2FS3k(r5h&cbNTgUCK`}>Ax{F)*i=lyvM9h80hG%;tV zVsO_UAdp#Wjd6>4`+e`eOg*ck$N#`|IEHpnV)xhx*XY(RDS9`t`bPNq$6yOmW4c{En=;#-BhlUP!6Yl z$o#%~Snw;BJg7fh^kz(2SD_T_^FQx6U*$}&vihhm+|s9=zx6UTx*2jmUM%uZXtx0d z>YJ)M`G|EUp^jH|>PeFfkA6m(!a0o;on<5k543-&b8RF;xJrm%s6jIQ@4FeiE$^wq zp5D&0Pc#m3ng}WmWvI)T`p_Nk9SHEndG{ivKMq;l#dbaXrZIW5sNHjJ7t;ddMrTMF zzEc8X75coA>(vKc1d|c=3qFUqz4|ikoaN@8HG2p{F3i(aIIpUChX9+VWY4DZrf+%oY^E&QUu)3^1l03B6nH+&Uh**PVDl^&XL1`4 zQ0m-q{H1A|aEBa43U4Kqz2CX%U2%EUY}B~Ie{Tz`Ba-pp7yyR$Ri zRa=!Bqmf@@BhIzcnW@KEhw6$LKN|D7tz}Q>-ut2usZm*|>Ml~mJiXg9Zq_%m`icgB^H$4LS19Nv;5$dHa`IAI#nPWTV}_r; zTBQTfJYOR0qj~Ltpv%%kUw!aRtGTpl{k2#-ny8t|qPjFuhcwi!?0@#xNX}YqpVAbe z8CHH)pb#Cfu<={8E{~mmmnLEw)oe&06Z|hOV06V?pHrbR_+)3d-A9!_(_s>W35lb! z?i@ME)@Z5IDhZ$1o4YQz8uD(Y+$d6Y(BW{rp$68r(0of{`!M;~d-gvO)xI%$&U)-HEGM|#=ohT>qB zIk(wMZC`T1I?i-B$zjdA1|wjI6M#@DK=jdKktB}4 z@4$*&t|%3x!b*&BS5S0QUdKBM)|?T!4!Eg;R!7p>1y zCTh{~ASqa%x``OmP~{601`zL-xZ0CxJOg7s36n7pjAq*=~-J8QoSn%@-L@ zu?bphJ@+>w)hCg%I-HZ?z+r2X zC+6-8(sww65O46fRo0_BuX$}TYc-T<0uN6+=v=l1^;G1BvhC~=-#L;qxO7h~i<>+T%0b2e=O_gQ#+bq+J@!x(W#Lpa=sNW$8XUNnk zlJ+dLH!E81a7^_Bv(;P=%X8td;}wVi@KQ5|ebtKjwe?ebIJ`qaAOXDQ#+jjRrImEN zlfxgac{|tn@eE-epySbDYabQe>jnbAzxso-MR0d(`V-OL)OG?m>Xju(?%RmFUoEf; z`3m}eA@=ka>tu?biEM;-{O1~tHz`v@E@Xm#v{OKu^eSC_rO=5~=`#s0GI&aT1m|H+ z&=ND7O`@|VW<}{}u!4_$1-kgfk2Kpk%8lXE!Yuv79aSf|*pD}MG; zB}Xjk&=fob6pSODqKew}&ev`&W57X(V~g|TDg39OEElqcG_kG!>94Fbf3TW8qeZmp zj|!9(mwWg4z#H_VhNL}4()C)J1XzlHU_Cx?)S-w9_yd8$#8}3&Lh($-X8(PeK6n{s z0np4Yfrw6J6#0SR)P zJJ_vFPG^o=g!Vc3dUhLB#B_6qMBw}HKFBBVS0|khODR)p{tj~C|2Uv?9~cW(?lOF< z`}nf5N_d69n+6`f?r>eJKh{l59|T_|IivV61983|pe}kHEe-Q5reakK>;%hExl(NU zFeY}1?*>_}=kVU}lu%mGVY4++YjoMI!>d8ASKam5Mu-W-vR8BqsCLXo+yPtHG|-dB znK`zregD>n%B<^ObZuf9XyGsH{O13uVs-YHPM#2>7LxO~j7P{fXPV1jVJ*$FlKQ)N zkNJB!QmERu$Os(!E8!99{7MqYjg5=x2)V|WHlc3zhdoTI8$h`(4@1X+x(@`7W} zoI$8`p~|Ij`u=&|P|qu1PHaJ(OuYEA@Ykr~bv-0J*axv)Qg`0Jq8N4{a(Aq5-q)eq zZNV&b;(WEPJb#d{-!WFhoRqenixRkvHm!C8UyfX2MM@g*c`*mKrchB{oVDG{+!U$0 zGhFLCm~k8xG;pcU?`km!30 zQJc#{Ethed+OCvw_<^>5FI`YB)cW8ZAp?q~bnMiE2SH}Pe|vPLyXRG|zWq98W0m4o zMq*;4D%`W8@Ud*UU@kX9%3V*hbgWRs+bTrQZM%Nq#=NMMS)6DU2|OCmT?l}gL#g1P z8Kw)Q&7s?eeyWy*n{-D3?+-EH{z|`~Cf`h@k5XTab(seiU+n1Z__m)MZHGHS zN7iNYC9Pa5uMP^_4N>|Psjs0GcHePuOhz->Fv-bBqxv|F_a-0sF~*|A(TV6DUt!HnsCxurp}Jt|fC0$kjw8I>riArEJ7vDHZg z_1rMqWyup-G6F0qJI()m#K-nY>$8dg44)5D3oE4k;->Ws;rA|eZ7JB;K=7CO{_D&GB4m4YeC*#dT>&Ryjivb_dbtinEog-M4rO zHd>uLMZ@R)Q5S1FnYLCV2$EdeMQe*`N-Y_uoJ!6anP6uM7g8DxJgX^8`;Osin=Qbz z{%VpYl5*K(y~MA_#I{Jc6>?FJVY3s!UcBPo_@5Vm8LW2sOuu~K6`u8h4oRLe5Xy6= z)dHPFF`flkMTLUEE}iUMocE9{@<&!&$XU7Hp`a^}G|DiXJXlmjBI&anm4;YYxH&D7 zeiQYOJ)mz32`FEa5lz-B*4cnZI(bfqoQGD?Q@H9pJ7Cn)tlp`Mv4jD=clCB{xDjpL zxhlHqO}~U2Fb2f$xhZ?D1J2kU4iI&VrLHzE>@|uG9#v|ZMXo_;iu5|i0qO-Y_=Hq! zxg;p!mtN53<;B>~ORJYqQ#2dLw-KROS(~#m*Ez!=|(Pzw24L15Odmjon z+^fw!=>Vl=$Q^RQ2XB6EHlQt9U2N<9hd|#ajTrZbl$9EYoDwp2HIUO}*F9N%;l+S@ z&)&knHhb$~yO6pA7bv|JTQ-7VG9*Z7Ts5pdD-oK#F6N;$M;UCUOOLDMYiK*dOMej% zJ>uDZogjsLYf?ZBz~}qYSg=rehp)FF814CF#8SOW%)C@&_qV8~Cu}@qHz+)xml~*6 z>c!Da!pQJ=%4D;ZPPT@;`InMYUKsC+<2dR2j^bVXn5O^(;fCrmUWmr_fxrvLgkqGf z4S}KIwQLrz9o|UpW!$&@%UeAX4!NM?$l&klzke`!{QPT_GCptnAMHo-G+(dn;^yT@ zdBD$fD7y8`Eu;Q9ZWs_XsVI54h=?iO+1eW+^K!b@%eCBZc4k|$$xS6pmWG$J5>-T_rVG9Zf4y!Mlr^(3_VHt)jV3kg?uhmxqR)7 z(j}94^Wp(fsrEYnkFhcojpzV=UmVX_uPXDnx_Cjz>EmyOQS(J-`4Q=9kE73ADsuJp z%VWohTXlh20&b3t;ig2yVp4-bPUKon#}IqW%^rc#qF3pxe%>KDzuHtznLQvZ4Nv&( zo{M_aa$rE6EE_nLE)#%^8=`tiH<%am=}}&p*uzgYF^QftI;72BbvEidx`js`Q=SR) ze{a_&hhkfHD_j0@hn#AM985-oaEz1Bg%*dyN8Cdv+1&3}6{nNPc{|~rLFqzLibV7~5-U`J^g6cWt z1lSkPDBYz++lX(EdmdLXtQP%pJV;-r6U!}WuX4VGIJIs5jHE^dTdK#HI)NYBRfhO7!cem0jB7V3D2|C2!bTt2;o z=0AZ1P)xy=IMlVCff%obGW95I@-8mW7Mrc!$A(%>Wz(~?Lmhry^!O{U!?=UB^S?Y} zX?S)4OtHj0eJ0)0-YWxZ6RrOL9xHpV40HpT(Rvq%S+FbvMW>c(VZ#9l(_Js_)SmN{ zK4G@*{cHJ0hNts30Xuibfky4@8)1Z^e3_?gAkK4CUF65Do;}sM<#0pNH$+CIv#GVCfgk%_+!yZN;fujUyQNFvhF}Kf3lt;9tEo-2F@(E7a z$aMH7P2m|R;e?bJ75j_IdrV!6Gn@LZ?=gQ|X5)xkU*F{nhQs{CHvMLD@`0mrDI{I8 zYpfA8$LyPcii@$#lbq9WhTg%@Hw5W@8xyRtFE)yYD-vCzH-Fr+e!!xaQ+Yd&9^UWg z{P>B&$$ZLdQEs2&noSl?6ZMoCMBg(0%GkvdN|YD8iaUP)^8kHIOo2fe|u(<$2`*5Le&(;%!@{iB=(SlWbj-f(%72dsX+41V^i zpFQ9IuzF(o8fDNZMzSypNywi&QxQ`1 zj6#fWGa8U+dz&{2SBvL!J!Q6JVUP%2iOBx=!63Nf?D80cLmp z6FQX~as)5MUj8F&7b-^=LAN%y*eSAp?RtXM^7qW9_nL7del}{=Zo=eeDc!yjT-Rqk zCnWMz(Vr!iIsS;v^p~T_FF9)_vH#g%95V~I6Sa#JcesAZdtD1NMM>jTXf4XMoMP#G#yg|IIn zA6&iMP<3n96||6xT@KTL_DvGMKfWW!KjX!Tzjr-)qUHz|+h~E-L461u|9eU0t_101 zShQt(4=3V1b+N>X6=5QffT7hssAZ{5`Qe{`1gs^#zeu-gKc}xBcb)yTWDx{0(KR5U zTIZYKMc3BJi1zt*Ug^cz|3Sx}zg%9RaD(RO<@)P$X(>T!3)gPPIZdMowEp0s`RcVY zRqvk18u9v?MUMG~iR*>^H)_hC|yn425clgoS-}&&z2<006v9ULLp(xDfWrAe(GYzxr;3DN_ zVJM#P1|dlbyeU1>3Q2*Z6KZ>weSdLzqB_tfaw%zU#aR$RZLsi$G*Nd+b_A+c#6ep6 zn6EC=(7K1^wqS>&gBEb7$4WfP3DCi+xW+l1rIX)A7dMv)|J}j7jDY}8`do4I(&adh z&ZOvD=`XcW0*vTq&6jB8TFeFoa1B>egy+y}~+kro%9LN=lsqyF#YU6}mO%!|C( z7Yy0UJ3QY$*E#&K%W@Jns0H2{Y1~d3|ID5&+T?C++3(9~9uH8#ok=v9$O08Cdy_zH6LZ>C-$~OoL&FC1<>4@oZ`CyevUL zUZUe@`&Txyt-a<;auwB|;6X*Bx*Z!E5}ttdiNj#{J)I)Sn;tGrxR_}`)xy9mUe)xF zf_Tx$~CXM%@YE!2$Vs&db`1=$LOHsSY!2zXs) z$uB+fb2!fbR&&ViD)V1{sPy9`9MB`7AmANKg(13LO-SKYw){LZ1aF1riNff#dP;U1 z)9s1j4_^|ymOH}zC0@@(~jfRexM`p-zLdOK7gX)@Fl zL&muj^IRAHnE2XZ-7J$LE#c&=#W8$e~d+ zr1^9n%r#QGUR#fnd9%CTd%~rtE$5Wz=f!xSGb5$Pgyk0<_>lSkjCkJv8}U3&vIfnU zS^aj-^ql{cBBIOMW+lF+ESx`DEVlg}fEC266I;xAA$-?A1O5T=Iebf^v-QWJOhgJA zx=cr^OT+j>(WuNs=p;cv=?iGpE)v@Lea1F@m^$g*^lSEc85HC-8)Ys;Ifw>?dl!d( z|C=%`y;4@r9MJ48RZ-*EDhegaFi5pecOwH@yxsdS|Hy?9$!r~#_AYMm+MpDZSK_b^CI{%_BwaujiK()NdGX)N;Hj3%uC#6MVanE9PJ!p3OTWDKXL zxd5)x<#{H9f5~h>D1!EG#M%8!5yZ}TOabzE@-zW->y#y`+u^)roC;sOPVDXWG@bOm zYcG)Jo#wc(sUmmY^)9pyWQ$(5tPcjEfZ8-KH0Ns-+}M4(reFE3V5k~J!8rdq`^P?v z1=9g&`@}L4bGO!^Nj2q`p<8jk9B;R$(y}umlWJc5Fv&&lhM4bkmDz>?2fAyu*CSOP zKo(AfDf92Ybb}IKkIq_T;rNx;UNqlGlv~nr#{DP%Tawmb$C>m^~WYq!7|h8xje= zTNn7?(5qJt42YSu$HT@5E@&BFcZdaKKZCk!-O@WQZ9YU^wo`fR{O|bL{>OEhbW(pg zeYq2jE6KY&jzI^xq!ltQfb}F9 zo|-aumn9YGtot_G+^|mDh|Av15WU`9*J9-it1AU?oQqs#PHPC$!vg2jBHFYdBV!v& zyfkt|<^fq`Z((<|LE7Op`YIXb`*#;OU|#rD=J-&Y+F+BypFE3C2gurZPg6C?x0=l_ z(C&*aOp-2q^}WhFbcQY`iKqTu5KUKpY3}fMCVZ~%xS^55{^c_!gO%@5XIaVWt$v@`YzST`f)shzV{g(P~~=S-)XZSna5~ry#)HxXRjM zzs1+}uxg+>2B%B(n+QZ8R5;doJS? zruU|weZFd;Q{*+m`88JZE+&53;R+GTK%uvP0J<1gR8N&|6JNnzwtk0dI!YnY(dsuS z=-uv`L;k3(4$0c$Q|OKOWNg#H)QR8-#!E!ETCKXH-980`ojOE{jZCQ4%gBDzCky`= z6EYs_tkXZ{Ch&Qg^{_YNrg%FS$_d|N3~@Bfxw?jxsuFU{KPg_C`wPl2y{Rv~1IHU>@I81>g`6`p z+NdZoDgublr?$JYvW`O;m2tElPqU3ix|-LGty_7r%<_`z)9+U)80?YUds1h8!n1lMY3}RfIUdX0}_D*YY9pG*WiTd(VQ(u zmZRV2aF^&9eNqtevA9bJXt?#|na6g_4PouT{=q=#`M!VChP1IoUUm4{&G6m0cVD^@ zLd_kmCLEq|;1ov4&&m2u_dXXd%Y8*0<6f095;Tmj1O7a`te&`AOaJ|qGtZFi?2n<_ z8|iazJ%87IF>V?+>_yc3BREEJCNvM;2zi;%s@rbe>1J|wE}oHp{fBy$ zwfI$#ljCg>>@8*cPMb{La)uRyvgl-YWv1g?`gLW#lBh7Qr&B}c`Jg1o^6f&rJWENNAWNfD`sbqT-Kil{3L?Y8G<;VzsB7hG_TniDX}{NvoCfF zoGnm@SK!Y7Qn$&k-s@Cfw$9(M?RxfA@8Sb)L@{vq-$|?ercO8q4&O=5*kSPATHjXi z)-fZVDP$9{_gy~~>2%LhKDPOIQ6?``74r){^y_Y~*$$ebH8GuIAOaf<tr9R63WKLXHJ?Jvh38_IgdnNp0*)WPf< z5VadsKa9hlN9jL0b$JZ0{{gfW>}p!IGgZ*)A!Gkg$Ku9}LBBL{5;b_wgj96yHni)xwg6O&nJ0hGE*S{00u@54f?@cfc>o z(3Ezdbm9y5M2>Vl7f*6rsVi=Kpc<1F@hnI^NE?KM7az$u z|K2fI%z2(c*igKd!M;uS%owSjedlGV0NXoFrL*0748xecMCZ@_p)A#=S`kJO8_h-kP^9Fxa8f%A@oi?skjLH67dgkBtX_4g?30@(q8uH#kGTz z=rvsX@d3rFf7jr&Nz277qx_4iO0z-BJ9;=8J?lPTzzA~l9$5DeA@GP{ z^a}i{HYg->&ZFNb1Wc7CoeY-M?1+1xr!k&0FEr&-+J4<|pK=%|JnsMgozP}{7}j^* zAY(gbvCo;A@y^KB-?jE5-kL)2BQ4c*EC^4+U#~&d!R#nQ{9-#Ben%tj2oO~|!dtEg z*&=w#!?l>ZN)}zrw!8j%NbXqIex#o&qfuX327)4q8>k&h-GOA??JldTc^_nspP`JV zNG%J>(xJ0;Y)G#V1O+b*QIMOb(o1v0e}IieS~bVYepasyH1(D|8Ah;7YX&AeWR$-| zL7bfrvq#g*2#OY6U{iR!PV03;aR6;LWeh_-MdSROL8a@nje~%Jn^XwLl?GP-a_Q}| zVy6n*ZL^l+*+Wos5pveEY)Kfz{rDqF@1k0F}q zNKuuN00)r=u-3pn1`fCu#de;tVva9jSkn4IT>4_26OTaapyaj2@?v(j41ZUCw4)T? zvpi3_!0}aGlmd9y8p+oh21o-TTNp`c=#2&DJ=oc#EvOfdKoH z_ZZSUb!2&`>BB-*ZGtJOv|wSO?{5F>Bl)l#$KnX2ssJzC>uw=AELOL_3j~VlO-MKs z;0x#-`}Ptfh3XT)7M@L23PF`^1wcr?d3E*jy3s70Ppo1*V;fDiC%Ob5LfvIG8jbUYPDf0pDf&!%8QfD1^ zE%RTjBs3p<4DWN;Z+cpn-NmKyqQd%+G`uwiu|0utHx~|9W%157We8I;>2X>5qwL*& zhsE$hW(1Rj#8u~wlZ?>@C~v!nJ1HLw+K9C)X#impA$#)_+53M?o?7xpuDi?)9cq*; zg6CWPy#w$uo;o+RwXq%@jfEaEyxURAq&?3+ocU;7dij2=cUdf5TS-j4#*`_&sP>dN za5{;4j@#7@v}u#obrF91l_wz&N#LsHTy8^;6%n@snfb9P=f zIy0d@pCr5&#yCnjuN7H2)C8Q9k60&r?0b&j*2AshHerDm=#R6Vhg2gc>UejOB3q#VKjW2cf@NLufCmT_?kuR{uS> z?yBbltRDaO1Rw0nU(U)e3Pze@6J_w2y5AXJ;u0scZLq_jc@2;&0Qwe;o6-59J9 zg5{a&*_;E2O7(0J2=em=>f%In02tT)6lIGi7~Ra(tkX@*RdkWV0t!)lU4u80ar7ep zN%^5{y6B96urjlU0B{XLznJ5l+i$lg5u_u&W>+SB+)Zp+?|G3RWSMf5RBAf->)7Du zJ#3M%LEpWNrgQpk=jDgwL1%8!dQroHSLCGR=i+}jwx?|Qll-%^j5fH(F&oYf>5{#O z9Ixt#;3B2{ajOsVGq5nji^1o`rbXiJhJ~i7O>L5%ml3T1e?65vazRm z{NRnCV#cFQjZobFcZHck?t46N*{;8Cu$ z@Iq8aaKx1d&-jISBsx3L##-#|t3m1wsz5wr%q=xGZ9(xndO_P*U4rKtJU+jKi2=2um#t2WXz0C=>Y zAaLIrK@p8LMo?#B2X*$XJ*YpBr9wh^fq+x zpI(?RqR9mCB#O6MaPwm34a=5GnXcy;52qX~6Uw zIld>{9JpNQy7A{+D#RK`b+yqoD~F761N z<|n3lvQY0Rz=WELl?!^ry3-qr!%5_pXcDB%AB)iFv5&POl0-nvwOxK4oPyjWwN=r` zUv4u-axuQkv4>Y)rG7^o>W-5u?eX_He^ubd%23kKOf$Y*X;hITIJ}QN{~p%P3uM}^SL|_F4W@``f3E7nU#yg=pWyh#X4#_MwZqR{NBUp zq!b=ani;sT{THzpA7WRUozE9PqHKJ zb#6OcHY#ByHCUpq+Kc+Worr%ii`Au{5$V&-CwSOjZ5@BV<7qz0Xh-1H{bdDbiWWT* zFU*K^42fBd>dOJr&kf8~7#SyKL$mHwF7_`4y{Sv*z10=D{d3oNza=kpO8hM&O8%;Z zY{3>=Pt(czg#MHmyFmAEOYwmimjh5fbLRln+PuPVj&@b=9 z*NUGKbiEH9_XRzhEh+8)T|lz_yMUO2P4(i>qQ&8bw7hfQFS= z`3e-a?m!qza&L_5I>QqQ7FZ(JESUx8IybjRFH=f`j-x~I?}9cSrGH<&)cl?4KYVU@ z+8oGGDbj{8SlvR}^xZ0?_XM39t%3Ol9yFy#mK6AVxM_Aqm*@mj-tmFT5Yn}n2thX4 zhtRjB!K=owcFP-y`MbJ02F96GTpv8LJflSKQ<`;464xa6R6h)e#t^IsQ+zUXjO0C| z`7*rvi#B{NWb%{Z`?2G!JYeuWj59m@RYeF{U^TwRuEzUn%)~MbH5X6gmYJkaPxP|5 zN3xPNKo`wY`xs}L<4FplUWkgGv<9);mt1%FJ9cmp!7 zDe5`GXvpTpp+datwQ=X&ehN?j4SkHk)4xZq)PYA?PW)tpHRMPUU2pM0{gwjnOkB7| zPSpxP!zSypg%hI7%C@9(OE~SWsy2O>-(Fi&Eh2CY5Zmx?LT0t?^Eh3#zo+Y_7H0HF zFI$(97gW6kMCd=q7Ll2~J?g99#?GgTUV{qE?9o~T`f=W!EprzoC)jXo)*bV^TDusb zuvn~tQRro9kHy6H^!WVX9GwebIbJ#glmc*&J42pL>1$TAhVS2ue^Ykd8V%~Wu-XbI zn=(|>05sQ>h7dSr`6lc#JcQ0yFVfl4Uj{H8J0VDZn34&00TDO0sPb_`V-fQqdZ()(*pbB-+azpt-Qzfm)Ywh-XU?M1-(n&3gp zEX8KvUEQU*JKdk^)@1TEQ>%5Ixa2k80WoTe5$UPc%N^{?C_C9(O^fV6GF={6PLF20 zaa9YhoATO=@7SSW76e*R3@tpBhV#^0PbW_@x6d?QHdXIo)t+SBy8()~=43HUQuvHN zSDSpD^;G^yKp{Sg*B`$*c3^(Or}X@1CQ$vWEnK|gy`mVnTLG_Eiv-bjSSt zME&K9=Lk8-4)VA!lG^+{m8%Z}a$-d9fiNu7*RROKex-hC`nVS+YNxwMk*quEoiWP5 zgj{AKXri}+@+$=$3-#hk>9qKKu$~DqctvfVTX4Fja^!1MW-f{>P!!6Y zbRJT~gOYS11ElfL{@Lm|+=GV1g6F0^eeBGy2atc!L!TP)RZy!CVU5PrL4_l6f+Hy| zwHx58cOxWn9RSc649`P;$OtU@S*oatIhxz2xtqBUL7Pw4{P)it;N#SwE&X^aPc(a% z2L-AAGBhB?7P)tu<9iAGiek@3$XP&%lF!C0kOP8254t>Aw-^HZj9jw%$m?#IB}9UT zJTx9mb6!cG{b%5`Doa2cH7GBc>%CB;`PNw5!tgU?OBLl7%RHEu{=SwTEU=Q9bsOBe zkVf;t8FHI!4{37&iWlFw(#{IDKyX86g(Zm9lFT^Cki1sXeMg8Jnb?*uj?nG!m927) zdWQxYq8Y6ZPoKE(h41I^*1U=anGYh28Nfk0-31~da$7C=8;-MUlX1s$c9(x~XTznJ zowWh)!GkA>W$E>rC&w2CxQIFhZ$Xt-oPPw416=(lUeK+`y6Fqz8X|3S{(sQ*2`YadxlF1jC|bmL|% znk24=+1+XH-IvgHTpl_*&SQulg9++Yafp9X?pY*D0o{JXvD*54@ygZt$mIgodMdtZ z{S~?))vJ65go7IrR>{_y3e}j)S`A0nJLb%H867MH?GMbl5yJQ~+2Zr)niZUMHzb0g zlM8EBD&QIG=WMJuM<*7rNTQS*db<;QI3UfJ2ut?{VjUOiR{Wxt3l`q#*oi(|GSw;Y zkk&Z+c@CGGu(+{Z#!&J-kxN<5OuI3(uI=_fnl0ivjJx~|eaxf4DsHbS(pP0PRjj+c z#bQO0WgTzent+eF9r!r+*u+6M5zngons;*X!`7RPozj}m=^JGxm2l%(Px2Wp+R}01 z4_oC>PuYZyPtzC`YppQ8@x{1#%}+a1Zmfpr=>w{>gO>Gn++|?s-iK`r<&F~0-~=^( zAU_d*fk#6;ZR0g|tLXv{g&r9jU&leiNYkqo`UE2t@schICAB6s*E04inkwzoNQRuH z&OhlYq^w7XJ{pscexva9c!ipz{-IR&D(V}h0NRCe#~}$T-B+^vDAJ|sM3f8j{-H9h zZc7kcN%c(0Md_#c!i+(xl%X3YI zP)G#7!8IEZEeAxhn{v4Bo=NQ4VvSHh_f!&f5kqq;BGPc=*7*xEp6W+ZJEwyX&soX4 z3QjQQSS*xo5lIW{)t)v`N9(Nn*lx%lM1MT{?VOJL2l0JvF*Cb;1SG6mU~P~<{a zr9-qs^S(Z?v?)9vy2ZEt2g-xpx>GVlvZVfp(4>y&=l%8xe{%taxx0<67bRzpd15?g zn2@2ol;#iYGvaCU_0jIOTT-CH_asc~)@EBESALe>1+@!DSmcn0+&Y6F*sFi9oKCK10U`^ z0IbYOA^62F)!#(i^tYecwdoGTrX844aQd!mIEeo>anQ0_&YO`_X{S{S^2+!j;gKKy z{N$YV=mfB)FzF63&#VpxO^)1dGPtbL-sGG>V_P$CVu$1MwD$QD_4~A zE+VCi*NG;abSsC)0J=bI^OjS@WP~tbz3W%s?@$LsxGD5|2*ZJ5^l;n#FREu5)x_C! zdCu*|qs@9|ja8J_ahz{pHIZRMD$Mz?#BkAmI8&{Dv;SDF zDzAX_+sop#c=g3OmM#|*COcvv4!>5# zX19v1v_3aKp*=*qF>=&TviYDNeT;KtzR|pW8hT-N#)+#S{Ljik`{loP-ptxz(D`EN z`R-QJ(d6g9!s?OXTL4KpVJHXZ$VsX3M=kxf&F&YR7A@6!tS%6Qciarushrj{bA4gv z^GAj_Uf1J8%T_?MPh+;Yf%Tckf+Lfpb6U~V${wBCN4PC+|B-E9%(_)h)!IB~`ALY` zREI@SePr+MGqy-AW+_hUCh@*Fhhdv=U+^jd>dXOaEGH+sUBnpJw(D+PmY|KMnxUlC zx+Q0vkZclJwAGMzBj`brMfzLn6P2xuD;5UoOCO&RteSW_K7I@t5l!_yyk5f;Ih0Ni z?%8N==Q6$Ww4k0|A_^;obs`f#|LG3P|2gIPAToXTCF%xOIV&*|LB5~9KDmDvS3_3P zaN!_&?EML)^YgQ)vXpb?0+GD&-MYUwqkZD~M!ocl$OFLwLOn8g;%(uFmp>144LX>F z$^K+)@;)E2KhTzAZJ_enU4s|by-h^FR4A#KD(xV1f!%L<9vgeqXM5XK!fz0fb7OhB zYI3rh*uq&TgR)pOx`Y$ah6b?>e3+lSC$kMg5^q=1V1QQ|dGXjmgr&c+CB`=zb>!cj|caQFh2RyffT_la?9R%G24XgrTdy!)-2T@9@B`oL?h7ZzrXlp1P`t8+TAfx0QBAT}{oI9&*7-Eq6#$G7WYUc{)fWdc`Ll@mPOrj40X zhBznY18GD}r~bDbj`4@ZQ$7k0O#RCtzjWn$sn!@ThoJ$?_Wylu3;*}r{uU2i4rfis zI=fx$?b}ikH19TmR7gv@(Rr5(3v=bivyHmB3V2RL(7YGM8a2A-7%b)T-rGW}l0N?V zS%3?8EeIo_B_7$OIvq*=*r21qs2H7V`r`hBsS$rKdye0XMd=x>#c?jF76CAivN%VT zMAE(Xf%a(d^xxL%q>gZ|&b)YnQPm@Z&nvahowz_?DoVjspcEkt0Mewdla66Wk^DO` zhV1|*35y*^h)?%7Gk>E8N8p_n)hA+}8Q+SLGpCuINsw~c`PbiutH}~+q-O`hOnDF8 zovGz_=CF~GzFl_&KMGg-2Vdtaj{*^Pbhf+f*Gjt7?w1(s?k|G5KnobJfBnV2rc&@EV?qu`0pNak_Eg(1W> zWK2;j4l@@A*!5tzh_^D&j>*n1CRkZ&u*6p?qYbAiXwYV5T;Yr-z$7Gj9x6Z!moM|tsykQ&dKVqwGcSI%<+V=gA8QBN9AyQ)a!9$)3TzXcL#B(GVc`5`(` z_MDC^W)fr4bE8L1-ZSD~&soG>^6o&mJc=8IH;nZb3Rovj6QZ4M2g-|AN#_H-GEU3n z=zlkPF4$PuK`w+MQYDVEX!e(@Q5cXUKUc##1eI^eQL8YZE4=@A7}0-RdAg7RkpAaYr)u7fYl0MI!4`@JjEQ?)ee>7wp?J~ITook?GqXXTsGm|{ZFd`j{N;F&t%IdLrCRF_<@!f^qC0Wmz z3GX{iKHD$)=GH{PmTco&>Oa8{6DE@`cjk;cj=bDI4B7ZVR)2aKeL}lc|0C|8g{dj+ z0PPWbOGle;v1yFO#^r!9CM1w#Vc%ETGa>)kDMcRJhcyB(=hT*Eem$>ZW1BAKv0Qvg zI#8eqGSNjsw<2mTNv;hjacDNBtEm}h!0jnqG4ZKFW;IT+V=7xw1_P$Id9>zfVRxqQ8SV9 z;!M!*L&#$7#nOMa+0c(6)7t+O1r4j+g{936m+6Bi^QBrPHh5|CKZkzVVNhrcm&!pi zzVf(Z;9*7X&jE>@GJ&}C>%50nwto{o;)U6jKzPbsVq#Z+(aeV?I{_KTdtS*LqrQJk zyH{mCL$_>g519-wbkoV-KJ1$~Ga*4Y0`L^|0uv2ufSlBRBY7~CF#SJpkqd@?l zV{yeu+D&CXm#D-alEjPtmFNiKs@ui(Rg~Fu77fkIE@nKaid-Id!|5*Xz;k-`W1N;W zwAl2J5Kf+h_@3^u(C~4htrXXi=;Vxg_Kkb%vb~06Lz>Y%6WwS$2=w?(5435~i!~m# z{4#0Ej`=yrOpk6ddV8m|5p|8l^9t2=4tw)KVTkR4fpsl^>UdxON7syDc(>xG&X(z* zDP@NQh?}XQLJwfF7VXr!$z3dyj_`JE1vG5if z71eY0ATGom8uJl;g*Q9J$IFi3XR2f=E8$ox{+C;*K2r~*VRd8{wO<8=00^H+WX~yf)?7(5a9AidDA$K zzlC}s&7E`aN?Xlm!dOwR6NWqAZl4XACYW=UCaM0n4D`v@iluu7 zr1m#ZZsatjC^h$4$sz;ojLVs?Op5t`(rvfMv-Q|l&fv|<(f<)uajSm(kF3D6F!XS2 z{RGx>_QT?MDd-{(Ai(;?Dc@~Zn%e1ces%U^;%Zt3M2Gpbe@;5OV@}&=v=?g_=WmN z{l#v+QUJ0;@F8wuKyXYTj>hB1t#*iW8}~Qk)_^WRF_cT`)Nd+#(1-lke`uNFEYHg9 zrtXdo6YYyXUW{t1oTbbb_3i6#UYCrD_w@-Z8b;d8IWiqB1%dWxV^3v5lXZh4I`B<$ zL>_gmCEyn${7qdksf?$DO~Ot>vYKZKKnA)?jUnbr?pYa-aGMgt&Tg%xFIvF>Mt*ol za9r42UFfFAiVxDyTT0Zmq=@qnZDo@D)o|p6kScptN*Am2y(_vM6AZdFNWVmiGxv0$w+(YjZX772a&6 zuVg9x?c^VSHGE^JR6j%J3<{CrqdQOcEmA8HcqewHn6Cc#FkNV%L4P&9sMOGr6WxAW zScrR)+HK*Z#cR5wdKAqsh8uMq6R9?-qE8XYb+=SjYKW*kUyF^&lLM|U)^*q?5-Lzl zb40d#e_BodP5m}HL}iOV^N#i>gNJY~x=^Bsd*c&7`ow5RG4IO_!~zJ)`)| zfV$|=0%w`K2jcXT&c<$F<3awxsd{g}q@KxmOy=ptfGR&|(Qi=tyDU68#rGBXh_$TRc{HCd{HX1+uAUEI z`P|-xsH8(Vw`qi%$%5$M&gM(Ir7z-9l0=Qwrc{KP54c4LGC#x<47T4NHUW8b-Fck) zJ2|_n&V|7I>}$F4z3wn$-52UHlEm}0 zzamEeS(QY&?k*@7M-HM@$YZD77hKFc_%b{6zOSf0NWp)|x$D(snGEzUNr%oY_$p_k z;)lK4!^c0Li@&ECvh0m{Puk6M5Y^-TjrjUot}I>5uyQxiH6ezR45Vc`+Kw9|crJqABJApa*5}C6 z#rP#@O>C^TWVIZodOJC8FQ}ym61nbqW-(}W#smyais*g*2N`KjB>Vh;Neavw&`mvFsZgl!$BJF8=>J_ z#%j&ec8v^{hA%UwxYW)pU4sK#L=D3`Siz(*ewYL9lEty0T=7Spp-mZ}RRZf2FRo)- z(6aJVQyySPAb1eB?*y>%ZXp+q+;--6NvB40`;zY&vDO}U@MSkhie8jN$GjUnG(F@L zU+nOo27azA)4YwrlZ(>O6V~Pp&!AzP|M{dK6)xE43_p zYV}S@&Hj{iv>x>~$ZeX<#ZkfDvh8OWuJaadOFm;xu5?Z&d#K%06+j85k}88ZZ27W+ zD}~AWN+ILK@{GYcYu>7mZ9&p%>*(#J@#38Hmja^)0=C3kM%F8if7}g7w{-bH@=Gm0 z!P(5ae)dOFg3p3@bk%v?F=bs7xqBwM&#KOvmBqEfmIwT^d=cndD{SMjDzI=D_ZU#9 zPA0@yt|_`YC)q*lKySGkvI=hOZN!h>sSB3djSufx-8bTk|3G~!Jt!(&tV0*1&8QQh zlKqb0Rs+WTS7Y;tHJygqjCcd#JIbTX?IxtKq-(U)cUvWUi7{wnUFc=o;3@xEr0aRl zf3z+!qp1Jr3pV$IEiU)YnC1OS3%98xoh68`w%^!O6Yd^5%hz&goqiEGz7L3YEoSVr z#WVH9h*t>8p+MYC!U)xKyR@}<@WK8f_}+|fU?qAw_O$xS2Pq&?(@=4*1YyeJE2V;!W1rw#p zZXy-ccw>frk%Of?972SK08FZvsvq)`^zva%qg!VxXFkKE)I;Dm$Q>u3klvF`_smJI zgAKAo(YW%LGVaVrC#}WNTU1p8)?;BntX8ej4=$hnZf1|uLK>&%VK?qAb2}RsvELK= zJ?E#P3(g8l!1|}uuRa5CXdVL+i$*qGGS>tj)tlv$mTa&1?*zCO&k zrx_tSBFQ%=Pjp&hgXPe=FTM6ZEr2rIy8j+&wZTDfv~X0kts4VpikgeSuv!r-W=Ud9M0)FTonW{j`tA=5Ny-< zNl2;W-^^?1ig1Y|bk%$-B7pS#DwZVw(;yoDj#iqnKmVaIC-W_+kiCOMiR37AIgL}j zn;Sxh_m&!=pHWTA9+^d6=;j6z`+xXzro?~oXKm7Rb&I3zmP7Q}DD1&_(|%ARl0@jC z#Nczmje1uEa)ZaVbBfV}neEh0+0j=@VuxhIk1 z>l(PTmGSeRzx)%{dp9K1!c1Z{fP)9)!5O+5xLLEjcFZaj!x9wNxL(}J%dVftJK9xP zf*(89aCG+r6^L2PJk@j6benLv)JxP+`C_^gmr-|#<|3=>3~SA2Bal6$V9y^YU)BK% zRloqNkPqekY&K47qrjIHoLQ3s#?(_F@PEoqdx19EVG@^qQfk{7s>M7Q4b;kaLBzg8 zjL0wO?t)uq-|3+uOT*~;lkyPr9dqQPDH<^{CnuK9n!B7tox6z zmI_GYXqegD)e|)wAY_kH#-Ke6iYZ@z!16Z7m%oQhpIV%RZJi!{I1!hyV+>B%9}H2s zEWMr=vhy6z^GaEiC-=UXmWtBwErG~(`IH=Unx&84KOHt9yT?zd;FEsZHd! z#jl4qwy}FlV~=_wb^t7R<*fG@cHP4vOB!3nMqhY}TMs&rk^;;&c;lK^!cov*-a5~G zZ3iNg9{7PXJnL8QO#*gCDL9Cj84v-Hvvp1{izg2=s-KVq!gGvu$RKYcRhcIrt`=wO z-cVb(+ysANwGTfW&-YHeDg){7nS1@EELcEtluS~`VPtZCW|iL#$TkL*tw{*#w{)lY zWL13(zfVL~(}5NoEyR^_ym{%z$J$i9`(F_Hf5e+w7H)UAdp{5ty3?ZFd}F+$6A& zFx}0z8$uhu7mjdO^q)=}Vh!c4RZ@j4eEn>RI^}VqV_xKAIT|}?Bic8 zAsNWf&Aa_VH`T9RBYg=Q6&!ml`iS1wfL7hA3}H52-P5AcsGeVk@NH2JTSX!Ljl_~) zqi-Ybv9ovZ8k12wluAO6qED}mFlB1W=9O@L z#$i|+k2M~OeZSna*JT;MBIo2~pi9SFvHaX5i&c-tqlKny(d6dsc)f<>OZt3?j*_Z4^|!@a+}R3jh-BusL7%H=9!lUmEx{}{MT)&Jdqw}UTLGB3uqf_KioV~hF> zSH$9Lm4&;HRu4f4rIGH^FPXV$bL)jDv?miWg01j)FgLwpt%v6w=nS%g4MBIY^N)Ly z-pG32QSPGMIB!q`%0TBU-C8T8u z>0C+{X`~SeN$KzH^SnR5ncoaE{K1Sk?(KcZxB5ur(@qQo~fD3R!~%bbDQJFhX<E zn>$2i^I5J=%;KVnlXj**{%q!cr040d-MsL_cR}XlI@q+el)LZH#Z;>p@0hXKHaaot zmCcslf0$S2_Y&0n^mc22X9pDtG= zXIHS7@W?X5SJ+ulRl0*I;zLk9$ql!@=ts+IX%}@Bvgd*F$0)5dZv=t)A)>(i#O#FV zkzVkV$)7(I*|!ZGwqnE+vI5=>?YFG%6TznonF!&kz>l1Y$W@;Lt4p|b!`XmmdLvcZ zKwap=t!epBUUq4_q_$)*u6TGd=Mz3h>0!5?DwWfKY;20gYca159x+;oLh z*QO#cOi(MAqd9b;`BfCw@6_yH!t3rAvj2MWl&e(%*-Mf4!PW&|9%YdOe1Ag*Qd~z9 zsZFKTv%RcwiDWC#-6^~S?+Z_2aXJ=Xjzp3cReneTfA=!sr@ghVvSesUEijL182kn! zkeW=8;XTMVz4D@qIIiF;fXD@ECcXxy69Eb1skT|byaLFea-8aBHEf;58KL~2NBj2; zn`;BybYWJu=EWD zRtt^gdvJNU^d3eik@2z>3$cx;zU^*RS@%S7Rol6&TUt#udQ`gb))XbP%N&qNC+l}U z;Lji949gZ_O3OVh-|u#|J3dRC&~HCVJ6MI7~_*B^!(vBV0oke2uCfJK!B$1#7?FnKzxp zYm=-apX(9w``Zd-uw2o&Eajf63rzp_>{K!({2%rpfBJi%WB2OHWp-9$TpnH6S$@01 zdk$oYRUNZTDu)=hZQFEQ3jc@&c#TiX+-*c2v@!QlGVRRy!Cd^zcIx<-5u=`SK%E1> zmA_JCjz{JGeJ4^*;rqbrk26`{K##6@-7>r2;jmvUHa6RKS#}|)0VMbVDLXPEDc9X{ z#(TDY+sB0&)<^gvAMD=UKP0!8Z#5b}f?|?wJX`NQE7MnZqH~pw2L$R;EzMXkpE|2x zS})cM@7w|X+8_?`J{hNr1aG;7hH$Il9-%@NoF-By7EezkQu3)+pq6ShMuKo1tW^rQ zq0xUm6B~p@?{;#}fq%N;pJVm#G=Z~OaG^@F3E{(!`)-v@qEZ}ujM_M0$^Ul+@*t+iLXkvZwF z=qfh>1&FS4zwgp&;PmnG9Vz{Yf@73NX&1R=Z7RtN7bEw)WbQb8*mHk8s$o?M{__2%ht;Sw@1Ec}yGv6Z*O99PR(SD z$w_iMU!_7F3c!rUBF+*LdQ$ISwRV@)HFq*4d-uCU)uJ3rz3zjKbHTUGfi*39I5r^F zvM&*J@GF3lv*z`d^E-{;q4$duM%+8%<@q9YQ_jp-JG97bEMuMN@MJ~qBFYks)XnzK z&Yj=i7ma{$m7~A!>uqtQ@mVZ({qF6M(kThSZ}kj*1~%74B9xY_DT4!>ok?u0L^We* zv+zNI(a6_&siG=!ax%mXmAmQIi|08l!(3&GN6*T>z%>K>q#kFv{IJaQF{FheB&bP@ z7ZBC-xSa&A*{JhA0vx!HiELKMamOQoHyAEQJZixDbP=cUPiJl~65VC>i^xV<6g!JZ6ybJ0EJG0WMrpMVYkyKvhFcgf`)F7jkvGy zzRO%I%w+dv;qa2we?uQ$Px^mFb-B`)=Og4vL^?fL3DfAszUJ1M>6{q z?p-nE?Y2i*HO-yA9|~=GQKjGz$7{ zxnhAQ4etxW$HgR)7Y+O#+oE86uS0QhBrY&@BC7^Oe0jCsnC3m$5o7`Tu07pq2Vaxe zCp+4x@wFu}2iA-sqcK@Q57GdCSy`1cE`^Y#2+#a;+`)XZrMP<`a8`OfBJk(#{MQ2i z(cuqQTQ2eEW19;LgX|lGd%>!Ma{N%_jrJ;tGx#qxd39?f$wVcLsP%S+9@4s>5SzBRTrL zbDdJ6=IziayV30`0!2g*)FpXhuD1m`loaqw$;u>Ss{*C z{g22ga^|X+Ysj08R;bxj3~du9^6 zr#Dn=UnrE(mv$cE(AYkB==SEe%SWy@c$Xfd@ORkNa`45fd+6)Gw_&dI|J7fp`Kim* zy1e_u$@Rp+T#h7xChIN4+6?6hNI;d>lHvWw@Tv8~)}aJ$RSuE*AeW+*#l4>%OMm5G z*{dNHG+{?})IyKy0()RDtApw_zmWtV9jO)4RjO1U$&NFeZn!S^b#WY=SMmomG%0d* z4Q$?ME8ODLjTHvY|F!L4Q8QdGZ8wM$w`6=$(Wkdxp@RuHckQX(PK^?LZfYLmh_tKt5ql^?nw@w|5Zi2H>tA90YARxgH2~{%O>;9k}Ak6&6=+__CM@S*$G6WT&vF?Wby&v$D-DqKbhFMG87}^(3 z*SPcY`)_xt#L{osf$OeB3S|T+5H!G)8Zk4ZTEqu}eu$c6Yuo=BN-b?6T5Qp~SD4f& zttGSI;wzHv>nXSY^1CpDK=Y63&)paPW*7VBGU&JPlMlJfs7#jD&8C^}a_lNMG1vXZ zgw~ZL&PXMmF1v8-f>8%GcwU?Mo(4o^vT$98iH&Jd`k*8W+BR-ji5t4%dSKsit)yDB zAivsO5;I-=@o}8ldrx| zwBq0jFMnaO$hp}PZq-W+{C+{K2;nZ%UuTq@^pz@>RK3NV`Km3B=>zQOErd$FQ}c_= z4l0SLr!Ii@h>sg*7-awuOy*re+bO9Y3s6{3oHQ~NUoH5SpLff>$UMLGUR1`e;JcXZ zc$O&6(L0b;61aAE`(N?9?R(Pyd>CebnxF2U2g^-dFqTKfH5%JE_mKaMq8gM1O|O#T+yD}IY7wlA0UuE>D0(RXqj9SrOgLU3Vd_xij(B&+d}?7twEt+EllG8{ zFqPNzq{;?M?b|XXY}jLInt@p1F%Y+-rra7lm^1r??XM|$GGJ-+9Vlk0&db;7?gF|R zK|*Q}v{=IE+D$AvL$~&T@#{NM9T;xcki>r9X)o5>>cDy%IVz^rvOZ zS19+*^EwZuQWd~^Z~5mqi#Y});5kQ;dyfOd&Q!L%@L`zq^Mef)Oj!E#MX zzhvq2g3oTLI`3Gs9^a_p#=rn`LLZH9;wjtn>eVub%0V61eQf`{zaobB#rv@Ps2e(O z!oAXED{yP9;-U>nU_5m#|4g@F6bE|J);#vGBSvb)R+r*1kqAzj2o;ei)!u0Pn=9NS zlm#Jc_Y|Hpy-$t7mgrHFpc7~x5ep%jn8E)wJi%C(&?v=oZbl5<&k>GsG%7jDU6H$ z-UmZ9OE{lt7(vcI7xNrz!CSdOoYzy*jXr;OhHr!7|F3czUw-$N4?LJ@J^n#d@GW?6 z!lQ7l9r=>6<+s_J1I&-wRq9p2OjzJT<{k5vFF%G()eH9BrG7iken_5m?MCjwS`>Bo z9LsEnq78Y9UjnW5@#F#EAXv;6_H3l;$!lguAUhNN7Hz;X*51Rl$1NwKfgH?hyCNvP z{HYax2F;u_ALPIYoW1MSk;^2?#8W+$R1ljP;SMOXjkX>ep$d-`1T`!z)YFU92O{+? zx5ZVDB__NUa++kScm)F1gPzWgGm#-878BEhNtXFN4?5#|pNHS-I|E5+3Yj8Ea!Bht zKyqg3Cd~By0;!#_4+27KQ@;76d@VFzAc|c38 z70D;`_L8B^p#zBrzxC|(Sd6t{<4?lmF?7bT%#>qv<3)S$w!hq;grGy?+}Y-TNdJ6j zwZ5z#{>6g81k`@mg9)Xz>+D85(P@W(TrV}J_Y&4DlrcrrQ#F1P@47>3sn;GD`xjVj zF*VYzA*W8`IXKGfzxg^fm>WP*;n3+yq5K*NBCEGInI}gmzPoeQI~^<-^-$euzEgP* zC91F_5ckzGB4k0*ehZW?i2?LO#s2r-mT^tE?v)-}(W;u;@~NpiN}U*98$k*VC`ze0 zhenJr12Oeunw4A9e(UMbc0(nM^3qMrh~&U!&0~YmuhIh+1@>RXN`t^ZfJmci+=tQ3 z@~`Kq=?(N}Jtt3ds`H?3`d|^5BmyIidap}=zpQvfQUkZ<^}M0k;DB|Uby-C7UBcdO z$--n7+&Gto8SRz8Uo{U?_S3F23^ok{>kNMVcLx>?;QZ@FW3p8*6L_psL>;`LW-yth z`x0mG#}WCuXMz5djcl37a2;6S{^)DS({+U%_$75*mv2dw@qn#b)^_vf8sP0c##oFJ zfgPlj!ux0{@Q8k)-u*0?FYOPVJ&~ao2V$vY?jsMa`kA%I-(4wqvS~{F`clBdMRH&` zyS+aUct|D9+-H4-yE`Pbw|Yqqu`iFhT}sXRNXmLMbL$E5Ld@mJ0g5P^UDs!D4_4q? ztqGEa>!ySw?i&xRnf2=BoMTVuP>nl8_uAVrlx0FAjBoYF?rE3aLO%iKAT~_VT(>hj z+BoCJBf;-H8JKlw<^^dnr#mrGzHG^{$|63)lBy<_T(^r>+rlI=jjV1E!0dNYMP|y= z3`4~}X6Ci0l{?dweM^z07vDc;y&VZGok;SOAMl=AKF#|jR2F;=NpY7c}4whw5jp*G|GZVy&@H6y^+qZx9wesuku zD+N$a6{~utO7_`K&?`+kUck7J!bn*1^T?Z@YwKa-#&HuW@1sv$x-QGf=oW@g(ph(W|uYTIUD+eVEmL;Rp~(j_;^eJv=X^9z71=zI)q`c}JB zD&}NX8wTk(#FxVm2_3ZKZMKw_dCC1$>_WBxk>rTJV|kb(zU>*ufv89N273=Jeq8!> zi#8f>&{pBMV|@pGN9pF9$#{c<=&zFZ+5r}>TJRopQ8qK4W8sT|=hYl|pVyx5%4C%< z*I;mxp{-XlEywfMhsz7Tzk|_DrQj|!y=>fN3G_Tmkhb=k=S(%ZL0i<-Xq^%?OTx2Q za)36Y^}<@#SPeyp+D?_P_j5f|?88HqMsVCTYsTp^i7E`S0vkm=9Oj<;wLPTi0^+3GM?yT!7`36}-V-1o zg9ktmSJKZ=DWV*>W`)NA5L{}#3H{Q|Xf8zhT90FZI=yXK8*#0I!g_kIQOV6FteGoG zXw}KK@7I=H;3r4@ARnX$6)W@Z6h>@SS!=p5OES0=WIsq1WBLZpvX0fbuT>($#5}ef$w@*A9kTFb$sz^*h^(Lv z*1;hxKdpa+c0&~QhM@8kPD^X6RdnJ!tYJSy;s00wXfaqN^z`5hz48hiUVI>xcdO;= z+XLKE@*=%2!_9Rv3p=VD)w|Dj(u!Y|@_maLAEV5Hf8|ozMS8@Y$15$9IyT+Qr-9kW zTNc*GC-D^BC(7wSTcj}gV1$z3sYth26VEV-2Q{H!eDm9nwqdDmw^)xGrxx3p{9Vjq zN~k>G^k-zUh(O^#4N}k*2PR4W0r`EGfK|BsZ}x@DhO43Ss<&4=BNfeV@>YZ=S|0l1 z-f@omMM?BEtMBlPw_f5Sso)sLGIpH|oe$z?RZvQTqix*VFX;@`O83<|lb#P{=0aXQ zPZYf?HZSfVhqMNulBQaCn{k+4rVg1zK7DrDC-tOZYRKo`T%?$m zi=Z3X)`vOs{eqTp?tcs*bqn=bp~d8+4k>CUsLNJ;&`j1@8o!YLXMDFgp!NL&XKJ-I z^o=t@=^J!Ng77fGip2RQ0L~R9>5Y+rM`drsy8;Hm!uE;#xk{&MOE-mHc`%Z}P@CDE zcibaN=hfaSB%XZG9({x8b6`F7#)ygGf9&?^-4>_Z4itv{sI}yZKvjRlwrKN&Lk%Hv zS-k@1VF<~fV}qUle{ojah{{>Gh!9Om}Znpy5rBvlWZ7lF{lXE8{6|$Y5G5w8-l9qDvtwuIx*^*_7 zDogQ*1%+g}Q8PLeGj$9ORXPREfxJTCkzOT358^f&b06Xq-lw;6;f2S5U``8J>&Yiz zTkKqKyk_IgVoc$^?w>ry)WJq?yzFE62t=u5hFiKC+7hm7a{}qc@@H3)(wCblGWv6b z4^`mhU-Fs@Gw!bESJEQa{ph#~Tw67P=fmcg`f?LDoNt_ok(rImb?xk`zY~|h1U2=6 zcK17~Mrx!eXneBDZ5Jp(e{A|FjG=-&>_BE_$0ywuUFTyB<^x?fUy#nL=J;(j9ixfj z2igPELEH6Qlrcu~hO_ugsq(X<-JtXF>wQy`rn{Hhn_5A&=Ot6q@)pTnYSM=(F}kgJ z>1sBD?jdC~sU~!p1SUL!Fzyqnd?WWC&ljREXCZ{a4q*a%i8<$WiD?@#-F9K3XTR@f zCE_aD5nUTPyruZq^QA?X6w`nI{kL`|Dlk^^B`0&4!!LhzNMmRX8n#JKUZLAMOHBAP zXd$)b2Qtk2yk;$7PD8|6-2vJ}zr1<1rI|=Po1J&_ zSRqigU}Yv*-k~mOru!Ac=y}I%w3594{XJzLo0+*spyf5zl9O zm?x$Z@|A07TjKLtr=I+tM|A-kV>Oi2ctJ;v1KUZ>KHS5sN&1*4tsM*yo2%&_hzQK$ z;F>oRxbU&M6I*F{lSAuwi9%nm0*7!N6GJOSuAxlfnR4r0X}N4otI{I5&jjxLzZfgG z@;jJ896^tX501;4$V`ipUPK9qo&#cN_-Htkq(HyChrF12$JYMYcdu-w#Zuj9r)FFG zA>5f3m%xP#7a3p8;)sJU`&Lp^F*uW85EIuPzpGdmMVD}W77@V4pRUi+6w_O9&c@z8! z`+|Ghy)*yXbPbD3@1$iw|8%vkcG$LFKa%&8Z0;EN^WmEE^6cbX-UM~!OCafy2AhJt zrJJ*S>~s$2TSxweRXZt27l|&j>kjwY3a|I5hUi1wk1*A#>UPIxqf%?Ut@Cv@IMGxO z{H(uA^kjDfZ%Yo7nbrLsDoTJZrT#=!YOmd@!oJrA^e@y#Ff#9s0=FLFqk_g8W%_n} z@p$kgI0qL ze|D`R{(6J#{I3@_r@MS>-h5Te@YCfnjJ&5(xCpc}03EWnn)Gcsa*Yxvj96nG1m0R5 z-U%mA-wBGSs+VBvus0thP^-(42=nbdRg>@mR^C~@ zJ6b)=J)SlTq*KB1RB8>m$4h%HAwv zPu?fi3J}eHv%NTG+);Xvp>DY>O~@0)V99T&xz#_u9celV1Vs)Vb23$%GTGG$Lxfs& zjAmNEIs)XykAHEWqXdeAN=+-u;v{sQmmMoXpHvE;~N9+pT))43oKgKAL;=hqAee2RsEcu6~Jkn`3upcl+N9 ze?D3;9^kS?Qc2922xqArEck**03C>9^P^Pt@)7zWR#goP+}PJ&A>>6W?guJ7D(m1D zsgC68S7|Ld6RAy)TQo0iXXr|D52GZZ6HMlHMsnjvb+GY0AQ6Q05hDA${X6w@r^o~0 z-6MlSburRCb20!vX2L(;*}hf&_?G!I?)?+rlu&Djup7G{r^oGv>`kY%Ll@M#PLktx1YC%nJ zLvOM=br2}`E$pL?5O474bhib~Ei3D~n1$hq>;BS*b{wN`F?3edf5K{NCHQuql~q>b zF_SfZL$8^i@WgKh2j89iU-d=(uQhJpc+`3%d{OQabopm<^AKOFo3EBO$I%Ndm%E8t zwKM{TVi)B$l|b<2$0PO!qO8{iVm#~%MO}$Sva6OzKBYwY+PJn%c=u^k8_v^!VMhLe zqkkjraX0j>ZlqEEZx!wel`U01o^uDeA91VHw0&bdal76ITtHOo$ zB-7{ZuMc~C_Ztc5)+H)RLq^LA>f1$HtiZfD z$tdXaRAu4drBsz0b9U{;VFzQ5ix*fNjdKs5(>?gGS5OKEhqq-HHs1&=Yb{MHA!7M3 zF1hcfI-inZ66K^><<=CTMKjC-6qLy%(vKWpkNU=nEu3VvUL0y&ey^9mJeF<@5je~G zIT>7hK3qLk?{fab<%`NteWd6Btx;Y&ypV0N#iT1K_)X|n;*lLoa%z&sc8qaVA-oF9 zL*2MK>=`!HS6R$H`C9P~q}x6WoJf_;EQ3tbbq=fdq6i4+rc?JN>_#B6*zd1yxQf?l zTyGuFmx{t68(4n9s1RrTt{JzNhZMGa2#8NU7s*v*a^U%33+f=*>m3(Zr4xm?Aj7zB zKp9Dh(*+U{geb!goi~s|cI@#XP!6CzVmel$D$;VGd7FiCgR!x`3b36zBh=)j&wq>< zgnpfDF(9GRk>cfHB~|crI0|Xh$LfCfoAG&6>xM(7!lh!Hfmr)yMd?y|LQzB$CWK0@ zl9P(5(eF$T`kB^B+`3%d1pBuUnMWNioIU$1Xn89q|K+ke_{T|5!_2j^%j0HGrn|Vq zfgV!5c_xfxF*+uV_qxi>6PY$KN1O$mPC6;@JQ1*%W?|mpU=t*p9lB#ZS=HSuSsvi- zXE79=z1zqvzG5N)qUCuI(RTS@2blhvDY+twyy~G@jur?f0UpTBZ~~UfJ89JC`oxe0edpXxR9Y zq-I)VScn(IGHkES8+WksTH!itMl{)f@Qz3A!v0YNHUx&yc1ERLGRE~OPxrk~y2oZ_ z17nV@;vFF`GBI>mYKcKoc#I+0dc*#92cz8g9Z-M&Y&K0gHa8HW>*xypY+M0j&jZ1s zfb$>mx+L%+bn6^xY?*5EGU)k%8Ci9FmSso%&cb5t55eK~$8>~Z+3Qo4U7TG#lqX-R zuTEN4sqi(mG+9LkqvpPHd8)>q#7$486aUDMq_>6%NS8k`R=UwibX!K#QYuPw=A zF=NJRE-MQQu;EF4$;OZ2`nRA~*5qNLgIu3$WSVr1`z;Sqfi2>hh}dwp+E5q|vNjgG zgzDnd;G=P=VR{em`YS92u?RMFLa^%JEkcTTFnHN$tZC2Od4Cxk%%@XHp#mnA`c$x zDCI`z%U%`=CCT;%-CqZKDVt0J&OaywBnyW_FDzoMCZ);nXa_|_diK6c35q`I66!qm z>-W~`-5+9tI4_K(NcL4)Eb}$?2Mg4GPHh)@h~++tDW*QF*TAdg4};1dmY2`{i`01M zz5Q=<(!ME<_CA-Fa=JP-DG3tIEpEfyyr|Fy-pK~Xle0r2c@K)?$5Q8wYBDO2qJ)tz+2tE-xK!y&!%2HR=MD0dFQ_jroo* zf-p+e<3Dr;0QqGW2Y+aN*6n<1ES(uLLHNEP-j;iJrDqOm7uZ`@^2>xrXc}8R|J4Ly zA-u;sH(vb{EP9B8%kr zajlScZVg;1o~Q#@r~a(ufMll%hd4lMZ%Vq2CooNdx9MJYEt7}St=hqD3F2?V3|2`} z-Is#zUwl3)1rq=>t-q|9L-DM$*wQR9t}UOxz~x3hqHG8+$dERNg7G*ET~tJeu#0piJ#CDkAlNLc~fur;TgIkl6dPcwl`DrpU^a?x=Qdqr3F? zI9Vz-P37{iEGC`RqdW1S5tXb>Ko#k0{(Kct?tCI zcwpH^_ z<|&TpS`eMNPNrfMKqmm>l>Ir|X@A%FjQJRmMW#E_wi#Azb^1{zBx?NMO@f!wg3xAG zvVSdX?n3So{cGDFF)n@`)q1$lnu7`Uk*6>@4V~{D+T8Y1>ZPwBXJMgKcU$d0@#uDy zJ+`yhl>+ZZqPEh_!DM{%=9awb)TV<@=Z*CJek4`!_7G#smsV%`z$vjvq_1QyE?7=T z_9$>?h(1fbwQ`-SWrzaE6|@hz#9iyvW@$7+vV*w;;gOS*bDc>**Dw47XI<&6+j=93 zfElcD)|#5n4_F7Z%mGv63=A@fjONS>XWo;!oLNx}gAmsO{cMq*1A)9GGab_Z#v9M> z-!H2`>{@Md_%fp2@RSkUhwcPLG!f<LYxftd9^}cAJu8*e-ga-#4MX)FV zQ#2M@UkWhGW@4NCi&VC^>>4513f0$GM`YibBc>M~}yx~&t_~$}tT;tE! zwOjdQIJaz{yNLFrYrp9-d$ZS$f2#rckezwoMITow$2wV~E;UrAaNlF27nP&Qg?q8> z7u`XI74)l`9~KhkIAsY@xhjkE)?K7#PvOQVVt1ycmf+LYDiu}h3x{o>S8bTmr{53R z*$ac{GAI|5!D%gIz;fECgXs=DCPC>-2b#)wfVkHdeiI-%;PFv-vTvIBk>M~Xl~|X* zcJ+GoBfYG_zJ=A}W1O#SWx;MOjW^ zhFtTY?m~pR+75p(mEuUwM?1W$8GhUG+n5#`(L4tP2LrkakC4K!;QBVVWX6?3LXz8VKk1HNt&R&h*AtwDim{3%+RL|~1BBJTle@41|FU#m$qC+klc z*FfMYB{Pk=hTLXvw?67O?8s6IEhR7Ax>Mu##bjofCK}Jgn|VEX zBlcCF5e0Z@KrEd*{{XcY!1Re$6e?1dEREb$wd%=XVg%qU*~OhgXX8L_Vpk)XY^%O)KN6>$B=O3*Qd=P`Ye2`@_{8c z9cnZL3j5B;g`Xm+k~}8Co{|UeEt8bjC*v+iSESys0B*qeZ=yu4;v8zjNCpMeM#${%{|KgLrEji=k3u6r)oz_(OiZ!g|>?eD>vE8q9C?QNQx2P_V6$lLNg6qgi&sucjOw zV#k2t(o@wp(CfR1hpZDXz5mfY1V}|uXJ(S)AFv_&-dp*AFejGLN;4U@r;ih>Lku_# zf|m6)`Y2y#B1yM| zE@?Ja!ukdI=bo7!8*=(jsMlKX8#P2In3mXCW50KV2o-KgZ(*RvPF2Ua}U9rn$h z)UaVm(xwh+vH99*+3!9*HH68|QTwR{1`viDe!DOK81%Eh6iInK0co*DK2Cps%c4X- z7Bg<-CiM|2q*99;fsQPhAO#7@Pot%ON&8+}^E*nbt$&0aF^RzYWtjbnkKyCFC~xrc zX!btTmsiAFpwPy#r*ln3ugPDl$-}NDhJV8T;}4sjr{VrrlqV|gd0%f=&;2O{*4pJ< zRR_%VGkm!@@q=87G{#%wbF_1x|LBkmvc8*sa!|t3TOi!iiDc7cRD7N=(iA9=e5L~f zakiSFUnQ{VblGXK&V~)l;)Z;ZbLaQ5(_f`hlRs_mRTlPwbmJJj0myG5coxKRSqc(G>{2 zr=91$W4!Qo4(pGsLbmr?02c1lAA`gy6bPbzj6zr-tOk-25y)QAx2RU5Pil#AubdiJ zYo{Wz#gN9;ZB%y}X;TRW17x%977^}TCMVYZ^{>XaP{1D6plEzqTLo7)LVwSriQ~Z! zIS*{!^ybHJ+@@RGr(MXQ%mev7e|f*7-R3a06VeL#^Aj@pnmKQIyIQuC{$!E+VyA{Y z;@L%$F}kl@kZLLFQtrNG1r`hU>S|Zkt+~sbT!E{U4d;|p$#;2$o?w6s# z*U9`HO@Dh2(&GO?TkSv7E>>Hib=lWXv{xDHhZ`tGqU;NC0AEt#E|TkR6O%L?9Ne_- z#pE`vYHFT%U6=aiNgeFmdF9L2l(%ed4&|YlRJl0j;Ky+$hl&FV?A&ZM5>a>9TE;SM zXFx(NFj?5UnhD^u7MPMWT?*iVT@Qe2DsyD*fVXBK*1 z=KPj#-ffC(KB51qt7qUjj!*9Onoi-tJ29)7?(mQDqJ^g+inpkBzes-&`b2l89-~EDgZ{g4mc6XMi);*dHpv5|R z@M-wv-_*_h7x z%ueq_&V`kf+k_|8*K{rSA^Es=xsg=UM|(X^>=`@WCzK(NnPiv=&G^~5)~DZ=yLRKc z&Vh;R#pl+8gsb-5>y@1EZ*i`Q#7)J0k9MW_(yk3jCg*RvKTIC9Ai%3nxLNQr!~?br zm|q%0bK9Y*pTT7nJ@dv=wtLgxo`{Vb^<=Ua-D)uZn5mg>S2Dg>)wuKm@>Os07Sv|u zF&*lA9S9{Xa3i!8Wi9GQrA;nV;eY$#H3a>7GWx^NnB+#Pc==}&-TO4ic{eiBx-Tp^ zWfLBl&@oTJVelStArHH=0eS|VR~$9-*i~iLWcB46gsLcsfLL1aYPU{)(hU8RaKP54 z2+~f;0q?``Y>NrPRfl>?cl?%@L-PQ}PC}bc!;%dc;Mg(s5&vfkhR}WwOcEDT@uh2O zNP|f1*st~8@$x*k$YMpNh3P}lB#zE&o&I%#p!W#($>kvVUx=>kGap~UFP*wX=Gh^lFu}`kG0R|sioL8XC#Js9>#^Gvo9li zeeQ|3SjT9n^U)KU;YnFhGhW<#k|{L|4pXO?b%U#-pp|s&Z|qUF$U^oa|7``KO&4}> zlD;wb8BWe|7DLmUaB6xq*?Gc^>#dgc)6HHw0|qA}>kmgcx9*5%h2qf_Y@(ys%5mXj ztQ>`+BX+Dwa6lTBjiSNAu(Jb3F>o;u+#?XQMR^(8dTDtU&NAm>c-cMd9c);;X5fW& zH)M~S7!!M8;hNgy$&u2o9rd(;+8}P-+2|(MIHOWw<;Ql<86@9QRHfBVnk!4wDhRFL znoyG&VzsHzvbDTsTCm`aeoP_k2cq^;{tFH@WaB8sB@w}DW2AmKUxnRJfb$_p4Egq? zbmVOYGn|$=Fxs6M=Uc88c$T!`A>}l$e{Q`GfN>=g7V*I`H1S3ov9ixnl9PC^F>0^Y zm(L5P*(_?TZZ&V*037L^V{jkFmYhs;8hs?tHc85Ayg6MHQe%*@{sxV z%AAYJWm>}ETD_59fF`SZpH7J6dE1-wv!D_fo?>e6SFChldatVbeSM!F8q0Ln?es%P zDqu6ac#BPFmC`@I_2k)!P)j?oDEfO#r~Lfr#@ir7-ets{5j?wXAT*Dl&>XOsmXHLdQG|GOk`Toi{4G;zFhwt zx&S2?Sp=-n!N07^_U+ZcX76|qyXiUxH|A1!LdMww#%^swhb;1>t>}F1l=7o_9ADgN z7yv;{KahEKcID?e1ya;l>Z!BTjxRXED|Q8MII7?<`j}*!IQMPpXW>P)sa!V#hOE6o z;rSh!{*ht(wkj5;)~|eqt?h;t%b7qeC_a}>iSeDknm*h3B7az$(6S$MZ^4E0vP-~w zp`>mtVH;g7kfyNv@ysP=UkXxSkwW%1OmyV~h}vF7_rIDpR%HHGZ^kBu64^idgnvTK zUWmmzo*$P~`$X|hV;P9V>CuR!vmpi^qo2ZU+@VBLPTcP(%Eb1W=#@TQ&(Pt=Rn=hL zAtVtuV2?!o?evU!Ub?-CE37tqbN&3olR|fikT16m9|$)UzS6jz-?@_udAP3lBuHiR z%gCY1)i1=VrCk9hbuw+(M*iG&;^}WgY3pD2s z=iSn6IibM@B!}|{9@QLr3@775k#(?>+?ucz3#A9^Y?Wx=mwV)J%~>z}lY?-uI)jZu z01X`{dhzX&w577ivXvL@Tf3Y;{(=xd+5DtcPo;Pc_fIta=V#5_#5HR$pDC-q?^)vz zM0r~7u^1m()4o{{8kE_p5Acx!3d^_8Y3ZAet0=m% z!xu+887=e}D-=P4A}^wAaFFQ{tvsSIAlgVKMa0^1Tj;0-blYoez04bdH8vQ-xtRpV zeG>FCl?*qUZR7DMbRj}f_faenBz3%2ZWZ@ixKv=)t78?SMX&NW?U2?c29VWzzluOq z+3V-lFDL8Vxt_IU&%?^bex*gkoeT<%O-?Q|u%S})Lqslyjn+iLZr_$1tEy|Y2OLRn zzT>|0{)pD)Fr#+?#uG9O1&Lm6IYPaPwJ49P?ifA!6vcdU9nZ%y4|!{-;zPztzVO^H zY^P(%iy46p2|Y+n_8s%PhizLV>q$3E{WIBea(PsbZtwZeZ&oL70PkNjM&du~70y@g z%i9%MI_PK)%aPfHJJIUaydX?n1JUv;Q1+JdBv@l)yk0T!))=ri-7Ndwv(EH_)A9z6 zK945rh*aaRqxoucn<`9N`@ru``$YJ~rSOZ-CTzQD(YnGtoMa-WlHP(4a}ZtUjaWR;*}hx6~Pln7?EKR6I4D?aGkjs@wV5o11X5 z>?%8_mz=oH^^Bhu(aT>(q}`q>+z9$nm)q*pt#U9Qh3~i8NpzQANxb5b6T?+iUbP3+;k>6ZDW_%ue zY}~Xf5_WE>V3hC5i8Zl{wTz)RZQ2TCj@^ono9{~r)O?<5OCwiQYqj27iSKTyLv*a& zcTHdQJ1g^aA&MrB%Jw;A!;fZ?MkON@Yoy?yNNVLNUp_`m1%EY{FH(SD>AO;G zdz^Ff*b_i$9dA!NyG0K%zpvc1Tnd(r^8hr#`e=HZ#5MNq%KK7f{mH>HmBi0&-S=j0 zSY0y2>Bq9V&QNkL2Hk7i_9t8rjC#s@yMiySmJIWI=SRzzyPv##ik}43%_UWPAiXhq zB@GRDNYU_Q0G7ocsADqn?CDH8%5UTRh$_N&arBarzqgtFjHYpKdmWt6$;-BZ(@wo{ zk%$zh83-SJj0&g%it9{`Z9^j>sL)F(eD|At${}`fwRVd&{T~VuIZiXK;&HQHvQC$D zsOoizNd3%rR8gMYi~}0-dlxZGAqdN*=Zs+2 zoAFGJHHRm6G0#H%Rt-Ef-OroTYyqDbcT(H0InkAbbD&VZv=2+BOjav)!rM1V!4tZR zOr#4nS@#_JVvD{#7OcX*^+lhlf5H?z1X-spzxw*lm~b+{*oUh|_Zwqr?|m(_7bNNS zL>UAWKbn9ckarn)b{~Z`1`NSfec+hdfMRL}xIHSrHuBupGCn%bjS5$Rjs;7cWjX0X zF%VPkoZV6vuFhq>Xl)xtLlnZR@G(a8;~<^26W<6K#@&QHi$sXJhv*m5nk>58&hkNW zd-njPmh*6ynSZx_S_S-k|JGP#y@ez3pnV>3({p)wCn76=iDJrp|3}n$II`LHZ@-idMHQ{tfoiE$dzVsbQ(LT(8WE#L zYE!jmQM(b;suFwe89PQutWq;2B}VP_UiWi9&+q*gxN?5a^Z0&_;}Cfh)lRH!wVAC3 z{TCnHbU;p7-EVY{I=v`M3-X6|TpwH7p=25Q?-AUvKuYX<^l(RFZO~*AQ zdjHfr#d#-DvM1S<$^-2#r+xPK{MP);&-cbp;DUZ0GKtY<^m_b$&YnHYp`KUDomg7m z`*VD_fHzp$DMUGD+_R%n#PltwqBZtuPu-H_4qfk*n;c0E!tnQFYk`}6)>DL&n9L@S z>^faj*)35TEj6_G`2rdEo5AcNaR2gp%g&y7+n~ombZ>kgc|lq|(g0RLAEQez#H9r8 zL48h)t`N^aF?~P_=98(VkdfCr03h8Q-(b22F-B)Q_KgNNMfyU7yL@h+QkZMSsvZOB zz2eY^Rw7;;gSnhf`up&@NJ7SZG)5Um4zF6`ir^k&TBMb*L!aGpEsJ27H}SuiDumgn z(UOxk{4hnDxWA|pZ+#Fij!Y=71-A`_z^ALJpfTM^Y2c-LUM}R1rkCz=n!KvU-m|4I zxno5uSL`>~SDg`Ff@ph=(P7C~%uw`X2vRVK-Q%Lw)r&_*F6g;hk~~BQw37!7<!~vLsRdm>@#mEmNuw#6bNPN!#`(Wg7lj_bFw)`0tKI z$+B?a0t*dn%?@zyoBpZEF)PJuA%4GNh6qj^Y!XgM#i+1VL8U?X`max|Qyy%l?)5>x z@>LjqYVohf|5)du@Zo9u-R278Ed2yyTOBgM35B(rHbK==qAIXb77Lq7Ir%!H)Yu9Hf*P8S2-3-5;^l8>v zaUiiGKvIo9`|v>_6#$$1o=VR*H|9KI0>={;t;{2o=9u&8b3U_lnKez5Xjvf>uH_T} zmj=erybHepQknLS#G8IJ-E8ogqIn#~OvWsMYo`bKanSEREr{g%d$ zQRJMO2G@mQ*i#Nj0X>rIFIp(9Pjf-H(hnWa4kXdyxkLAaYq}$<5($AZ&431P{B*mm z%>4o2(Dv|e@*#Q6ZSssO7FSSf{kZhxfhk)6R%N(Ae9iGx`yGn290Ia>WFNE&K{EQe zU$aHCS;v&}#_^7Y!v86r54!uTC!G`6PT2svsI*l(+ktrZ1Y8pB2St^o2xIFdtD9cM zP`aG*>)0d{o_FPShEq@8yp48`v6BEBMP7oqQm4~%=)LX^FFpbvG(u?ZSYP407?W~xc zdb3$ECGv2{YL(uw4?tc|ZP;H0;hmc8a5ts1S2|(1z1_zx`{KZ1dn${yFSMy#=$#knQQqLF?D_cbw_32z> z$79M3)Ps;Y3QOx2YJp16PcM8?UtG8Cf{B-R_thqxba0ozxUCzcWAP*d2KKW72^1Yz z?;^^QKf!6jy|C5lA;;O2<>qn-3kpX53t0?RP;>J~3SM`iQm5n<+;yz7iF@fd=(_r2 z{Wmko^VW*#o#!J%f>%yb0if=3_u=_r+X?ziRjx_SX#Z*!^x_?+!E%Z@70H8cT7`lV46?j)Q(r5A6RNZdUKW z3XWeLDsT;XToaI4mB+gir`z$wWQGJ1vq(d#EoI_UV3ww9`~F&C==E}UYGe-g*|6pD z!C!)92l!vDuV~WdKU$yp-^1VGmcGjZ&ECFygoTE-rd{Pn1}*NeBl-y3Xzknx2T*KW z(X`a#VZDt&fvBtW|9FE(@{WLqCw-JTMm5xI7CZ0OTV496s}o`(S$ZpTUqetnXf_8Q zyqsW;ahrrW=J{w{i=qdljy)vaJyru<^(Y5N3x~G6SQHW0v11M z-Pa0;I%-t~?Op7(m$5G8%6on?03lyMVCquSNdK(m8%G$TWv2%sbM+4P`s%MCoW%Vk z9*p%ZW$(px3Eiq{_XQCd{es67PTDrF$5~lX2RZQ!rsP2C*L|^ReUk3tvE4~mo^6*| z&5Crh4_{^DbUr~eF;71S0b-FCcn77My=hV&V_NGU=u)wu!)t+zXj)_!+Fk7Bq7_C}zmQCdTk4@S6fii1F z42|25=u$xQn^EDngvM5dVNFn?6JF8fS4+l3(4@N!SZ13DPH?Pp?zWaO2LuA;XYzW$s9Kp3?&@ZQp4HNJRdViS>WQJ^mBlzo+w;*=N5PEKfHeHy6rkYzZs}gPm5H zCVKB;f*9$Ldv;!y{Gn)x>Q92NN@r3e!zdPb9Q~sWqtH@f^MIc@Xnug)q_LY1=cEp{ zc{C+LwOpAPG`l5O$I_6665e*!ycxRGf(a7;V7ey7I0brhPj{&xS>@%k^nB2}L#tI) zxpK>UCXU^J8(BV6x)GW1OeDU(eQ!sen4xwAL?WkIzl{nKuQQTB9&cAOobrD@HXr=O zf;hN(JKC(zGNv~}lA4~P?!29u4orh@FQ9&@7J2t{y&T-8L3?W((X{?bL#SI6tLwIi zB$y(ysEv`+FyzMpH|4uO_&_`58jPG>I$*v+nB;BZsrL)Tezk-$2<}htD2uKgSARc+UoZk-owRwBNH+f`dgdB)Noj*&*&f+K@ zUcE?4jA<379jV_%7-#a58|&IeMWUIv2+&SQ&Z4hVJ%l9%Ztw8gkF*pt0I*?muFh#jqs6cr;fnJ_edIaX0h`9uoLa{D%2`}iJ!#O#RPxakl)jOk6W-X` zdRAmo6Z}T$j&5R)(Rqe`OJ}eMEB)Jk6ZxWfL4MublWL+QlEf~Lx!iTP)sWEWHwJUf zR;AbdVHQT(giUZ`Tl@5ecD0XO%C4V2j%^$$wid2MNm<{?&h>MmD7>q=qQeo0>RyVUw79e#e!1MmTb^z$Hv%snSb>i;cw=%7`=@cOmH-5G!NpjM^P6D+ zG;gR@KZVnFT`dQ2{OO}GK{DsYEl02m9ITlD%v0`HwVKho;|k?Ypc52^aZs~uu^hPJ zKKoOWy+QtWhrtm^4>pTXE%_@@NT&rU^|2yx%t+x3^O+QZJIuepcPgg!n}qGoS`E^Y zBHbOCM_5UfUhTsf$(NAhryX{4yJ6};k4?+_h-CeK!&@x%4wyy()Xz4U_= zrsCMG%uCL;OS>2KI=z>2zXkkzx69%X4}X`eoCqgU-JX<^ljstBmEtaHVYSQ|nB_)7 zj*Crg)S!SScAV~fx|C>sq5H=(w(RGg&>S?Mi-{KY{I%oh=XWO^zSiWoUsQ@VQWX1^ zl)GaQSyBOa&su_m6N0x^iok^XSUO^<68Y_MBCXz&yW57Ws`Jchu~$Rb z<2#F6fe1XaeRHRdc%rGo+>3^GD~sPO+Qj>Zm?48%tD3d)p%QMrB;F}3{KwaR8iKWR ziQH{$5*Ix@h0SuZ;{_nck$%Ohp9bl&rtJTjxgnY*FnqJqExtI=iZA_BryQ1=y{2g1 z0ZSqL7vxU4W43T~gK~nRv>9l>qQ@xSJkC0)05e-o{x=yG^8e@R81X2j?!r3m>{fWW zal>B3xOVzx^N)##*8h2S-jC_yFX z(;z`Ig!6}HcpOkVS7N1_kPq^aWBuHnHa$WYUcT)y74N=QGn^7n`{?0mkKfX|t$Keb zcd3WrNAm{yTJv7lbZ_G)Tv1<#<(Fk7Z%(Q6^q}hCoocyMhJ)k3tTH5!x6~t>E2dR5 zjc2##T}KQs-za!FU1zB#NgjDGI$=g9C|(5q(TF z$LzSh5R@2UX=0g|e(_x5HvJlxq{AZDV~bfwK~S_b*nC=quc_ANJtEwD<(TW?YZCPs zmkYGZ>C&v+E{1p+YjkuY(01Rb&A%#B>f$S^(ODy*W0DP|PBlkQS0T^aKz%%Z%rBa` z^M^Xk`Z1QIUWadEavDH&HPg)rg!kO3fh3dMVzGrux}4 z=-*JXmYXeDWHda$#E6EunyV@WSeX3d(~IlmWCD#?Sv+gj<3B?GW_Iu2ZPbry2naA| zi&WzV@x1Msmy9u#;jN6=hDl0!^;TS*T?z7?ucit&NV)PE7Qq!d9wlkp{3eNAzL$Wd zbD)&>dKiY%$A2^|$SLRY1GQ;-3-Fi5ZP|Kl6o(ObUQ|V&%-~BcL z?0iJ;!|ji_6kMGto0(n1^HT0?nvKg+C-2MZUasK+C+?5RyRF)FrqqNOe(S|epp^vS z?bNioD&h9?Kt?XJe#;k|1jSm0WuJ@e9)C3z(^5GV(0;1lU(!%+`2<8bem}Q|J&3~I zdity7Ts9VFW>8%I=56(2kJrkc5e}~K=rPkp-F~BUZ2LGf!Pxq79+%7~#)kXXl63vv zPr3ix$KOJ{iTYaF>kw?1(PRT`O?queC>~}~Ti4yT<_jRc|15kBOex+4_^cySF*jX! zfmSTR4A(ujgM4}c6rGwf3p2h?aycgda9H4SbK&^U?TfyJi$^WMK12AsioCjV^*hwLN%|ayMjt;lz8_oB zmFQ6+0Qj}zR}jp2nM9fA4Ci{afRPjZh5|5EH-n=hlU9hm&yL|xvepL4c$>NlmBGNF`Uc&UO4Vq_>1>HXbbu#27Et$!*pfbuELb8sZ6<4 z%FrC&!l6;G141XhtkeX&#CQ*gib~^Heij{qGO>tx4IJ1brbRr@@~>cl2y2iz!99vBlZ8 z6hY!;rR&v;=gUEy*BUbp7es&;rk_M+o`Zm$f@;kZ?J+=X6W?C)=w$BNTkq?Vd3irZ z8qbswCaJvkNWNUZtQb_h*S_l~Gu{L|(RH|tZ5#|9m8ga_zXqKE)0n$NPyuOjI{w9u zi~P{?`kvkm-<%r6>1sC&hkME6Ho4#IfB9*?e|x`NV`_#u-l5cQJI;wmZ1Z;9?YRRM z;7wh8vJlB>$_(^lzemZSc*D9}ThA3OeJ|`RrmvncO&GN{ob%VYUyl9Clq`^U;RSNB z?mogJ*;W*w4+Zv8bSR{WCY6+@e>?3;dM_ zesxRc@_3<5gWrHYdV${hG|)TNw1@ln$4RP-{$j4*0%4Q34e#B6YS3C;DH*p%rem9< z>iqx50<;&vD(9XH-7w{&4%4Mz@@|2lUk2%Zy}Cv-{lN+Oos4TvzW`_@ zJ9FvqatC~}QS1}&?@_p^dr5S0`;QGd7MXSSI^aT1I&H0OPT&@|9h;qzsiRtC+O0ZK zm!^Vlh&j-#2?c2lDkz5?2>g5jgiCJRElFU0S-hJ1m$xlG%i*biZl-)|HG3f-_r3X; z)#%$1%dpf^>OJSwVq8e^cOw%|Y(9&h{UAYiPQ)j~bGSH{g+|EzZI+3Z00U;WU^rnO-B7^@_^9kUzRmHPu#6ONl;R`ut3v6!YSg_uTt;nWts_n5K4nJ)%+XU2x zQIS7|kw8q%ZOkh@_Yoi(5%^ho+g-L}#k-mRNe15xPi<~!u#T+|d)OEM*SIGoiUwCV z4@`UDcL5}j{Zp<6w=c25sWmT^^TU@qv0{!HA9fJLvrfyiuX6hiSxX~v7jvFefs?SU zREPZM6me`hq8>B9YIs9*`yJsxydPOgp@?z^BOQxNe*=xfY8H;!QeeB&nd|OGAp~g? zcSnAM9oz|0#Pb%y?h5&*6a@9w#-ODi6og_KHEOoLs{Od0{XGMhihHFJsfif{z3!{* z&QK^xbyLz4Sk%REQ{D+>FL*UTo=ZPl%2x1cGRl>TD}MvWyOj>7fW~<_rB_u4=jp%8 zrUuH>;+vXZG3bygg>(v%9Y4U@Tn94ynN2&wc?A2R*`9G`LgAvLrdByz`P<4%chzI} zjFA;;J8p1!RgO=3 z+&17=?HRUDf7wI~@I4dFTG{jY&v$3?C1BF>KZ~}pLi2?kT)>ic_P`~xJ1IF5mgF!? zY<`*qINJ-$2LvXuZznlkE^MFJb~(vykL_@wgG##?Lq(&OrS`@=0N{{pqL42b;w45Y z;WXuw3fNq%8wwkdVK6thm3nIK7(7TiyX z$rGV8y|#Pxcm~ z<8emJc90UAwjqFrD75KdDpERqFS9eh(@sxHA;Evk9c01--^s-Q{ok#A=VZuVAEp2f z_w{nZxlmR=UJoQ$3N)tz3P9Zp@5~XGvTnnlo_}WI4ykVg0$kKB%PBK=Y07T+R}AY# zmG-vlxdve*!1wa%SIs^3>rd|=7(!yM1=Fg@2%X)c)Jd^5XA=7T>}^z} z-i>BPyJ}sL&zXnE%eXK@3M5;x0+HOYp`g3g>Ae=6dV!9y^-2IdL@>LXZK?X3;JsoI zNjoXK=?U`tTw<}^#uo5yS@$#ssi$R5#Ug^7Iosh}uq0KlYl_PaOdwtEW(mY^IAzx? zcG-b%kt1)n99@mpy?S7bRLPg}cX-8S>-xtFJXPE3;~r9Lx$*7!L|~ziV$vtU8@FLtL+rvSi}-mU4^lMr-k0v+HZu zoc&d8jgyFj<`qXLD&F|h{Pb$CD3(k4Nw}Yx-pO#*Hpjo3Upj`9^uJNUqix50mPb5a z3(hr_xvN4VknP2qnDUYYuyIa1y;13GtF12iQL-BV&SzUB3x_UORzgjy#qvOW*P{Tu zgZEft@NE}UajUzWR1L>5{IkIIY!-iz z-OkM`f~xH9%b;s208kAOp`J7}YHD1p=GS$-w{nV)U94r1;1kvsXPKX0 zz!)Oh1$kxGeKSCjPl6jIo-|kdF(P4gkWY3i<@^KxlF^aI2nMkCuK|iG38&sG&WJQc zd7d7Y*-VdI9ul^I^^GF^Ct>p6`6Kixhz}3&Dk1-9@#E4Xd+Rs2N}J+bsVq+zD59xRT3*8KMdtU5&xSzVjZB$6wFDb1H5C7k#nNUVtR2A^I6c?cGCZBO)V za>2gtCO+0LwgsSA1Vs=!=47n_Ey12KY-;mzN;*+!Q|o6ypk2a{fZ`GBw~w@6fxd5d z`hZSQ*%@@evH9t}NIowlQ}FY<57x+$oB_+R1!F+|s~*rXY2scaNFJ%z47V$t4MT%; z`xAG>3*6`*y@^oeKNnzdC9A>yO0vcC3EbWzUKB2vU!H28b^J^A$NZnL-n}aY0K2?i z|5f|1;kUaur$e5A-_xdn+pp*S%l<*F^hyG^OinqVxdHpY>|`8fb#Hmcpu1U=rT4Ao zTCu~21;(OefZW@1paa67u#t3lR}&idm+L34*4B|Q`6Pr^hC_P%wn z#Ey?U=fpo7?OlB}kY2j;r;t4NG3ncl?TqND$=G|$m&|m4*yFR^s&LH!Ic1u!TM8q++D@r0*@3X=KY-zy8_QW7MUU0*S3hE>(HRy=eEnB3$e7e)f@n7!*W8B&TC zY{&-dMoI3Y^vkYBA+JG34isYwW5cG8$PKxdEG8j8KEM19bd(QN*XV8oLJypCrSn$_ z??bhh3?b^-^huqJUtW1~X>gC3M6y>K3Wq)BBg0nOMv}o(wkL__>8qy}n{@w%(oB#3 zZv#>_(RSeEa$%A=;W9JgE_W1mYSZn|WiWfkJ&gV54(F{7sl?TB1iN0M6u^#IYwbU? z1vJ7KU7`B2CH8s;(-*9t9pQ3{7Y_=UOt0UdF?Ig_sLTli1Nlz_||S;l;^ubC4s!zP#)G@OQ(hE68aLYzpME& zJ8`y_Etw41p?ze3cb>Du$bs9?4>XjKvRENpafoZB&$%jstweD>A4OF?h7{b8bjJ*i zqnW!PCfisnPun*6113(aJ#d?-h5X>?Ebo5Beb?c;sALzbBJxNW!2ICdLDdkMdB&1t zD;eCgcBkc@P#Un4pbBNpy>h`6eF_Wps!m;#8(b#k|A@3uUxk4`W?dQNAGr)}j!LV4 zdma{n5&CP&CrCpt<7BGErq*Mvr8KgRJD*}RXBGR8{w#OPobg}cMNVDz)birwJJn^F zf=}VC_ZZJ+cdxfd#dX+ICV%8z7UF8E32~}E%xQT?`TH^0*qgX5@3Q)-8Aj#eC}zw@ zgZx@cjco6diCi`AcwNXLP`Kl_3hz=|@X~tYco6lKT)tbP=TFOvZ>>SzN19?o@cFz`V@j^oOo50ojKZ zVHu1o?x;S4r;lEd9QVV@MeoJibbN7E1h2|jiu)rUD3<)y?1C+fdA}oHCq=VzP{yz3!Z>kN}l^7DPz={R{l20CsmwO$UB4}P5Pu_XG@cI>tg{_rJ zW>TDHZ-9jt)~Vi9xpX$D$@|+OwZS8f(zsUFW{C$Qj$z^6OEm#k=$i_1gj43lvJ;!Z4Z$;}P47 zr)7JJ_$vz?u=|x+h7&|i9Q(WT?~Y7Du5><%;C99Av!+CXIa8tef@)(@0u@1U$^2wa zXqtqlWxqKs&e3nMOXiN-z24XS9_Z|7>)`}InV(;fdmi4t`Fz`2##`+e zE4Pb|zTB9UY01($Kk}qr*jwq}me%IxF1|}|kn>(h9#5)1AcB1!2D0qsNYhQD`uwOd zpL83W#PeO-m}Z^{#Pj%=VOax81-v&WWqe<%9Dw>qiN&kU#a)-Q?#SGv97CSr%4?;k zTaTc_e3pK ztf+3C!^Yt&xZb$MwlVn>Xt)}sT6XLk@F(p7AX5D|^$qjC0eky13*|n|YJ76g5FZEuE7rtiXzbl<92yefA;ZX;(YM zWK69J(rm^RySgf}`;O5|%u#~(lEK&I9`rZ^v47sWBUb{sZ2&89Zu`kNt?B$O{7UE^VAr+wQL zj=}3^{*WzMl=Ii)pLZ!kD>>UYYEN(O{~eYzfPFwNnl>Gt28`_ahRnCPJ&0WjU~;gv z_Xu{Vi*4z74G`B-#YjOA8?o{K{bi8W#PHq{6Bh5ToKuQ)_}u$ z(}xco#rPieUYyc#Hm(;!4-NGHS=NNWg8Id9KtJVOo+@28;ig@L(ej(h8PFfB_~ypx zMdpa?>5kmdYO1CGVDFJ#Y``AgFz|!UaqYy_ugR3Dv4S$!io9k;5Rx~I1q_Ynbs65F zf2*B-CY{#X_y497rGF&5KlIfnTBZ z3hN8E=>X>s{Z~UXF70=|NKyY~GPDU>7*M;5Nck=;X%|Mr#fh0zCGOudbwt&i{okQ}8PRw?7)W?}_v}y^ z!o*ljc{}T;`5xM*myuME6SN$m6lRbs`We&rwCj$Y#^x+KhI@P7Ct6{re_e&!`na&2 zX00m!Cu2jT*nX}14Y%34ygn<$v;bX0-{!YBYEpkN3Se@q(y$Oqe!V$zx|VNV;=x)% z8Sx=u_}kK!2#xwu%g>Z>t2|GtGOg=~GHf^8i}AJApvFKlXscu;eP3^LJ^_EOT+)W! zE&0N7YSb%Rru}*zPA!6U#gqXQ_m;yj?!iM9&cRJz&QxL}HBEN z;14?@bv{X4I78HWS`LvL8eZ#3uF;T82%2>e)Rz1bq^DAX$CwyEmQsJfk?U`v@GsTL=DwT^IiwzPw5k1G6=t_a#4bgvX-V#ve?3+3+Li{TdJ zC#9CrZwx4fMF`qx>HTue03PS@ZlT){A3I1h@9kQr)1(`n6PyNw?$y2OSN-XYdGc>_ zlpYlny$fUfooR;?xdKR{Cb%(4S6NCJr1F(j9IN76^zM-!ck^#)g~Y>2)8kX0<$9K< zC`Oy~$o8wI9MlD|sZsLg%Zx^7;n!9|ylepm{;YwAb;l9^sfP0JU0?c-HIDz}$=@vh zOMKv2;X?B<9}mvs)O)GG(p8RfNO0RhVkgv2z}ULmzGBxD$)xx$A_$ zC>jH_a<9!&Eg>Hii*a#*|evssf#mq#cyndHP|DpbENyn!NDQ-q^SeNGrx&^=~CUTzD$n5HT8 z@Au&wQ2Fe)1>@6rH8CR<^elwr!bvLV;{ICtr{}lkryc8^jfS{^UJerR^NeuFuO1&o z0&5mLZmtAN`nKeGj}EQ7R&&#|5MRzGS{COa{<6yRMED*5ll zSs}<}eeg{XcfiI)&FCtDlmq`sSm8C0GCwPvbSE3OgeV7X%rS%0t&>=^2^ME-w@c^G zj4XZ6#b2C%IjwpfxT;>v(zJ8g#nvOH&tc-=cVP4IoB zs6D6HnV@oYA|q+CcLF!laCl)#oYpemrK)f;e0c&z-y=Xz|*rg9ELUq33y!E5t6TLxGD2A z)bNuRFplkq0Rf9DwdgVCUT$E|SlU_1-c+iXvyI)|@Mwp0b;<0e{}ctxmblt~Y`~JJ z`uCF`fX|>Z>!}aX+goN7^jnR4gdA|*sJ^w+OYHSE zwq0cM;6ZVT1VBAP8E5u%Qr+&UyWJPkDbIRRbYmzo_HcLK*4qh0_+64)laksiD{vxO z)c&+k7TBy%2C?z-N>WVz^l@&wi#AqJoBMoKIg-zWJ9aYT^XJc8A_WQzg^Vl820R~} z!pDF#;m?@IAKrtRIzJh{pFHsM^SctF*7||;vos&z5M^&bAjW-w!@aoliO#1dl&g0uGSS;UcNj9=fAo9n|KSEz=rt>dYO6)3XN=dzWVA7 zjXT?8rVr`~EQj-wVT?9+3iA9rAj52Ai2cmDw@;Y{GR5$vr}MylNkKztid68^wP*<+ z6eo?|JuT%otPHL`USLKb`WUA+_k`2X;u&YME+fZiJBk_d^gZn{xBPePLA+2)odg)t z!hw?-Oe3?luPNZ_(M`2chl%GUrhk(ueTwe=;TkgV@Xq{4k@!4avc3it^>7TWKt}LH z5Qsx7B273G7Q#F2U!um z;XV>+g6B0^GoM{5v60g($x!CSnh!#5vIg@$4sp_P@OWu~@f0(S+k3?em;(B>N6Xn2 z$2HrQ2;r}N@FW%Fa=aWCrV3Ghhx>eh%e^27zADym$k*cOR~@)gK;+H`RLU1j)ZOqa z-Aiqtoiz`7AZMf_@L)rEzRmU5tr3goq3r<)8=VXUs#0ok;4qajMbA`l98DQ$cn#OLI>KsHzS=?R^JkM^&(1` zJuf`hZ|YQ|%U53U$0wFcxN%dmWhXy*)!(8n9r0XzjcYEBh7QEm0JB?XekzEdc?0@$ zfd1{iYSB+yJSJLKR`Z0D>8z!qx@PR{Y$VmCgaEKsfZKrt#1AW!{SiN&C*LbofKn+# zV#Z(%Y#JYROmAB!*t93IM}B%)MPIb->+m}M$ww|TI=r{A=F-3o+WzSi<#`r*DACyM zB?)qjHAS9xDPz6FYW3aDb6i^RSQI#3jt&}43R(o3PAhpM@<*9Pi;K<%9?teQ)w%tz zXot}Gzv&Cx!(I3sXk8XCeAUcI3AF;8^o8yzBNpa#P^yJthaLv!EZ28QgT7dE_PvV_;bR%8r?OWldhjj?tE3L*LHJ{^3||mhYMxbFhLYoO zuU)#BN-v37;5cU%xM+#0Yt3n21@?2b(1uMTvznH_Gi6dQ=7|l`_a%9S-6=d!K=_Jp zsS6WB5eoAI^j&Y|xi-9Wi2gB$pB!~F(=`+gFe>4|?Hb*H#}14?T6<-gNj8Fo0;>(w zR7OuKd6yLe$WbdF@`3_C8x=cQ_02nh%=!qVkr%juMa%B3G43S*5`UG%HTdfJu;CFoH!vBEzIs2xcSF+On4CA3o$o$88lRQva#1HBFPC{- zHWiJHCxb>={I6|!BUKyTtGSu_m+VA!(N2jXlB*n>yPowQ_xeOXaPWWICzKt2vR}Y8 zC}3rscfv)FtfTLSaqPo8Q`0`p!J|dZ9oMn;GpI#};X>W0R#$tD=m?#~q^@Q_3}>kz z^y-(Z(djM?yUIf%$2`zhvAo1JMoILz25`H9i_Vq$#$b($A!g^k$#ZkW*O3E`Ghv*{ zVn{oRNNzH>l&?qeLgx z5AczBBPi+qRfeKj4Nmu%ak289l=EgF-@}foPO}Rxy1Zm~}lY7Dfj*11^4hUJuz;>3?TbY8a%e*6b~72Z6)Iat?Z%q+zAf+?h3_?N5hOEKqeZKz1GGC3N!h)ae7^@1tgjR_=n=6q=FTc46$(iHLzx6H0{8QK&{{AnqK#g)mu zm|(H)4PYL*s?i5Xo1mv{0YA&UU~_w0A6}~@z!$$Pwhwq0S$ZJQjqa@{1(rJ!pJlW- z=2b?HX@H9^b?)6l?+*K6R`$<(eXJZUdycb)fJoW*R?6iP=m6gB4P0`{TGrMJJijj1 zmOp#4;Kk70BeeQ>(%)q6fuJ25A@{x7a;!&tZXN}l0U4#S%Y`(UQ6aBND5QWrIl7g6P|qAk`VV7+3eQ6G9{2z}Rv*52~D46}_xU z`w{^wWHlQ5ted^=B7fBM>3%pDig8-(f;BXL;ML+C^xd^1k!^LVT=u)h(i|(7CoO}J zVF-ml-OSk!Iaf<@k^czX4Cfq#|H1sd6M?%e3;WA@Ds44M<1v%>{L-ER*C(Q5ZL`c~ z!+M^rb3~BFTFYjf8HJ^sOuU}eLt8OTj&w8pRPBAshPLHe%L-@H2Vo@0eaUEn%!S(d zBbyY)ikWS+ZP(A8?y7x?F1;|Z-RYlH>RNt&b2ygM_EIOnQAf|vum7}x>HIg>^ogb^ zWIxwvM;H4(KR zQ*gOpw0u~sPSr>v?w;xXjQDtu#ic*4Iyv%@fm2=a-7Gk@S1$&{7YXjRgCMdkL%BSZCX>x4TL!#0aYCN(|T2RW}lMGOBedW@7A<#X3ca4$~8@x=y5SubR>4Sac<>aOP>iB3s+f?`)_miB! zMLSg6e$j(}`?!hE|4)BO^VvzgL}!Yh93oOe(NaZh#UCzkg@AqSGbYS@cDH91Ut->6 zmGbgJn7|Vw)?77HzN;H_xZ^sKNZM5UWfsNQV6q=VzYdgcKY*0PqxH~^O?FQuo)?PqjLsyU{<`;6c(G*BcD4l%(9gGl)34eJM7&%b?y3#ntL7~^+LMi!9@mmSA>Bz!6?cDE&|CM*Xb3BF`{21KDjITsBdsV&Op8vD zHQi}bCtidSeV!tjxbGykb0}(wt}UT8ueKa^vu8^ub+Qby=HJY9 zktyVi=b0)yjDwf}hKG}0$l4ER`X^pYQsT^@!SZxNC6oyj+zcx4S+f}&7iSzF* zlJY`WQ@F=O9KJ-Y6D%AAY7ANNCdOB|ZJC{Zm)N4yIc>C5H!fD% z3|4e5PTCHVk9%LVVE(1MGK>C=ux|WqnjKhddY(lzD+}DDUvkPRKcKq3k2-m-?GcLy zuJmEv9i~Bkv25>+cs37~U*%-imIcS^Mw#QjxB*-%!&km6 z*4seMnrqrqL&FVHMJ>DXsYZxBe7SlkWimweafvjqN{H%r;hIu#m?MzPT`S#H%FTl8x9+`m4RncT_|V zo1H-lTD)&IW@jkEr1@?E*HK!IQ*ZW_?xwFi7Pnu!@#8HC@La*Ld2wDF#pM8YsPwYpp(6Y8I5+ zD5+KF=WWn``;e>~ORPR?(pgzesh{XDB2ll2EQ9N;bBaCc%GXsO92!jg^LV+R)r zew+7BqJ5N8b#iw6JCoDKSQNqe*f5sE@TAR;cqjh*cof_!veMC1viDr%YWYE&V7~C% zF=(uC28u#YK7;%n)APK(C~^Xx|?%)+ToUEB@L2Hy4Zl%U?YU2HyjvlS_bbcY};&Az@VevdFmU zNLuh{hu%_^5$@v;nzAtcsJZ7pp0(I;xUuN?lkNWZ-S)#(0YJ~hDP)Pp)&db%(%fa= z;x6X3%*oL&6|9diGCMIMX8=$JQnO( zC(V}3qonfYinZzn7O}qt16a;cz&|RO!Q_h8UWQ|7pe!xwy ztx3f!cvSucXqX@EjgTpAI-*nAHWp5pz_u=)Z{EF)i68}(Zh`sME58Z|-f7pidV|o$ zsK;+2N~txqpe1)cwPAKo#0}jRi7sV;sccoYQ0fm6f0LXG2+jj^&2KfGDgaSa^tM{- z9yK@cy7#+Sll;5~Z0jlg{pPBi8Ur`#0uQFM&J-=Zy}f)3k7kP({{H-VIkUgD$E@VS zi)yFo;irzOsTks1OahFx%1|F@+Lw2~!Q4aKnc=gXc=s*YcTBY|vnhf-K6L!;(#TNx z1=IDy)J0Res(R`Y!!#)}7ALaybYZoxYPmRLCwn+&0br_Rv~FjLypyM!_ortk3w07?b+y;THLCt?oXY1S@%5T#pB*N(kNXqip*$bIB@LCj?94nb zG#3wUEt1u_=2Z6C7!W;M!XvJM$LAWnHj7q zxd&`GprPe~;+bB7?mYp_5V!}LfsEBK-pnUrSJA7)NyCgoV&F6aVe6R+OjlJzywfMn z0>_6R=`lW!q{b(W>MwpF!fmYz>&G)ITbr$W4~Cm zRs=R=y~Lx}q=t*j8K<`d>++T_aCTDHV>yuljH@p-MC9`&70010gI|cheN) zI^QQPOE7;1@hA5ZGTV3g9ZPsJ53ktO7;%u_1sAN8?S#d`^tOjFEVv~%44lz*Sa{@Wi8zU{G>{&Vxuof zJ+c1KOH@b%CW~k{PA`|OwAI5Wy+7w&sqR@T&ixyH=rY*HtBjS-GubAxC;k`P7Fq{;NnxbOg<^ zOo-&u_*l6@Y}>$FV1Kr^ro1DGmEU9@HE3Z9nQ|er!%T3>CE0k2iBvC&kXhm zT^?BiEV*dDFL1v^HAEEC5bd~7FHy{=FbeCpu2JKjqXpdT@f5~T$M72lh{io6uVKGW z30iRy-ZP71K!Pv9ngo4Hkm>TgD93_}RQGWn9LzEy&f>3C0T3HZ&{S+CpUp(PMJK0% zci4uF!RW}b&yIS$0!%M2?l%eYTd&WsW`AgZaRVM{s>d|29NN=pnny8cK*!L+|hH0K_7g! z_qMa{2MFHZpMKtc`0I@&u!UaCetxps-SJXy!%}fYViPY?$`z^ReYa92aXqifCB1Md3FJ zqQY=`s2~75nY!j)1^Q2Wsu=&M4PWPd2G4Re@%E{>6q7gy_(U4*DfNg|b@qy*pH7&T zu6Iq^mdZ+JVO2^wthmn#@c!x)CB=x)pjZ{zcW@|qWh`jj$^>FGH?Zx4}VU_ zy40qBRw#sTGD4J)$>|rwyu8A-fpb5B7@LxjVD%5H9TId|heSu=`srJRS?r1A^$4I7 zN!Zw#V`}iX<1U-Uk!f$N^wzaxl?E1RfQ`1aTZ7bhx?{&vzUVlN=IsPclR5lg8m z?~?;u=il}LzoIwkIFM&+Y*^?1R*ezwflHFgo4r;ZTom}Fk(l&01dcP`VmiV5#UvEB zF>k%G!~v+>#xx$Il~eKBfplDCp5ek@p`R0ga;XW>1D0)2mPk5w`^wcTOabpuEXn&Z%>!!TZ&2- zeaM(v#sX9*)zA$-4UBR?v^$zRGz**flBH=UDX*b&CElG-2)wyEJ(?9Hd5|#LM|cqW zs!K`o@nT%d0HPAI%A}{t!qgT#0XeS*X+0ATyS=G-C?3fTp6) z0jNDCU9pt~E6>Ak-af}x9H=gkqAryv-{_c2x^98=qyknD14%tLA5C@Pvev)BgQAZE z2-V4^T920(0tuE!cK?T}KQfv7_~<`0Sr2*faQ+adm3Ct{z=RwJRtb3pwa36G{t=6$ zBpE-lp{&SNb?*fYvUDVA7K+ux7b^J6gF~vh7MUEtA4rU{ccjrvlP0K5edsmx?Z!hMk3va-FPpzG-$x zrfmaQ@1IL~)})?>)8X5q_4H2Gw_i2aJzW&m6yM;FnmVOA^*Wx@|!;?@M=@HF6KOgAL}L0K3Dh=aN^aQw_3@aX4PjpWe7( z^OWLy0cjBRte72NE938Yo#snD+?%74rEJ05{(yIM1Ip8(5&&?Hk3T=q+VC4sR%S%X}K*#5a;e*ZLg zWU~l+#4AVv{SekU*IISn(3Q`v`NPo}zv$-ws+L}q{vW=nD0A31>5hR5I226%V>YiMX|%UkbTv<_mzz z;J}5gG79-^@eQDlYV4z;8(6=d_xo!(R6@ogi@ zF#6&Rz=LdxN~t!JGSD*F39rG=Nl^5gd?p!NJQj}cansu1pa^{zyD{Qvp) z)!i@4^tmrE$byG?7COeXg7T^11C{{-bQZvQn?3eyQ1amj}FfPteAjzq}&<-P@$_>m1xWC^_D=4 zOS*R8c$s(WsYkLWGlAx7bwlsv7cO?dbzxVze0(u{5xH_k5@J(~k$9p5*PYX%Z}#esDVTMdsKEeUKJT zes{&Ybo)w|yhMq`Ufb1kTbia%h?p2Z}t7wE0mxEZE3 zBv_H7gjz^~yd?EH(E;`?2*Dy*1qN~3;T|vN&Ewi>U!tRxXdY7?z}cO1^o!1eR7hsq z^tM;>HjgRY3=ruhXpDcPfyeYISR-&x-!HJ7hVi;b-G=@vyrD;pZknR0lD8*SA`VI> zX~*XgqSD1+IAIkwJzn@r*wBD){GH(n7Vu*i=GbN?;@N7skllyBt7zh`jE0L6>S(15q4#Q$#OZw!jwl4e2e`fV~VK z{9wwnzTT^_$g5=uaI0JwIKDpwh@MU5`WGx!d@43ifuTXb6906F zu`^1;`9;NtY8~OFPz;hf$|ut;n>rF-qzG;97X`Aa*NiD_vvGL;j|<>YQz&&TlHtIU zU&fh(^?j?x(4`U)yeK9Upj-GCmYYmKdxSJB&*>80V}n`>?bZJgIt(PXU5-{IBidpSz+{0UfDQAP?LgE~BJR{VT0U-OvI_newa-=oqJ-4X}%tF5McSac) zAO%d1dVbaCMbE1bf7NSt9_F~e6FVJ?XxTKqRo>-zXuDn>X1TG|GyR?KSn_LLP?hOv ze$~Qa5xki$dN+dw(o(7QB{=SyhK18{Tg2fxc?oG>WU#cBkSQ>ZI^v8=wf?=|X9{vn zRLLb|7Dqv!WZ-NMiNrySSwdAcC0nMsC)2mEVoeG#N;S6o4?YbL#aV|H6RK)2qTysu zCLKjYBNUmLD*me6hfWI=S=+Em+PVfPz(@D0`YiKGEhm_|YwQ$sS+7;8!l$uWGK;6a zXxVPP{2mi`F)pq)i+fU!^IlI?z5-LKAH9~*+&Vjo!-Owh&I}|@eIyXIb}c=X)02z)I3Ld22G!%b|1g)krt$xy^V>~2 z+>Re|1W4WEA$EuJqU*nx&OL z!D;7w1E9R$_~&KCIK)HEu)*A+>n1tOM*~MDt0|K zLFNJAD<~EQ=s5bi4d;#W8jElttugEm4YRQX9(h|_M4|}W?+L32}0n*-$Lwo^}AW+6XeORb; z4!^O^aS1opb?lK>Y!^`|Hl9B@@mRU~7*;xY)#%iv2j~coIq~4+Gj4#w0a5NHHbTFo z1CKtuoatTndT77!K8h1N+te1ja#KAPOTItnSg>E4zU8nIvgCb7SwA)t;0^i$+I1P> ztzH|ord^_B^S+RRbY#G@_&N$5VZ<=Ond|Oo2unC9Ycf)zFBtj?VLJM)xg_m$DRCf{ z6PVZ5n<8=FQxvQaiw2loxP+l)lDJ?`NxU>_RG!C9b$kkDQA{<}DU;18Vn|>p|E5H1 zQ4k;vd?xo|es^6YbZAT9(D=t;>lK;LU_K*6u16zu{pd2bxiHSIA*xN6pHaE(jS!X7h|fe%aEfq+x*@fm`;lMaLnS&m4j_Cl5h^CcTc^=1^ttb@PR;YBjbIe=US zB+nwA7W*GM#09_;UA9Q*X2MK=9<}Q&JBB_hB?aOx;hv(ohNsJ-J*7=?o&O_YKNTm# zUilA+seNmB%j4rPdv@=xwqhUIUdk0Px-0@TdqrJ^KuTsFMLA2vU`Jv+4t`9@@xQ&- z1fk91Vgk+Kuf8vP)vvnbWto<_EJa24uZDo=IFSi3%i&i~AU*x`vy6Q3{NLmYcWvG| zbbtOp`iu3x!SzSEB(}>(A4N|3SKIbhnZCtFQ+_JK)50aa-P~8mTJ>p#&KGilBv!>64JgL#oS|rNQ;COW%18TVxnlID` zF^Laq@9{d^epcka|8Q?JD7=S<2=0EstsT9(zF*Z3!#+|=c}lStTz!HfdG59tVmC2P zF?c8NOL+7qg$+tpqdb^EgW`f6pLk@thA#<+e(j+spppgz2%YN!C&$Naz+7yai+}w0 zxQ{HOS<04*V7+ghlQ0H^9BtqWa~%>is{U~i?u6fp8Wg*{8~)b|BIDoPjg?f-yu)1> zvmWjd+DOwFx1xQJ*N7B zVCJ^+DgIi?Sq$-x?$M(X+iARHU42xTm-I1t3?|$n?~g@KrEut%e=5X`R7BL(}tr08Op3&vwS`n(F(s2lImaFKNy|^1&xppHb!c z>{y+CngHS@L7)r=I+^uzzwS;2RKt0RBYYnn@_f@0d}}VkRaF>;_B?9d@dUQ@_F@=7 zzStIzysYTKF>Z2M;|&s=&?$fcE7oFnKuLVi73YY9yX@^cyeM!Y5CL?KL@|?V$`VF? z^9X{SuuV0WTP?lA9{R?b?ZSgg#|Lqgh#*;s_8W?BCtbiHVP1>*#Ke`}FBJ-8uOaYH zl?txRVXIE*zhASJ2G9eR3Kseds&>^qdYuEc3j~bs@E8Fi0a!AY>UFB&==L3qdwviw z8b0O>9}*lR9~|Hh<;o)MiXHjVmN;=0Kf^Q(&|h-xyK_1HfQ5s&>(u%Fw!$AgZ^dN~ zAh{WUD-qTJaoa`c_!F=R>BIou1%QRVnK9@EW6gPpg?*T=I7}8hKYTcj5j!iOnRAd7 zyU#r&bLB{G-3@i}mvms2GFbcayNyrv^_{$Fdc_7-QI`d^i$&1MGB}rWDvO<`%w+Ec z!6}{MS8(?;cs+Vrwa5r@vuh||QjUQ|vHk1iuSN>2$aVw;CY&8m>B*4?s49xn+9&i3 zoD5iZI>lEC^)f|$i5>mYwpX(WMz1&fxHnR}ALbgH8Vn%@5F3NyJUG_^%J;@fKQ_l)=Rbb>pm?i!rOOO_Hi;4d>-`se<3N+~ac~RYNR!5NIFpaRN zQ#R{)15*Il`hforIWCq#@dFoW_@I*I3n|bBYewxfur>pm+ncp$QKak)5vp#Yt zy4Jc2=&A3v=}e8@=u1e7`@2t85SU{6lH^f%vyj3op9Ax{P=!DJKhLf-=TE#xmyEG- z^Isxt^&OYTN=WSfv3N#ebW~!@-bpj3VAr)hDm*I-|J(wnFn?HNTP!*z-Jb10ys) zlF={KaSk5tji#HFLbml8k3~Oz%Gm(rIu>FKyH<};<|dgE*)#xrnlZ$v+|3hUp=?>B z2lGuO9-X078ixCnEdr-%>h!))`AuLn;Ge)>#0Q93UgH6mQiHX;;HgO^ZCnR%IHDZf zk={~D!T(Uw%j^4NP)v$CLyq`12v`EilB*}nI1VPv9>U-)&rB~C~*idM{xSCqo`%v{t6zQs3J8xic`fKVYuIA5@ z%lU`%cln2(moGd&M{tV>QU8LU)QLsHO%|(o5tW4$Zu!R-t@pwY?W3Rj2Rrj}M0Q9+<~R-%tI?P)R!5vxriCCa4y>rR zYPxZsh2bpqLNbe6$YGmUL?jg(3%%^1w-GFsK_|iB7l|wk(GmYPB4-jcB1}^dHxhk= zej5`s)Svi5D)@fz{E=0t?9)iI&+?&al>VoJ&wssv9QTIqk%>e2rZ+rB=d7gZCT`c& zbuwgMNrXnNU_f((9gRHQgQ0I(h6}sE0}MNss-UNIzfXh(1KL(FiU)Qv>7Jh3TY#=a zauNv@e%%B~WIfwIU(XQYf7lk9sTW-ly7=^|f*ft_h5m?ZiA&lZMlp@g!Pd3d?JX`x zPHao-c=Ec4y7lk@@2_1}p3mH=9@Z}X>+7@*w<)hI^CB>wB{pp*ZHDxFU3+d#b;cto z*#QJo<;iQ;L2a?ZPGbsB4-v=GgSr_p4yrK%Jx_?W^Da9qe}b#y@vWuJ+IM@wT`L%I(V_RYeMge9${)`UW`tT_d-9-k zbqCu8r>D7l#Ebb%v6X1YlRjxtWFPkZ%^s!P7c@)E`p%XAbSHJvC}+b0Y+c-L)9se} z6SiTPxB3KOTleC`vFLRGjHO2~V->u%_{vL6rAme?Mjq==VxSYOFAL|{B-Z(Ft$1wR z5|nyN@#r}QJ*lLbYkQs@dGlygqHN{fDN4o`q#zj-j9_`LDO1dcmVIho7FT;MYq?@h zSM)J9R!LOqa&FG7Uc+Fbc)tNG*PuSC)J|~~7td}+)3HSJ%Ft+Aek^Q?K zTV(d;TjXwY?u}5_+pvcl#&bTwnfj5x&t`@VFD2<)Dz5*|t3N2AsEWA+4@-Fcw4ltU zzwy0;7r%KgFWU_g*l%4;g^USS#V+92x3}1kT@wBbGv*IpFl0?k-R1>?V4y1}XbGT#)O&`5F3;T7~ z?nEjJZ4suPABHZL$@M4>earj8v|RyRQ-6Kx9rbBVEpyk!tYEnPmP>-Rh$!_h*qYR< zt8J36IBez$Sbg_*dnc|mZzRpGZ(P1ZZk)swWU$NMJD;vgO0vZHT6$UsW4f)kZI`C! zX2{vA0BWrr*oPmn(XN_2LZjbPaH;oe$q&isn_YAY>T3Ix`~^Ku@_3gNhP{>kX**eJ9zcML2@ zMJYAyVGFg1k*4NCC_TJwV0u3Sx6e)t zh=%gRoHx$orl2RSbgzrtDNF@9f@Oh$b5H1@nZYr1MO+GX@0n+n`O5jQKGaAw8&av;a$%tFf)2=BzT`{c5jRV?Ql>oLi6GucNz-5FOXf zSt^4_Rz45icoJSwz?MHdMn3Fx@=(2VqQ7 zKrk7%JwAvy&Q_&vB@vp2|KbVI)U3>q-6Fi4bXFD{wJHwcGO3ac>Z&Mw23MOLy#xyg z_PS9Vnv7JD7>1Ex_(h5^^%Tek$sckh^XxZU2o1Oqb`G_Gk3w;&spe# z+lBun>9F^9L%ZFme)ID6dIIc7EpkG~DGQTUqOT=|YVb1lNpCjHeCxc|kLe-m*mZ*u9Jx}jvs|qJo2Si#(8UTgqD|1{EHi$V zLVYh4#1-(KY<3c_KnL4T-rccXi*k!3@lKt|B%DiW!)mPYhxFHxxzBC3aPxnKeD|;116CK!%^>P}D?`A%t@fJWs3zk< z`2At)lwrbu7w7uN%k1SFo(TM2RB4rkaG1ypy9amrPF3!7!2FtnkCRJ#(Y#*CTzp_Y zaa#YlD?L_SdUUF(Z2!di9pfSasAt=u$`eYhcsu%HOV#SrBR7*J=xtu35epbEeqAJq1)my_S z)&5QzSl)_}owgNxv-p)UpVgJDt)j$b;m0XZ=Yd^xZ(Bv-FWAH=PmDWgX;pH=-<4XW z?no2T-t5*+^=975NEn2C@6XodLVe;N;*W7ehtQ~fOGIh^>p0DNHdn`v`;zKnmAUX}ZT<)(A?ho2VIvXV-U;mpls0(j3&){IdeA3T zY}?S!IC^L!xZgcyp*gRA^RIy0siQos?q8E)&@>v~&KEmCuvZ$>TmJ9Z%r8v1^(C98 zY^@?KeqE*6JvWKm3)m|MQlrXXW2)-+r7l3<46{9-5Jyo(gbRx{w@9OvzIWJJ9LnN- zP(K=ti}x$kpIcN8rvsMr+8 zY5%+1Co_M1R3@d^J?yyFZcM-}b))4voK;v*m0L-Qt7jZ18#YIIV zv1iCa2gwP0+n+jLBBS+OJ}IT|N$;CD?oX?K1kr)cVek4gIW@prRA7nxtV$DJykyVD?Z@kgk{FzMHLW+_Gkte4V@RnG~# zIEHxRP8szHa=meWs%DqeEkUni(;Oc1BL!b|c!0)W;TrEq{p>UjZDvssRgyJp_Mp~d zX$20ZD1T8|M9TVeZy0$1Q+mfUdQo#`A468bsIKO`VR7jsiHzdm;3thvbR;)2#k_YL zPPB@*Pk_yD-+6ds(6gq9pRqC6bZT*{AhQ-J`KNK1^PXEI+!;nEzOVZ;1paUSxz7C9 z>!1sqmjlG!gWFg5OF#demD|^SdkOcy+;6pwRUU3NYk`WK2w9^oE7J_0=Z)m{`EV1@8fH5VrqVo&AI95T{jWbd10wJ50cxZeXb`T zeDfVr)3-xu2MG1zE$1Lbd0WS;ZH3m@T+WL$o=)CTDeuzwi$x!^r-XBP04?8wEt$Ce z(Y2@S;KI&9FW2B|O0srMtv+uOznXRT!LMxpjjO)_fC*XNRD2{|T>0h7?)z@a-Zx=D zmX%+Qds}75;dB(Uz=^P>tIGK`_yF$F2u9f$3Mh$oG`QmdI8P zXpY-6xI25*C zp=w$in`drSC2-z8ZEt__r((Axx5<>XkDJi*4<5mWT|Lp)P7(DK5rJcw29BJzE2?%Y z%_RYvt)%b3s5Eaa8C=o+L=yXhX7|pUm$2*RdVKqWl30?$a(eBM7ExcRl7 z|7l4nN2ptOZtyJALO7XEA&EGo5HZ+jIdI_bvk*>(2mw2LV?pl+hq%=aJv(wmOmhKTEqRX8zI!4Mf z7wRqRBu|Q2w@i61j7zPWwLDZ5ocm)$Tm>3_rE8nNcoFxf$gHosX_n`YiXgalzm<8a z;h5Z1j$*^QnuI4_Y*^-z&lRAgV20le?{s}o_rfhlH*4B1bff=G%CXiz_c{-mraY{{zUQnRf1NlTVl`P_dFTJ{-QuOGFL@Q7GNUhj6fE> zlFJkvC3zz%`))!1=+beeqHlwvF}z&<{9;`LdtR^tHMT2n$)>%3}vA?5~~`q#7=dx(^{$1Pn&#VG-R0$Ed2U9ZzP)p031r>nQE zp^PQ3I`NkA>uob-K;Z-Qcy zd>x^%@7)c*bch?E@{N{2NlF)?hVW zD`7`kn$b}>!WPq$helnRW%x5ylw_H;jY^DET;3U|C$sgZ-FBJe$XcG z`;vt=kFX$CIzFO}UG@`<7h>0CX)6y`zqAC19)S<{ox;WcjqZ4V?rd6?_XtPzPG^VmOpc>gV>e(C zKSQ7=TCIP2nO=q<3+?@4L;n*al$fE*VQO%rFBc2y1yUgXX6+sGY; z@OM7lrgu5h=VgmbA?{1L9so1vUY(o=kj;?o2l&D)qWV1hA4LC%Pii8;n>B1m_Fo8M0z z8w?#o82baRcmUOC<%5wmGy6Pn?z&)yR%RD5m)uOeKy%WZ0lLnfjh_vg20TK&Zd8%t z+EbIVF5V)*k*j)5f6<8aIO1~j7CKHE^xE(79rM!9%g@pMKP~{a{%f{o7b?Y&HqCjb zw;Dt@t6j#)Kf~IYyOkAq`_9h0w%JiGi!}ASr#HF^Vese?1a(HZ{^X1Kt26i3J=NQ} zi_Z*CY9}AAIuB^>Co7A@8eGB=FlNZ?NVqJE((DMzKs4hAZ)>#%sGY~ZuSaWPgcuDW zpUh424E?+^d#YZ+u4*LO{Y0#({%zp&bH_A|(4`4P=RSJ&1uM&UTtRq6b-qh`ZCtTT$RDN3#C7d~lzT&OSluZApMX4So7PqUD}) zZKpsiFhZ>#N61|%0SzUo(^>M5C4VF< zf1MS*BlR-AXF0cvC!iprf#s|IXjp_u&MhFQbhKWI*#-1mDT;ZzGlG82TR$4r4iZ|qZ2o@KPN z9lWhYS#m?4?LH}lF==A0BpE`L_f&ObVi2!)9Hce3BAK@G0LIsuGS5+4xi{#vpP)ccdRp4k1>&yZ_n;bw@k3j z`I0gbEMs69q)$$*Aw=+K+KxI}L}0&8n-wo;!DyJajPi+o-+O;xk`ovrE-JJ*NuK~B zS=l_Dg$QBUXD3_Hy}h@;d#z2vf5^`h$GD`Xu5S)QN?+7i(K>a1ep~xzx#4;>@E{-Hr^zGhz3>_O86ulPg!=u&3&{Ko=OL^S;uSOfj_bpYk zNo;LB4tUGTlL5@zvz27!d%(NF(tE*A%1uVLyo}r2(wu-8BBY7>S}@bxzJvwB!`SJi zGBQEo%^#YqFJ6R{{aHOieIC2BjkHM0?#1rT8o5adPHQ^bv1LFx~lAWXS=Xfd~dQK6I|Ab zqqXk#JnU}nl{~0q;x6xeT;37w())zcBRX^Z2j8#jRlayIhe}&j8H?dx68;Xy=ye*X za547)ru)JY--Sd`Au2U^R_=;T`3h$^7^=YK)8}Jf-44^O7`joyOu(TLp_Q>Qt+PU$ zcedHi>wV;>{Q^|_!2C`Fs`)&7-5EStSj~Ey%tXbDkIZ%+<21UOcnNmeM&{%-WF{Lm zV#hllGHc@b@%3^DFTuJb4{k~T5B@Q#?$7f{Sb|-SndS@wkxSII*x!C(B1DleQT?>; zYoF3x59{V1(*drF+=9J%)|Znu$_!kOdo>$ZECgtCO$_+-U1tL#<4DN|08!B)$MgLF z>OQ@P3EuJ-PXMSCVG8UE*sRC^v>L1>}l^2+%`J8Zn_YNxz!kLuX9= z^fHoi1!4Otga0?8KRPo?tP|Re3jE7FjYot(`f0k$ZD3gpb{~Mfj&^o z-FBX}StkQL=@%-jN@3F>2gHn_06c1-p8A%$=?|cT!;l0uX8)A#iuR@4daf_A~(r(zo!{ zJI&+2$!M!KJ>WR}7Z2pDaSpNEuviJXs2<*es2El_PSgOVL?y^b$$9ZxP6%oTDnJ#r{fX zkqezyi{*o_!Iu1Br0H=VT2pR+%5Hvq%zNCUNyJzq@^!hnExYuVLSoi)fst_kDFK=> zX5l;2klFaJbul|P9bs0kkFnT`y952;Upr&TUz`^l$iP%w?$h9su^Eb%-M;QsJ--qv zQKpZvM3v(?VK4io2oGNd=}N3fE-kqCl)ZR`yk};5`~8!f*EXTrW9_d%mb5gQd=jmH zdKZiw8GW`C1E>CkL~gBeI*)2mHCYz(sV)q`t+%qQxFpA+{rUl~(lqpi<>=lDE_tlF zhZ%ZYxr+^x{{x&{OD=MkhAr+W7q|9W&r9MS(OAl~$xRKls7E|_MP2yZeb`=H=)KY0 zUTy#PiTu=VwUqlq!|9Gy?=dkW^Gi;6J+gF?aI#&w20_9g9mkp8Fv`3rsd;4!uLWScR%Z99;0OVJY zSYJv0x~7m4;QGFua*wiwLase6KrLM>P~e1Gzwgt0`vgefU@j1dm6w1r3`U!k722d5 zl+=s9(A6d-h0( z#baI0Sj8PMLZ3-VGDnTF6Pm1jHmJPdLHBL`J~TMIW9-HRv5EscKKjjz{Nr`s*;jQ0 z%4hTC&z2XmyxJ}R#6*eeSblw-gyQtS5RybzamIf6PI=`4>jE+GEUs6**Sf>Tsa)J* zWQOv$anqBp<;2665;72+@e=J5^Zc*H0>S-CPtsyQu_W?QDSMH0z^=p2*6lRtIZ@Hc zo_X#X^j_4j=^$fp-L5bs<^r_z!`}oa+`oj7yXgHVVA+f^c`a-g)V8F|+wl@p2Rhpb zL?u*{!dOk|XbG9P4Nb#+XP>+Va$JV>wMd2e>A%SCRt#TQ(FzPvLr#+%G`1M`(>eqF zSR;!4jZn@Z?Fbe+PSuIlGmMAXqvg*=fv5VwG*#>D9XWi|XxB5L_vyiIakMw=+0k1a zv?bKmLooyOr^Srr{Tfsc`jHC9ztL;tZ%X0S55vXB#Q$I%57*YsEIYvwVlo2&(}4au6&*;FAxTRg)D0YE{t(m?VNTlZeT64@#>K?&sm2J zMq9_U4pi_eDS@3`R~wJr!)>C?e4?U&5&!~v;&8kk#_T>ZXctPAyAQq9)$iTj>ZX7txEJ`MOwvpiI9>`r!X(7xvKD0QD&8`sD9X+qP6 zn=|5-LsZxp7;0S%2tScFgKl8*CPtDVgJM#^`u!Vb2GK+BiW6zTn#IUu1Hy6bdk({;QMv~8gT+!T4d1QQJAVpF5JBX?9V1d zoKjD9rO}{_=nOCEGI>vFrS)-sX9=Js!t?fKVB2~Ogxq>;`XG(Dvcf`P_O@)}zGu;r z>u?oNJ-M7#O^3h76TVOgex+72n277u;|#lJcV|2V{=fe&*`Opm0~vYvMBEL{@Gns{AA{xv%~S#$B%D6tLc zef{X&55v1%f|JmeYwf+Qzpa`N6(y5XJM^-f2gmBcp8wYQtN?8_{hP}|CFzX4MFF3v zMctytNH-M*d!BIer(BZaX{u(qr@qh>TiPyzO0jD-hRb08^7q?M=^4pb zO)(W7Tw9l_cc9QpOHFP6Z8TtCf@GGZ`K=Luz?=PjL$oU>>JNLt%dYoa9?4}~4dmIp zdqB4IC{;MrgMDC@9zqu^yNwx2=t7mrG4OSrmy|^|9QS>g@lkkgK^?VhRZiIx95voo zRgtNw*{{iu$V}EcwDz}>5-XZJNW8lLa*|H@uI;=*s39o?(W4?Q78{n&y?t^Xa)D3l zzagsz>E0mWlRtl13xCvP+P0Fygf7YpX$zcf`%F0eFk^TzY|pav|7iN^sHnT|Yq}N0 z0fz2}p}V9(YUu731Zk1(ZjhF4P`Z`wZcva0iJ^xMiFrSMf9w65HLO`{&b@b^z4zJY z&x7-A{l?Mt|IxB4M>o@ci_U6B58acrBg5sdIh{Fvi0A5Sr|aDsb-7$qsHtM6e!k*( zzfhZ%u;}?+a{cGYFs&fw`uXaIN&5%(+)SIxH#CsCeBJa1$vaPme z$)1?9*anlZ zU6(vcP!5!sw`3$0gLQ?UUGNfg(#S8zz)lZT%xIi7f*r1+0CwTXx=B!^2|nt2|LdnR zV?~`ngtjIUQrUj%SED9P0M6eX@A4i;{I4K{qDQa(xB_4{$ z^Uc3K56amMeX($s#%i0p`quXWi+NUI(j#v_t}yM81H*+{l(qE0N|K0Ck_r#PA*+KeK`e%^s;f|weCT@+}S7vuVck%)VPA+bBFud6w zkXaVp;G7G>T^!aUo33O*VYqoI+1Si;I+=Df=*Il99gh4M!II3Q<$@%5 zy(H0|mLhv6&)f5_>{(cf4;*BQ`O-8lT?Tpx2LjNhiKQ0CTV8Bt#@x9(5I0Y`0$WutWR&|a-W~n; zO=Rl!C9xdrg{$2$XB$R_w5}vXwiFaUd$mrB_V$7R30XvtyF|HAZHLzamlG)oM}#KO zj3rt(8G=lEItM91D`gPumt-h>XTwSJQJIl}xOs~!p{gybM&L7{xJ}*0F+~?1IKmr+ zuz%-k+!hy%50jccv)CeuF0g@o4{u9p>3h&ZMTt39x_i7@F!Re!^x`8EVJ7Cs*$~ zMi;wU)(_h@_C1_$^f&gq0%bboPuF}a@Gqp+uevs5FA+A2-i_VeYb1y#zb%PW!Q+46 zn5ljJ?f_PMw=(_Mw`uH|BS)yt3It{?-7pcHNuP(rO2gr6Xjm-OKzNNZ@`+fZKH+PJ z;n#xYuYt>S8@NmvaE)BQ$cejo50XbxPIKtepw&7$djr8Dy-wq>Q?Y&v3LzDGx0oiz zVPlZ@jmHG&NoRh|doC^YQ_G+0r}QcPxCNlclGULN_B~0C92OfqoY+UXC5dgfkhfdd5Nq1&n!kI$S}k;G;7Iz5*?P#H;JU%mBU0gE4JAPdx2)ll=h7?AW&1_YSBB z);_^57n&;_{##ygpr2tGCMKf=d}ay-Er7YobqpH(B;?X`yo}n}n z<)b2Ta|2%@Uv_3|NY{N6k(0nb6mjmpJG@@Y*i5>p_g0PUdfmlV#~;0ejjP8S2+oXM9jM-UNp@>$qTrBEtK|n_iK; zX8!U`VPI5LE6b22K?~vhxIle`=zH-0?;jZOUs;G$c?_&%M+j=Z?8rSr@nZ<;!G|KB zNeLo09HcKN|KoM`c=Ghg1uoxz)sMe(u?qKhQ6YMEtK`dXzr{PM=(TmnJUd*QXz@ar zV0C=-1+0C?k-_~!+$5I%_IUU=BBrhW(oxv#yx)8ctn188vhHiI7ewIuRlC=Yssv}f zZX!8ft0watVWjVet>^2j>^YVr)+a?p9hMSM$15<G zhJe-d-$-*=HTRCybY&AHFsIAxVmf{l>1N51ax623UM@Y2_UxtrhU1Sv$^YuXPuP*4#koB_N2c6uvKP#MzGDPEc7R&@4)hdE(+ipWdN} zUx-_$4c}=UWGUzH8h&z$PKy~8&NHcs<|b4GI)ZIJ9&fd%V6@3?Gd_v)fc#O!(Qp>E zq2sYc!_NpwX)G>7O+{HCSnyrueJY*3<%Ly0e@V2Pldy>BCM`sRt*WVdJ=W$Vq?(N~u_k$BL8(moJ+tmMpxey-`Ob?eWC z>3}s;_4QE=ZPWw-mt4l4sCuR-=qrm!1kSx@Z#fcA$^#}0i-CB7#yZ0BKf>loC=MC} za+-!MqXD=-V9GDHYz{36i@BhwpgDrpCnrjX@HKKZy1o5p%^97g7}rm{-?yW1Uy-23 z&r*c+D+Cun$SeckEfzMAmT*Iso8I&NpNNxd4r_OhO<&m)-X-=jd&`Dc5@XNAy+e#G| z5Ec#_CRgK8fxFlV3|0!Y2pfP50)IT2;eF@zlHYqsqyCYGRlt8Dr&xpS#A`<(mZ zY!hIpwsUkU(n?}`AZXwzaM;bilGoMge{vdC3EZ&}s;?lop}yEYIKG#de4VzCQzktB zpw)3HHw?~9fwd0Ca5_34#FYsU6H?$>NrI-^ZjqS_Dl2km%pL8h+!pQwj$3WtG^L6P zop^iuk50}2UM^EpGoE4Pi2M#%gCl@m__NE#r)hoij&yS#<6`U2a8+0B-|Qt@2hLO9 z|MZcGi)QjqV6JM~<4e5zVjFLL##`BDZNbL<-@4Gr-Nc|j6-A3S<9k!ZQK$WH&DJ`L zKr1;fT1FyahRCZ7+=V|i-)^%dEJo!c+$znCJ+E;M?vbpbl({uCu-b&>6x5lcW203o z*-|n>irus|xd(gZ^gX6#A%g@cWKs;XEV} z`iNOEF ztV+!ZVJe$=X^Vqt5f!XzV;z*1Oke@cTavGu8b#udk-=i%87^e5b)26oh zGgNHQji_f)F6=tE83ES9fT}V0?Q% zIL+yIA^^94ME=S;dJanr9&e!^z%wxV#Sc)ms_U*7ywjhQ$_Q+mc4yCfQbnulMX3^u z7ub&~q@s>j^rU&bl%lxpQ?1 zUgKp`ztbj>q?tZSUbqDzG$kFMofHBV7*b}p?Jhb~&1S`< zCS$1`=fC`Z^YCksV&ll*Q3NsgM;>0CO;R?ugGtK;rDM=fdX1DykP}iqo4ijCScOXI zKt8L+%G*|0l2mm3EK0{Dh8cm%vW34{i(m;E1h4N>F`3hnDRTPnd#p`fYf38)?$GzW z#|VmhHIhgaWy|=*5(DH^9<56rHX-vBk>4p8+svqR`8U8m%1-Tl+pUd_j;_1jwWL** zrWI|b0Q;7leiY_-IwJ$VsQHctj{s*2UQ2i;gG3X1J0pIw+2&|EV-F27ehYFdxkH^S zZkVzRKmSZxTBhe4*fDJx|NWLx{d5--y!AFG+h9$9J-?~hYS^^ivO0>WenIdF0hk*b z;F&}yWJ|-6R=5!a->NKNaxv1pZ^Bih=WJVmr@K=6VmFfDU{%551;g*V;Q0zV6b`h% zo8PL+Si)ZvUDC>Lyv>_{HLbg8{JI0R9)|b5uEwJM$er@}$x))HxrpP%3HYFR`VJ*} z!MgYhx!ot`>6$r9koLzX!kFsU6mhw3wtPca?LkU}nHq8`7HkVxH({4at;B3N)Tr$) zukSTUFi2i2*&j$3aFa#8eW#yx_VzEqc`kyt`y}xyFm~JL03t6uhOdgYc6GVPJ!;~! zU8sp@hzT!=uOl`?zrn zV#)ch)^yFvhR3h(9o@#Std@PT)NEdaULFrgd9|heu@oqGOGv5Mn_r8#C6?a3o!!f- zty0BSyy|`5&MtAYFV=PahZJ6`YkcaFtR|lDFtg*nfvef+7IIUqG9@7xlJ4Z@^$D*e zRj87jE*jt*OjE1Yf^@RHHDc22At(x z-iN@eYlWEYE#pM9IPds)U-8KX&k`oXLtrI#whp86iy0G@KJYcs@tAu9F;sK0n^a+t z|B-x~MZt9f^&2r*$_f7^uuXT+hCpVd0}{<$)ZB}%b|g2IE&Q%d3N~0-+x+L;As9Cp zZru4!L-(o|j(nId8_i#&ZeVriC)9&g|82KMPhDT+>M4WL1)H2J~{ zRwdxRRD!%UL$6+dpxepn2WiVZXci?jr8z4c_WwnTp8cTNe+<3lBmK%f5HuSRji-KU z-$ypyM||AT4;kDXlzwTybkO(Y72N4(l%=6(L$Zj?&{ax`K?K-MjS7yZ>C2c*J zMCZd$A|m0(y{OgMzZ%pyrG*~~2c|?mtC%7lX5q#=zB;?M*_<7JSVOLvy%h# zs0Op2yN@WGKg|2nff7EZ7E|nEvP?f8V^ZTG76%b)!W&pYtwpy9%Z4nht!*m-o0j5M zShVxWqK<#3}uinDADrH|f^#iFrC=+YKwOhPX%@FDnO?9<~m{1TNDMW50;X zC-E}u2p0X-L|*>1RxEs>%UabEH>>qQ$v__i2AqH)a0HD4qI@z=ZDDbuGK&Cpr5;vU zp7{HU60+afZ|&0~ZB$W&gjRRbWC!jT9;6xiauUI*k{YMOZXPK++6LGE^@3Km=5IQh z_VUMt39qG7Av-b_EAO@ybrSRplkvqVs(MO!A3+{grI!96Zl6GBOql*J{>BDl5+FN9 zFl*!S{IALI2Iu4XJ%UTZE?{`tKzTN{e^t!h0mLyQi6@?Lxwe& zqvN$r)=|mg4dn7!MZic|sx&?H@r%Y;B}Ft67b}iO@oVA`ybC4_=D`jEzu*(0IelR5 zP_^3x?)WvAi>8hPFV=>g4JlTV*qw!lLTb<@EqMsqqk1ux#%~lH`x`IH*AR1M!gwvn zAedJ`O0^ys;CYk3#8Tp*1(wvL(seGj4{OXL) z4|I!A4*Nc?-98{*N4K)yUfGt`AIo89P{|Zrt9Ha2iblE9;sDU&gyt320@!1FWO0i6 z9KY7|V%>5y)??g&7dGgmgf_G(WWUG8BfG`4a!u6=@X>AXn^zdk>|gYXn%cp4UX60B zgNxJD=aVqC`?*o<&|A?w&ik9#_eg%%$02Hhi#d~feob9{5bhAstjR&vd}h>mV%a6| zxw>9i0bdrD=vs9Dh5dUHzlRHSiBCRLI$fv0VR!>a%VB?OBRdKfEv=uZOs=dcz0ITb z%E9+LF}B4Qlu1lXvev<_48HXZY@Ix@bEr?0tTUX~A16RL>KcEA%)bDZgM(!@pC;a@ z8}v71o%?;KEgSIWwc-UPChArG&YL}>BBr~~Ikr#XkDili_x1OU<70im`|WMKwk^((N_3hW+4|S(v?}dXU=uwR-AuL7Z~_ z;+jGsQ7O0dw~XJBt?hW?i|CwC#{`->XzGgO0L`rzULN;bXz{tm84?CHHgf}uQepcz zm3t}8Amr(%4L*xqbDH=&b;)?J0FEOhzVP;CB_5RSPR-ZIvR4KUg9y0jhz0!FPY zvcXcl_}gh^pzYOA1Z=vvWw1;@sq5H(1B`Ev*St4S1Wjy8>n@a1%CQPOLAD;Fk^_Gw z^63^?0thBfj%jA;%lGTxOzsjsS$u^|lPEBQb?FPLlzH(F#jRwNYGX0)waM{h^!b)J!R?G2587jj2RSVs8$`t{7@YSKTGOn^D6DGT@bMVRtv!^eezksLjc_n7!st zgcWUajKx%TS;6Ftu|zXD!8yZ*lbfQA4JDL=sAA{kK%SL{n5^zkV=O*dDHw&yP2PgF zq9a~+Lzktgw($fEZ+%uc|J90?t|3U=*LuUK8n^?0Cxo1FB84JT)Bgi5d5(ftP6IZQ zZDYjx#jXak9^5=bJ$7qRn4iG5(8700iXCOlj2sW?t?b@>`Imm*i<$p3@60>uvJ!il zqx*ux=Yy8{$0(l(JEB+sOCH@sUCUZDV)_(N$`e};Hq^A9z7ZD^o}5Jo_VPY_4V{XI zfGW#|hDYpvnK$hjv~QpL^)hAK3+rjjd8^sIII1b&-&q;C}F8+ zIfTeJtj%#RD*0J_Oa03h$>rn~xGLG&P3I_^2&Wz@TZV0A9bbeue=4>zC3g6mB#se{ z1Yze~$K3WVi zEpTs>A!$iu2P>q&}pKX zCWT5}%F1EB z968PL8&B+M-^;mqz_uT_SGn56UMwljpB1gSYA#O1#*Ejbt=g6fef!YW-Breci@_v3 z?~XzFTCMGUI`cw}JU7y3u~_QbS_2(^mdJ_jZhh{d&u-aDz=5f#Gxj`L*XkyfH*GSS zdhZr7yQNjofc{>k--hDLzc>85;G3L<7f90rKd1}^{PUMk213cDHw8Ox8EgJ} zINw3ywr2fCFV`Y6+3zBclmga@ZQI!h)Kq~#tAhoHN`@5#v1V-k9u(86=@Dd8!Qb=l1tn`-N2kdLk_%i$? zl;8IX%F#M@O9^v`saS4$T0HFWzKcq-cgP0nG)vMbIZA9JB%q`VTW_3 ze>C3U@lngXZ1I$t=8;$ULGZR-H>UTYv%yo7KZl>!Q1IhYbIznq7y&ke>2Zr+-J8PE zOQ8U2=GFy06ITbiH~E~K-xc{d~vr-G^LNylEG*~Qm=@7K}w zrzCUB${euokK+{cb9xxDSQ+d@p;Urkm*|To<5g8I+&f%u792Bbe*@vO-PgB>;RnD; z5x6oDuv^O(SYI>wEjD=|FDSgZiQ7dp)%XQ!_`=TxO_p;m=bRM3TP$9y-{|s%>~geU zkRZtDKVudYf3(fjQOaYlN!a$U5;v_Es@wjqZLqv~qt+Aq4)vEiCr06rzGjFxtX+>3 zYc8Ayf$T3Zqx{gM>q|2p&VI%`>!TK@*tcCQZ}lY&oq4_L)I z3lTc^+cq!|LqR_2L?MMeX_`HcWwS7MPobDx0$-F-GXQ0#tLZjz$U*JvpvzR2t7eH~ z#18Vq6FAn?ip1>L@92Gb!Wz`wmhgY-YLZtiT#3iHma_ehdztnd_FzOmMBZ!hxl2&4 z5jR*|&L;C4vSj2B;RlryiF=uD`i>uqN+nP80gXZkvr-#p=XRc^hl2cxZu!tUn8h|P!N}`NNN3)*NPz@n2$^_f7H5@U zaxIskN|^3#6q?YFuyfr`;XdB^gH;|7!;Cx}@ei0#*J(?5-#?6-eL+07!N(fQUUA<1 z&1$q6s>nP4E12J7u4ems~M8EtNDM?V+7)*Dym2= zd)|fpeP10!{b3V0!eCKHjf>vH)Ma8mQmf6-UFK#WwyOTbx2x&RAVdWky&l)`UDSg2 zis3ivSeo3&AwikpL7V%0(TAHq)8B?ywj8&Q+Y$CpjpgHi_)t`tRL4VDVPKs;@cHUd z-=-!VV_{@0cTp4Z@(((-d_^r%S#nfy^mOY%0k5-GDk62o!m6^{U|9_mOM>6`BMoc^ zRwcSw879+qCgWoGd2LNX)cU%NCN5QnF#=vKPUfaMzOkLtt5+O8Z4mDpcZ+%)lo|O% zDU0Spo){9x*k9?gI(3v3%(6}Ol9n)xikx-Yp&}GV&`Q8Nf;}l{<+);ZC+^$K9%A#M?eX|rFQ$ZvKBz-CMWe)gM+DnK2_<=ffG~?3wYN~*>yoZ(DbROgOYnP(+3iD1 z#|c7^>Moq(7FGRuL)S_30blI-qPkJqx$Noi^pYwocCL~Z;Tt>0*LUIlA8PLwQ8%V| z&;P!}D)Z6l34Hqo7I2kV5&zUla)}PydNw+v`>2aD(?E}&wSJQK7l>u(jyd>apNg0M?rvSxfSgt_|=(+VrqG%----htlI%F(cs2y4bGKSE6k zo7fKd$3Dn-F@=pC>2G<@AJMtTefB(abaE_K86vS9`bi{|DI2TxKE_r-1sZj06Qyho{gW$x< zxh_oHuV3RXY>%Yeht%QwMsik9-R;Gy-T%F}GW8I_e&A@A*m`buejauvGs_4pe2li_ zeTcQz09oC5Ovi6637tvlyhIuoB>|81Tl&GAWXEGt{e+SBoP|nrU-&Hg^GH98mJRXlf7XIy&1j1L)D^;E*ek|FCB{o6@xt2wa(QO!B7+A|Yh-6Bq*XEFXS{ zx~OW$^8Cl9*B_+;J#ECd&F^|j33Ty9dlnhfNVuqEQN%LQ{2atg^g1jGr)d6LPGPCE zw8|{f!7m70@jjAVRy@lANj=8)nsq1$G68MCH|@n$sO`o6k&ANn84^7G_*_(nv_6P* z7OMRWhh6JoK4`9&Uy8@S`D%LdUM=l~K;$cwg9WkZ&ssS6{fa-=4O0VOYka>@`ahDf zw}F)E=2>C-=)>(s;N2q5u~lfFVc|V%oJ%eo1Dlbv%EAVh?yO;ryOw1tn{=bSix7vu zRLIfB_?og*z+o7f8kOdLxn1M19!M^#|I@H0b@R2Iz;+l&VWMVJyGy%f48@-qPd3oF z`1E}XXfEbYA}(zJynYx?g!Ds^_8_;sAvv+5ip8`^gs;zS16^%|`33cE&izp#tp69b z+IQ(_L9}T-RSvJE6SOqZa+)0AxUHEsy3yl!@fn$?0=zYteQ~$=gdaYgF);=}_eIje zZX457ha8t5(+;CH$I(F;3s%zxV&yIA;$Ai_o8IlEo(W}a20DZWE(;TxB#hObcboM? zbMaz{FxIZ_&OfY`ZsKb-!r#avz(Un%E9NNWBHzes+R~Rnehuw5rn<&_ZgTLYAHgXN zdp^g}+#fY3e;j+_^=f3Ikccu(Qc#ENwBS=V7FnJpaNoP$8x7~TJ`TxKukm-SBAWiX zO&km2RJ&+=elDMRQo;I6G^%)z@rvH9SfkI{j0}6Qn@l!IwdWgq>qj1&0o&xSAFoEj zmZ^dDqoHcS1cP-1DM%~f3{cpDHw*nYHm&8s&3^G1%QGp0i-)|5Iy?^Zrseix6J?NL zI(9f9xgIYD>5e;-gxgHpPC9`zGo-6YwcfdCwZ+Q6h4J@#k3qM~G zriNXxGHyn?86Ce6t-dT_I7Z@SmGqM#P!4aaYnswxQ6ULIy4e*cFC`5Dmc6awSV>y3 zFFy5Du(TdyX7!zZd)UhqubayfD1K-MF{cgf?1!B|4us>ILln*9HVGNdxUmZgbdN78 z+Ga7Nb&1_#=c66M>4FlnW2r=prHh#IQ_va z$*Wr303-8huxb`Y$)!}K`$NaOP-P^;P^*GISy6n+`r89vS*d+|ri(+u*D(!b7e##U ze9qT-ls)LyxJqlg@5G1c3Br=v1I~>8JM{{pNYcD{R3m24c*H)S;o1E9Nd1rHly6nK zhp%RDumUX#({t;fuL$>hB3yXT^tw z(4d>aFMz0VPtr&lnqmAXjr-duGwEJWM+HU+RB}~bJ$bm3so=R-Xh%;~3!yUl+mQdgw%5cP$WY9Dm_W^&6 z9~|!Kr~=Mk3CMf)R3E>{p&Vq8D3-{}(xsNf$*FdbLo?y&R5C%en6Z{nn=fI*dj*W5 zD_H6*CQHxr#emAIcy7HMLl3Xrw<&bd9}n^ znYy>+G`YPL(<|a5@o{d#d+D?eT4Pnn-dORcDQ`9bHUD%0465{zgUe4Hl2&_)+6($U z9lw$z2bR+xS;$){sb*)!h1Z9o!5Yz53Sp0yH;ahV?z33L+|iKEe5>XYdL{-o$Nc%>ZZ zbs#Q_e#3r~gJiD2rOffYgCkT9YQ5K7?_xR!svvexBNbJ@e`x6XLmOGf{e?9?xj_Pw z+p>##!W96uCb#*HEh%&j+HJa!*m=chV#-rkJIyy;hMGpZEc2M+Y(o0Nry&=LT#$0F32y?EnXLgn%+z#@GYE@+Ib<>aQ=64 z9~h@HlY|J!P2ljI*VB=qK{`$#+RV-zXJfDcDZTsDJ|!2Pwf6gR z_ZKrQW5Dhqqq~MWI6BYe-p`PE-K^&BX4j2aM+OU-1QpJ%`(x9)W#y07&bi;H9q;(b zf?tQ}rC0qxB%bbjc7Xcf1DYwg9VPW717i#OafsG>O01SY|;+7AKp z0=(BfXwBpP3yb29AYc$PKa#_tf660QA|Gt*B)5J%F1&F&^|3fvSKlfQJ6bdp-Clrr zOmTDK7{W72$HCzkF1kd?Ar|b!;R0l_mdf0$yD_thdmh92-l`G(( z9+SK_a;>iZMAuXS)0!kzEQPc7Rl6m2`<+(0#DL7TFq4pQG;{9NMN`(KC#055D*l#g z64vqiM4_hvG7Bwk_L@{{_BuB?>*yBUl5MxfbE>Vo1AlGW0=j9EM&V+&FtA;&`c3Y% zs%_;BsgO-XC34V2=qZZG+ZJP!lM&QVHNSL{1 z7Nh)Kx2{4$mJqFFAu~Etnik(HA>5wu9VgNwl_}tgNE8%dan$GMtpW6sJywA6WdR%r zzMa#U`Fi}F)Zb!4pyW-uc(j{9TYisit>Lny*d}*G5nHsA5|1Pk;N>$le`3f_Eg1OK z@tTvt4b?92SC0KIy#%KD)(Qk384HD@ai_A4L@Ps?oA(IO)a()u! zdfs)Yo^#LoFeTyZHj*1M`k$@u>h@2$=nA3D#q83T)~w(tu{#-eZnzFzwx z4K(Kgu)3|K0$OoYxRe2_9jbsd2DY{^8PeP8oGD?)?t^S20CTA#$PfADf>w6TSo0&% zH^o0v?;@N~gW8%MZ(_RJG#=v#R9V?q!0~eh%`vplC6I+-0M;9)7+Rz^!}s=S(h7w5 zMcQLofiy@xKCnhFL#fxq*`oL{tVotbR#PctKg5Me+f8FKjKe|1b3=jdp z^U`pwy>MYGP|k*9fjNUy-3&N~L^y1C#g}UAVv^_EwqzH1SfO!qehr;__OQg(Lq_yI z8!LGgDII+rHYOcFss7<$^ug$?_YjR5wU}JPJrtws`hv8wf|lU^;(&D8lN<`*MYnK` zqMUqwx0=6swYg%+E`v6IjOFRQqo8{_H9zaZwL6 z^l|9~=%Ca;g!0R2Vi&;%eUwa6uhvncJyHqoDj;5MCa{TQ`-k>Ueq1OCUN}g$?L8nxF5Wiil8)G$c;lJ`;RTTG2W3cP7m8GT>$J)$Q>U&w|P9IzJmUZ8+2UDZX|K+^rEH6VE z3E!L?3B{;Bv23r-rypR2%sb(dw+h%+sU;@1&CU|c+?)&*HoCfZ3`6B7n~w&nZPSLp@v+EFe`Y%hJyD*2`>18qBk#$M5;z6D8J^&&qmsj6 zoTV|Owh=DF+b5aD;+1h^V^KtdTC=rp0=Ur@1jM#$f>r41Avi5dKk6Vj#yD{rMYzf@ zvy`=z9YDHx=F__7&2)sb;sICd@B@UKP)+1N{PcJ@8XQXeneW79`>PK2Cp1dJH^JCD z{zZ>R4ni}e!#0R#XbLh7_G_eM6JpT7SL`>hR2Xc-@v$BGX`$v>4TdyWmLYwgf6{UU zB||QzEzo|jJM*G+=ujOFZj6idApnUw@Yj*rGr#v!^VC?k2Dn&VE>!mmw6G~g(O!5A zvK9I{HDR+eBvq5(r-_PkZ=^pd$4uSCvGZn$TSslm)P(cVO_2^3bG>m(aqHL*BSn=^5bntH zynb?b?CylSKlv$an0c=H@0?M5UwNLff9^Raf1EVh#`vUt6CVBXiiU$-D>!gZ)B-kn zoM?u)`r#2i?LC`TU&SkCqJSc_kU-zqU@j7O2N@go=Eb}SS;wSePZ!;A)V~!|(QpyZ zwo2VQ@L)H$TAmT9S7tj(K5{C+r6-xBs751DWgcvU-!0$M=fPXpDq|KBjosNRD=fr5 z`Pq3(D`-g9H}9pKWHm13jC%|#ilzANHoVhV@Vh-^Y;0JK?5~YX@HW_zCct+#&>$bT zp_qYi?BRC#Ux%bLlriJvH5&yS|G@@iOF;6Wwl)p{nVN3y*`ndK6j;bk7c^*NgxCR>8(7SQmg#fqWajJoe2F>0=8PULIsTxM1$XAQ(l@UN*2;UxD@056Ii)LRu}%X z0gjTU6^g)u&f)WM?OcTnykPCUYHuQAQoJic(`613{ZycRYL%*E1_c3`wwsj7DIM}I z9MY*+XxT)&;;1fv!Ppw2yW+Ajk97~J3eY$vt2keY2ub|CD;)ZQ@!Jc>+vghp)n$p!CTS4*iKK8WlGJtY<>>E3>g%IP&%D5k zN%*J!UqZnsW!cL&NoWRygvfPj=;|ncNt)>NLj{k&%UGZ3zE+CotSUiEm77Ct!r-?+ z?VuvrG^s-tzkEo4wOM_5;E#s{FM3qlxW#_xmLKUxDIFNE#SO01kt>9<(;~tX^wA9q zabBS3$!n9=1Q%0#8Gd zX2S-J)s!;sLx=wleDQ||+KVzP_~k~4`f7Q+R_n@&v;D)$p{OXr|KV&VATZDV@w3Mo zOy~!K-#4RW)m_a@?^)#*L3`q7avyoKoqasQhWTtYBl zm^=P8)Ohs^{pZce%l)$qHj==P!L^b7=MUM_ zN$IS5MU4_G{rS%1Y5>j5u*Pf2{Zjy7R|RFt!K*T7MW!>0hEyuB1+7H}SyQr3Ilqc_dr)wRNfp*HgBio>f(phM8?0?^S?Y^H{@?E`im zUjZa((M&`co9p^(ElX3K2?wboN+1Vg)UOwe3a~+)4%&5cUtUXtPEdnIt8tn z{h-#ALl2i^iXyzJZJ?$542fd{OOLoU5K<@nK++6QC1MUz!mi%wc)65EOk;?B6r_QZ z|0MbFbMly2`kVipt>(KKxrU^-HL_9BV= zx}7$*0#{kwd>?9f@Y5cCB3dpxzCAgMBrN72JnjXXk31+ne_ehK=sKmiJz2~?g=deg zmYZyuublk_0*d1!cO|C3w3@)T;15j)X+Cr%+TNP{1M1FCLWuT^e23>%&+f}bgw3Bc zJ#pn(BrGEHmya=T$dQ}+$W0GHKfUckJgxK|SBekxk1-HrA(4Hfp0$ zyoz6KI${J(7e98NJe-Vmi(UOC=SKP>U5pc6eQkQK7fy3AQpkcNt;jS%LQ{0g(tpIQpYM7U;E#jR>0g>4$MVW zj*i9fMw`UgAIm>t4u^FpNTg;Uz7^dic#Yv7Y*I~=keURHU;Z;umXH2)*D3qswzNWK z`G@sA>`yDOcYB>|a@<2c6ZtZa4G_Oh0F8H;gUW5GS$l{eSd(193E;k_m)QZ%2ZQ>@ zDLUVoQY>JcpsZ~BZ_nlIL8j?ExrWJI{(DCEz2E;7Mg(`oyCuu(_Du26=WC^IIpGRwB_dTKrw(v zZ*peVd)l2diLjI8F|Hri1AVF4lrI3?*nGC-PglN{j z)~C0AUGOOx#?E?J^mF`D8?15TMiBHf;~_cLA`LQHN*uDUXpoP>%QW47Nl0rEHxO3D zxWLEE9TDc2t8Lm|nFir=dC=KJ8gUE>rI1@DbbhAc}%_R>cuE`v%z!;=i(6e&xd zn&S5LDRmpfC!XIVZQPqro71_eaM>Sb%y14 zb#}b%9Pe7@zUO?~JO6FRJ=CFb-?{A}|GjPfaUX;CyEu#PyXL#^eVF9)w&P9LYnYIh__$&-vAVM{P(8ua{FAj%SLXpk?wmP?)rbK_x81Y)BSzO{hs7?|JfYs|~L!bMQNP*}iQ6c!ke)IxOGsbmuD^)VD0=XB%E_+f2h}FqvbWlo&H7{B_?_ z7bPE_oYA|H%`i||@id<_b6)JWTxPGa%V_o!#a~R3i`ksg7LwDiG?#tHvMl*%v*!K9 ziu3u5ZQl{{IknQX#KeGgHmcAn(t!d$$W`Xitlf( z3Hua>LV(F)z-q%;-3aiTf@ix8Up;-qN9&g?%7)$8v#J{^V|m%`1Rp>t7G=d%-%%9> zgLiudfn`E%572p`eNb$Y(6{RtJcyh)^*O#~$Se8w`#vSLgIVmMZ z@3F?W&*Bgv}QB(G{*92yWy*+7ksk0WSNlW)Hf9DmY zRy0@9RD(*dET*)|6SYB-D6?{nN@y#JrOJwWq14S_&U)hHf!;qBlVJ=|mJvLZ?FqLSx)sx!JIZOYd^MKD(FjTuvu-9a?KbRCQaKN)PM z?nQ}HWm>Z!hK;ChmNh9Kk47Rb6sV*~S!tOJEJcn4nCLy@bS?7G&?hVGdl&g8B5Q@) z@7BZZ!+q0Wdei*+rg`D6b3!Bsx9dKfXAk!oUVqNp=ebKBGPk{MU2u5Z?S8y% zF1zpgaVqzw^~<4lH6qXVo94zhX^C%}Pp5W1wC=mE^_%u5?jD=3TNB(~!#;GKeOH?g z+w!Jz@a8q)eV(8D{(o0HAFA(6W#F^-0l*>a*y;Z}#_eY=%?P1<)d&*uF)8J>)SV(P zzG>M+s)8FI{mSJh@lSl~mw*1}W#2wjEy3OZ5EQ};ptAB`h(W*=hEQ32sR)H7)Ry=b zBfz1O&#is8e?PXE?wcK2-kz@fYlczQa zFT|ucX<6i?$(raP4K{=*l|)c$x-&$R9H@zeSHTYYx< z{L^_{u6tT-9=35>y-eHPLBC_}o6hH*>NU6OJ-jc6?p1YA&vbl`yy&MkOxJTfsPC|S zhbG6VPyIoErhDQK`ZC?SyI%C_pkKp5y@%Igy5>{+L#{I%T>snS@o+KY^SwT1;iv%dq(!pL?Z9Y6V!B1Q@#!{w<`r{ zW16nZG`4bIQWNQ)2deLvEUOBmHKPx>5Lgc#kLOFqC<=LL4Bx$a$*QUuL*TM+xtIy< zU*{bc^Ep>t%j5Y16tqsl$v;?~(z}t?jV!C0kJi_m)HRn~%cJ?6Cg;B=rFgd6@&0nj zb>A^7g%0q;({oO9c)wDLr@J+mZOfy@G8-Uf4Bj&a36ozBJ)Ivh*~>k6&#Wlfx&Z}T z%;s{*m1gjc!FvjA*bY6`SbR1>XsswR&D?e0NpwV#3~Zw{Gh0v?LtPXSF_1&)pDb2f zbuF`^z=go$`I1j}8#Y6iQx9r^N;^e6T#lMbDzF z7`$Wa92c{>pyeCGy6+jIr#4cT)|%ZoaNf*VlyXmZr%-c#S zR1#c)loUEZP)QV6bD(Hv`vY+@F6011f8)K!$sC^m9R(Y}{m}XSZt{&#C}MtxPLUic-z<}{58{~DJxP2lD&HpSz*;FBg5W0`10hZL%b-R` zg`vK9pJddl^Z4}coZd}`tG#LIdN>zPYmUP?{_tIXILE@PbKNu-Oy5(PsXYZZ^Uu_V z;o!e%Ei;`nul@G#?>tWSH|F;Hf5`g|*C>aI^I;71;WfIwik{Bb->M5_Uabz=cX;eH zr%rQS&y6u@TB}X-QaETU+^jubw~mHIjt=xeZn4;KfIo|$1At? zAD(M^CZ_tPu{@36!*%+!mOdPp)Ab9vf73OZ^c|43f9^vHvL;GV<{|CmeHgM)G)4Ik zZYlrijt`Kc%8ao=+hTJ%KimG(?|twaenzXuzr6W>*?V(X*_Jgy>x*FxIql)>Grf23 zeecz)s-~)`ZmA@s5>|s+LKvkI4!Dg64i-3AG!m$RmM{<|0;z@3Sa?Vn?I5hCq-vnm zEmT!+es?-&pFQNxTw_FFM8wL-*emzh=XN1symhosX08>(AAiJ(e~kaX|LR@PhPlnDM)F9DABSedUhP zJWB!aS0Mby2>()`3xO;ZdZkcHMX^>^R0>fmbgi7Kq;*<3oE%0g+R-e`^3C5H%*}sh z%=K>xWxcT?xb8={H&ysg5rz0D;5g4SX z7<0TI1!NcM#X2bipg3&SQFw1SY)bh?5nsr-Ii~`g4(;^;7m>imKBDj*H=G3rV(5~( zag&W>k9doH)k&X`SVmYX4x3zr+lGE)opkexa~SJ(;g1Ygkw~S&XJkU+rWn^tWLP2* z+b8!T5sUcThq@7tIF?A1;@XPsM?CZa4{d-u!W74n){R%RG{m(T*9Qr1QU)t-?y*le ze!n6U6z45&xsfr5xQ|R-%G?b}yD1ThOw&FYQ&f7S?`{yhCq~C4b>Wy;M#g0#2^qZc zohlCFcH`lmi};Y>f(G2_ATR_1H|fsAOJ~3K~($O z9hb`$tr>afTOWALftHHijC^+Sg4fFp-|n_3A$hwwmr7xsqtu%H(DO2+9NZ5*+uo<}Q&OUYn!VP!Oeb8Z++$f=NxrW zFpLHz1*I-1mGVo$7^Dzft=H&~NamsIc)dAiKlmd7gE8!fj^BCthHckywcIcoZ^ggs zTHdTSSnGU5`m(|yXs7rHlFk@ z=@tcW-?!+HYOpf{gE0uv)tv@R93mE_K&NC`wu(&%Pt0Lw^vewf}i(j)gtXCN< zD{=%^dM3;QvJ=Q2M7Splfoi%2fMtbbpv0VzX}!LX0m?!W+wpfwqsL)n9pL@D9NFPtYW7L<}GC$NJHX%s!q zg>?w+*(1?;!Q3;#0w=w--q|3R*ucMx?qJ-IeomBxk-)J6Y6K%44B(VNhk17`xWXGw z#?eu2Uco9zQBiRkX^fLa|NqhNUgfmt8NB%A?H)L(q9QcYp`JM`h>F<aK^Dl zSc(jm8XR8lt9k2K8TX?0X!q@R(FM&UT+kK>#SS=(^T_e9<& zeoqxWCTXWi?)3qNMbbyAoQZ2O&PBw3#OIiljS8cbH%W!nJ$`Px z9`T+Uh2s3CyiUBLA!NsamrE`GsWGnQus-vCBLBbOz-P&%y)xX7il?}Sqwt;@9pilN zl4nuD5P9PrhxL#e{vxhYUNG`UO}H2F7kT)|(?!@)kE5Q6A42(kIrd}YdntU|(U4*t zxUj#As3;Z?PyL7Z@aS@Pe*U8WH($T~gFhPD#XsFQ@9$iY>%niIi2^`|q4|h%cG7iw z=PD6&KH`xQz{Mml$*eymb3gezQvjS)FPu^UJgSf>0OmS=N(lguZLTMTe<4v?pi7CY z6?!4jXA-rPmZd^3l~c6>sf?Atv0~alM0|N3I4tn6*}D<%DbNLi8y1gfIA~vG@_p%<{HR!%HtMk1 z#&e02wmKE8!=e;X!2b*Ag1A^ba8{y0q(Ft$Dl*Nfw<_|B=s;z#t^lfZs!@!iGTzG>B8nI zqVN`pR^03p%c~%asQ}l6Iws&x1Sakwl(BwHB5vSuW!vO4)!=qa7PE=C$F>u13)ci3 zt?(OQAn0!IapF1-%&zm_8G?`)6e5$QNCy~GhM);^F@mU*H5KQ)3pfo&KirtCoyZtO z1x{4xw0>h1y95_ulXm8K?KvCfHYz0GK&S)W-00sAC&POZ=Pb@`oG-_L5R3sQt)IUm zj#tYyy&3t{-3>2SD}?a=Zb~Scq36YN>4W@f%`fk6Sri5Pewc*RWm$R)st_!7 z$)>9L>fw%MSyJhO_qzwLj8Mu8;zP%)^%<+O@|JN2i&A5pqm+tf967IR9~ccEWUf#e z=Y0CYb{Kq?fuh1Wi^1{r_Lgnea$YaJ_d(yWscHsec)PjaX1}8@3YJyPhy4!c9HTK< zEZer_aIK`j4N8j< zF-VG`VE5SrSZ~!f2BZ*18KoZCM$WguxsJ1_7X41N3S|p;7NKoc8>c3RS=`fa?98FhnJ$fkF$$G2o{H69g7Ppl^f6qwV8y1`a}Q@8?DK9&S~@(0hU2 zjt*_S5@zf@lOd(>3dPYYQ^qbNR|@-}>w?VEIjk1wMU8%Yb;Li>B=j&a=)!PNu!gXkAo_h+y1tD&Qd+8p3n82sA13C^ZLq6qWFSx1$I= zLZFE*@^^;$+}kQ<@oQowI?r4zyetzqZ&O@F5b#kTZeSih#4IFvxh}$+{fY z){)R34GmFHjtYy&PbBl<0&f?2G!gmYz;6q6;E7zA+sN;`z{`mUPaJQXz!T>+G2l2C z`YA)7F1#lye5MMBzz;h>ECLQ;GLB4b^@o1qz7^l`$oodTO!5c`OyRu}Dy;da7)y0(NI_8rm;YL#R}!&MR;?7G zkPaoSlhR985aH~_Ab3}XqSAcXnFASrYJ zO^C!L$lp8Cnt0yk$m1)PF*9W+0+l__%RXu+tDEd!qt1yhc=A~ z7$dIIa?iP*#OaCg%so%n)5-TO`EEGA=H_X3{hfmCW3Xn|?3$dJ>sW3K_lLl=NgUSs z`__T9E@-F&tqkwAJpt1gBDJgk)^PumJ!kcT_qzu!gKPb|tT?Y1+%&t%&Au51E*DE~ z+lGsJ!K$jcZFcPXp5K1)iko&Hg2qWc?DzE62NrzjI;ujmZ99|{jMnnQ%PVf1J?${? z+4%)ZDt`9iJr2j^a>@JMj+d(y`>sbR$*QQhZTEa{b3UcrTb16`|Ds;;0BOYL;cm^=`+T%^BZp?|8M|`1E~dWEe-je}2g~+k1?)J|$nfM~1ip_if9|)e7Sr zA9g$5Zq693MF{w?-*L6ta9-8?9|{R3||=e*l( z+4UV4izT)8M;(6l;XRkjW$;$;F68fb4_N1Tz4j>x&&vhZ&5mVRa@RJzUTu&w^mdjMjv-g#|v<-iVTNO7FNX zDjtT8i+ah2y@vsyaXoi`@@s~b=WOFS{Pfa_Nl_Tjy!(jy4lTS(z7lToP$;AbLG?Vh zDMd&lDDkNQ1Mm9pyy9VsVDEg2O8{qr(gz>Y zK!~6uP`*#WVXQ(5TsRCL=g*OFZZUOWNCI6bIpN-COYqV65yA%^bUxWM5`>aS2gY~> zR17NU0Be0@{V`-fn1r41?gwj9@K8A5O^DE60vpaBb{Io!jF{a(N^uyA@z#GG6gQ)@ zQ))lLZ#|ub42lLRS^U=Tp%;gP>2v%2Ng5EjzZ3kz#OFk$Jg3bQ{hXq)IUKY6M_TUG zvyYUY?JF;n@oTf~#p@}0Jk!SC*&$N<4tMdt}d^6${{_ z3V`nr_5ag^e-J4AVNIkw2>&aEUP|ObS-DcEN;(Q@Ez&qAN7IWz6@S!s-~2a>+a+2| zLj5H8rvW<*$e>~D?@RjLYYU==Sy}5Z;d=A)P`|l;bN%M}&GiXa zDqQ*VVs3IAiE@y0K8kr%1jMpa8#A1V`nlume~>i&z(dzk>k{L?>w+$DI1z&IPd9hed0qX`cB8BAjdgHSP z)V0qB(D&3@v(N>-gg0lEE0-abU0`N=QCCyW}rEykk|Cl&avos=4c0dNUvq zY^s`H-Q56!FU~JnlqLJV<5zb#{P5z6-j3WhK8Ar3lDod8lnUc4>!RfAaO_}NRxGsU zu5D>Y@Ap~hf^FA^sQnftB~rjewcwZcA6S(&?a=dPea3CqvhTf_>#SbU^c_+NHf7DW zZ+#F!A^76*CHKva){MN~ob&73Yu07OzVB(xz|~^Ib+bhapY`CkUc9Cqd%AI?l!{Vo z9{QGbSpWGkB&n7v;Q$e!t2!; zUp?H>4m~dyYqmqn_s?GNtGjFZapd*tjP1~|)CKRZuDSo)Ux&5jg=&#f?SvWg45UCx zFGPBV$x#T<+82&Yo7e3-A4OhxclvI0AVNAh02jOi2-bZZqw(qN4E{ZkQUuG1z*sve z2?#oaezYhR*o))^lZmixg2hQEoQ#*7JF(1h=}zz|Il{Jr8&1f@uCLLx&EJS_suLr^vt8`2;O7)Jly zIj@Y+@z_F8ltiaDjaL|gpBpD&3>%9U2jg4}0BC|xRGqz|`r#Lk_MG2bzqx*M{pR}r z%Qb5_3fFYsG0Abp7>pYjU5oYkn2|*k{DUD`aY&IXgby5V3U_z=z5nT-{n7c%%eS3|cVMnxWQ=rDCiUrYf+7#%gggDByDy0qJ#03Gj$voWs$Pke|5z&nXK| z3I0zh24e68e_tWJDqE=|;lEgifPZU+S|}$8OfY6}iApC0SuZlmE`^J@x0JE0sMiKiB4WX+Q60uFg}>roR-9kMwhdqi3{{*O{yPspC!ibf<9p z1pLpv|1SjFr{Mntz8=*J-j`OOfHx?}wyS_sSK{pNv1Vsp4z9}ls7E6A0|AEWp%Dew> zZ_&zI)$5{UQIveOy~R2=1sl9?8+zk?Xz$uRpI=-tI!8MUXen70C9N5Gvpz#f$!F&; z(9*k~Z>kz4e7eD_<%;{Z;e4@VHw;|V3-*1_A_UcI$AQ=DGeofPS_c>Pg6sVQrBaN} z`apeUg}?^~taJ%3xavz81pk3|4_jI@u=3HbmeLYu4K~f^j)onZue>ViHg>LD&pJwvy7Pmy`Z%y$DC)Y!K!Zgg|tDPnz=D z8!ypIA#lPSEHmMKP{B=ANr4%y@7h9H_;?S_df;N44iN$^#Wm*5NJ4scO-mW-1fC!$ zxU98|#tYgilZFsOtG%7GAlN_ZSd5ows5j6`Z|TLshy9&Img32MMKl}p5(2vIpIXRr{Hriosa(%Jb%i(Jc(EQb!vV- zX`Y|SA3e1mzRTL;nQP!V{T$8v6LWL4j^uHjSSBhmU7|RZ;WKDhy{CYcNUhMKrcgCb zTC6kEUTPyvvjoL3-~>)sQLXO(;PuX-j0K~BI{QydXd{?n(uKKq z$fyYBR8YX@7~&uO{#Y!4k2REf0Gv_)JSFUl+28rOf2E~Q^SAI;|KePsHp+^HLRQMj zT3YMWXq+&nmxC7nRd@dv|Ar{6@WK27GocTRLd2lkYMMs-vtkj5h+UBo%b?W}LVy*N zD$LM`&xsQ*g<&03eMSe z2andu9L!I(ozDr(w|lCMqk5l!=TDiJlYP5Dz`)}$aS1jtfLI8M7CixgWAA}fsT<9E zJCOgyp8MO5&(AODtzqByR7JsPEbZun&bbJLfZML&M;BM@h92jFi?a1W__XjrLSd|r8N*220u4}lc zm)v&^);e0#b6zdk4Lze7*$q8s)q-_ZA%%x&w1)e(;Vc|!sCB`jEP1oJ;HTH$uqtcT zg-<=Gm1J3zlZE{+KfL4Z=G+U<%Eu$P?^-VF1rL4a&&vnzgWGmbsT55=P$67Iy+9O2tVu} z_}!~FG{eBp?ymXb>=J-)_gk!U{NVhB&k$kXbNeU1@Lq8+1^tT@TnZ9*c|t4z@A~ho zqtX(Ao6-T6!3tf4X!mxsKCPnkuJ8nlxCF+*`gj3C;DSrM!3*?SiOIcR3E@-gg^2$m zSi&7%IU%BQAihuG2!YT7YphrP&z+N+g23P{_ujF?;~`kmN9T__7=pFjf)g%eCGeK{ zNvUC6!f^)0g!BB05Z;@?I2Y3K`TnCqAX@0-aRC?JQz1YLiNPxfY=G4ohZRi6A|lhJ zlwcUFpT}tq1%>d(jpAfREB|EV3e1snA#2j>A5gw|dAxr;bI*Uy^@#2f@ct26{3&oe zMFXC5|G7NV$7srPVE-=k>d89lXeoa5O6l`+=!|%#Z#JZovgYX!hr})RYXE~gw*2>55__6xWy*|eOK9>hSg-78MViqqQ zyqt~(=`g0H9~-=Pxj)*bz#kEa(z^tdz;!Rb{^$SXXaB)=`{fNmg)6`afsw*n|CM6U zlA)CJ3(Z(-e-xn7n6mKc0hRQTHG*D!&I{m_^?%k2VCERWQP~_#N0|AJXa7$r1m=Z* zsiY71S4#9!q0ba@>D~V2N;z3dYn2*}lg-%GJ$v?3@6f@VFQ3&}qxwG)IOK^fYehx6!usACP2 zxQ+PyQJIG_@c+}Vg|Q! z{T%%^9_L5+OW}0$b@PoR!sOf&8%%^fzN&L$PjQ`$_VVjS#b^vaIDf%l4c$2MVZY<`YR!iL z|9Q0x#}^8=ZNsuCdAEDu_2!Ib^hXa5a%(MGO16E+=jSi@^5GWaEH76Z z_QSwnMqVuZdHh;yM(6n6=7Lgs|GY889st-6KAK})lspVAFPCdNGqUYkR%OY47}yOR z>(X2I5&o=xtxE=D7|h6S?9o#4{qqZ&q38AbjCL64jiDWTl#o;*tHF!KlGcn2X2jxH z=z^c#zV}Bt>Ls`B9w7zi)q+J)@NWOWx~yo;z(d>eVzu_c0`V^HwJvDK0b?!W&T_fl zuzPtQT-iAsEe|>Dr}Z4^YVe{Egh>0r`i=FODzxwxa>XGCpbU#zfwy!M(hCMgOhzlW zWQx=r-vKSWxx`zq4`~SnctKqG3ZZgYB{!j`Y8yq~A&ZaU9X`5AbR+D%}0hu3w23&Ojl zj@!!TJa2yv&QtHnr|`*X9ZvM0UUTnE+F$zI6876UevHTM&b?1_Wl!w^)*Rz}4*pYo z;Yl2Gyq+Ed=npo5SHKDlZVZ`Sf>PE6dLX5y5H-Sr8OKA~!Qkj0y{vE`%4YrQdoTZZ z9A5vE-S+GEFp;aNXTp0p@o{p}Sv!f>BZMvfQ@jA4dX+z#67Zw~AQS#S*7?T-fB#z~ z?5#o~q(mu!DiwMyz3{(QR&8_;{*`rF8G$n6AbZ+>VB25)KH3ZDLV}9g7%9S}!R}p6 zw+-q)4sx`Lc@~1EJPP!lDG>fFUC>MT)#1y_gfQmUk&qxc5Qa=>nk=Zv^Xa=x%I5@C z9m>qU3@LF`JQ7x>%{*5;r2U&iACIo6fSB8`G9l})lmSQt%YRV@fD{ii*Z&s`z~swx z0-o9L&wvRzkFPnn;uSaRQ}dKx-<1K#l^Ih%dHrnd3H5kQ<;sNTG63m&G*|u+1CXq( zBVw3p&S#W%--Q76j4Efl@8ynDD0f}@XKzbG^a5rksHtJQ{D z7u@$9-#+-L_^YyFu$H)#mWqWg!@LXHVL%DVd0mI269vsM zcvov{(cw78XbjuF=k4YU5G;z)k2Ay?h*=4?eaqEy%|aJ60GIQzdYN`7&FgO-wZ7$^(PN|#9GkAA#Zta#`tBdw1C`m|_`3m|R#J zLJPDLJ}{r~_%OjD<*c{%3n`{}10F8_1}x2 z*fQ2lDG!bF_ZL6<9b_4Ee;x0|={`A2lb)bApGuQY3Al6g_q*^SbM4OYJt>{b18*ffZ4CdHKG}qS0`cLN~A5$EA%8Q&@L!UbSIRjOCeQXT5 z;QADQ8T&h>;5l{g)cQQNrk@%MJkML;)H@lkqxXi>Mz0*6xi$MS<;~pvNmrSTFO{is zFNP=a64ya)fJ^yF5u=-g{aE|{h4A}vJoW8E^Mh0cT35KiV5L8DX5HavS4{1S=!)~J z`d`2Qp#RstyKe(dywWL^UC)V1@NxN9*gkuw-}_r^b}7iEPbJW%KnYPW;Z- z#wq;F)yrTN`FfdKqX|yu?mxBGE2Lr!!P%1MEUo&ljPXM&Pa`Rzss zFNjQid0$}UTgAmc^^tCj^C|awWB9@4B_H;Cw3IZ#x_q@-r4;=*vJ5E?@4E);ynBDNsQ>EzhKqW^yZx3$QF7mPyr>s!`<8W8PsbUIv%KFw z_!tFc#Y5lm;}+#r6p-;@=qaS)yjt*n{}9~P1%=jJH#?t=Knk=~w0D;72fKsMVX%k?PxL8WH6mEr zZQ!>;pgtc>-+5-=L9P8cdl{5m{#c6(-Yvc?ipfJj2=C?}71xpJwPC%-v+q&3jWA8^ z;Sf@K21Eq@059MwoX#DDtoZH<5iRuu1py($K=7(iehqlf8WEHtBEaFxc$kUd144|2!x?{se~&>- zo&r*a3<81AiN_l(A+Vzm$n_QF+gFeG(46m>+xt@bm(zh0H2$M|{7io$r?E4?CqCsA zEt%Q3kH(hn+v0SYlYfyk+?pVt!KoVS+DhtN2KtDUOR{ z_+wilou`zhs;s{p&S&B`hk2HtJjG8uh3jXm#Up+*@9#t(kNCxG%#V1qbY0H1|FL)I z#P8WPK7-d&Ya*ZDqq5K7!6)81$8F9Vp2AD~8?&{m1V=9Yxza#H(@Ibd$0Os3*ea7h zhd`+kCmgmnUSbzutyk`P#h^flzIgHW^S|}w&wuqlN10tr{}COt90DhU^|ZaLA=tj=XlTi9TIrb`mtVY zC%u=plh(<{p|ihyjCucQKWTgEIMaS}xYIWCD~>tERo+gVr+mGnW#c%~`AyrHTW^o9Vk3pUu5@DI9s9E-ODK_tUi(`L`%b3nRjW@(LzpW%S@N;ZOPY%x??!zwhX+;l67pf&82Mdq!geqpuju z$hX^jgoGczc!g4WvY@_MZ!p$!Ue{9)zWcUeQ&wCqmb^VX!#SVg?sBoj;dr&$@Md$) zO|z#NdyKWbUazUO=BxWVbO@(=Ue}abQ56OKIMNJ*4{(RyX0KKo?z@(=y5{TcJ@0o9 zoCkORySAkpNABB(oBfWjw|87Fm#oSf<1A;@0%I-48nlvF=Qytyfa7|5Po)d`(eP%q z;j4!`?%IY;RdL%i+;2|FZ|lkVMc!*ezZOsbF}=6_-?>a#*85-72=w~F{Q<`#+R}oK7hfYw8Ua@CJ4lb z%*{CCg=`zr3L0a5xwiCg&$k z!I!rm%g^x|DIJS{Da-@&{m;>woL~FcmC~l1FUtE%%cnTWX;j))%7f)}Hr_i$<8z)R zr+q0LXgBRa%~;49V}jO__YA z1WnJho5LAlkMPEJ?aa7R7}9aX&k%Xln=7*R(z>ZiEw&SJ5P#a3i#qmp@M<+>P z964+Z1&?u&mQ7)f{YDs4c++vrl_9w=leP+u=QQ?HWO%X3y*W73D|K;7=hU4TZ`_dc zc~0>jVaV5U4oeD8KHoCwOJ?Qcm}B|0uQ(18IP>wPWz#n2<|u#ntrT}{Lo*CCeNQtE-mQM{=hDv?3-;r{>-C0*zGqpMJ}$sG zu+)As_hXL$E*C5Ax|Vm_d$g3)x}emGpWS?5T~CMQ^Zi_>(DhhU8j}?wmE8217 zkZcV;{^VAf#zzl;(GtU>-C0h-|^5jC@EN!C1+KQ zRtoDJwJxZXMoYxWR%OM0=ozf#yjt*Kze5PYtL2(+AGUn| z?1FwAc<4JmG<%k1$<=a&7LuEGPiK6Z#&ub->pRY>8YLy)?zYrLL7@~v3clHIDTAVA zQ~AJwLI_^3Hgsl0OT}4L@$GKQ`NAKq0K8J7tf+N~eW%#}_B{Yv9IXG-tD(I7Y)Fhc z__Z3n?7YRFU`_YI0dY`h83Om=1$^PXVWK~%Pp#*scpgH9^m>TNtv#l|li?*3NSt#A z7)7w?1Z#E_)CGYzz%wRV`V@US^r57OMM^KIdzbrYYzlFUgLi==rrGmK3@nEX1%W0y z;on7{+DExbcfpAd0`3!l!;poa~c-+t89vAQBJW0-1B_HoSTq@S^^F>7m%uZ``;HC<%&Ql25d(q}mzFo%P*&;0pZ`FuR#IPEOY zeyWYMzaqOg$8V%O=-j;Lzw>7)e5Z~HYQv2r=CgS{%$7gt!7iL_fm=(;fxXTv$ud66?6Iz~V81$N{+I8R1ihdNH?z zP3p$>($DmnN!pJ0MRHGt{)1aFWbKJ3`b^t)Nm-IQ@%|X^&PtBB1@s`eWx}K7?T!^*E|fJKPw&fOeG}?U2xwvK8@aP z$7Q|bzHjMn%)?HOkwvxT@)xGc(GV9TFb8Q5kk-mUQvM%)Mbfr zmYcngj9(W8x6Ph!AGR#YauV`ZWl5zqo2q7T*7x1)e6T{PCRgxf;jH^*sP*iv>~$x^d)Uv7j|04}Hs0mt3qj)KYox zhlhJ!EZ1I0!&{o~yOxW3ffj;qc3VC>yI@__{QT~k!5UsI*4*_C>$1e*s8zvvRrAaH z8vt&by^mv{{SgZxU@!&?bmK^^HMSAhg=4&Gy;VB#h;uNd)>GkZI(TcnoZKn>dGdI! zQV@da1&h4EXWKF1O;NFZ;RRfWpb#>=CQ0x+ydnTch@yX}D})!SwLDmNq?dHDkvS70 z96#{CjXj+2mv|gco&ZE6f;eR1kYYO2>6{nR{oME%1<{Hh@#73=?bm%2@?}W&tb!sU zDnfkV#dxeCaqEvlemF`{C^5}}3OF$#Qlb`Kp&>#$Wq?V?^+X|4eb6@+Bz@DL&IWDesVuCFc`! zem1B7>AK11ImK7bE2p$Pr^7jKk={>nl(w6%lN7J%{O2@1osXyB&2cf#l+F2#xQ0@G zZH}jy<2UC1=J-V^9 z@MOSArXv7Oh5G{1SO6k|nYP8_Ywq6M5dcAkmZ{=Q0KmCR%zRI0>hTrT_fh(mwLmQ- z#X=&MULhcs${?h1qwKru{~m7FD1VmF=i?0%FJRmjw7cW3Ce6MGOo0g21t(ETdWJeZ zM^c1j1ds@B;W~1k4~s4ps^eekdLo&(pr?w1-1_>lE7gX@YfhV(@(PJ`CqDJ$>KL0-5Zm#b1!#_-w10CZ?3$FW#O@VTiRC&U-~~GG?BwrhIR*<}{6sIKL)8BLJ+a8ydaB$~W96XUiee+bqB_-cE{swnt8ePvXeUC=FF+#QM)TA)aAcPQ>Ht_|+)uEnLe zJB8vd!L2}XEiQ!=4N?dYNN(Qm-tYcQvR2l!k~3%a%-(zE;5@?H^!o7#2Z1)ud&r5R zMx(hNZZ5NS5keniI~SN1+!D;vZqT=n05W<4AI30=g(I9x(aX58DMTm{;qqV-*;T`8XZEl&tn z?O@#JTpnQ#4onFmG1)5gWMugkst;r_iMm0dLi4J)f@RC3`r^|Akhr7XL(`Y{F{ zv_(nzHfDS+*opGw4x$&wD_@If^~s~5IAt9oeb0b_+9X&}!kfGx0@k*?o{NCUjf$ib z1`ZxBp+6L&vAs0ovjAn~;`a&(uNCb0N~ZRz292mgy`)vZ5)4RpzX?+8l~o))HiRd# z|9CMS1!^oP3-9o0w&!N&mrsfmXD%%pa7$r-mW5%DX5gOw0ItEN`Y=xB;$0GOH9yT19N88 zR!=|Ki-=*jDm8A!I4>uV!-11xLd?qf%nF0||BSRvSl`d`2hhc4fIZla5Zv-&?=OVVz+eA)G>3fVBnhUO$~SNQGytf!nF zQsB~uj0(!@NhPb!WF&nUr4aX4P(7=9F-V^H)LF#&K7J zW~#Vt>S)$7R5p~Z-5j1zo?=Ffq9?%8q8*r;8+uOff?rwRTv%Wu@+ga0N@vd~2{_RGs^cdvU%4{sV=N&+^$8zL89;eV}z?!^h3ULl>7?5TF`B}e8+h`0IM^_?Qah>v=tcRMS17f?_s z6hkphhA2@Gf>V`8hS2=l8w&VMo=mQ-5L#6E&pD~Ambw3vwb&TdnK1O2(0 z3PPz?#%lPPta8x+WOwmXy|tW15pgBh=1Y4v1Z^o>a^eYoaKBb z=#BB4Hgnx(I$kW>pv_6@LYpQ+LXM+{!++sf?EVOcOuKKkJ_@%Twro}&A(oVU*P1Nt z1tZUP5wk%L7BVluL5wKM8)&_`*EHVyOVzz8KuoxT8uuFe85e^hkfj-fSDk*yD8s0z-^Wo1PZb}j4Ax$q?`@~db?IlfU5cS{gkCXv zl`%uU+fC10Ib~pvsN(H3`}(i)K^*7`$&c4-y>P z-ws34FOS&N=lm0Y#!}KPrN}RcTwHfwN$b2jN?Mfs0%hAx1ve6nXXsU+XqX8dIeMV; z&@Mgyqco0>{A4i+)v)(fF@K|;1c-+KdKJ@tiBy5o-d2S+zZ5`}K`hSihbh6;&tV5) zwz)5Hbx9(n1j*Yix&icO$NU0Ws{3liN5MT6NnX$HxQ{*4`$}u}w4MzQIfq9}A}PYt zk2Rz)k)1ane;qo>qok$#H(ZMXq8>rOHw^=%#cTv`(*xM*y3?75Gw0lU#y!zxrbs}F zY`Xp`zuKrQm3LE2U{n65UKhr~_fyJGQGgvj2iM3U77YDDoqH-bUdp)F2JcWCkJH{& zj^gg1E=1o~{2NtOsh;JMglG4|Ry1MRn2A4`rHBr&RES!TF~R7B=UbRqysGzJ2gI7& z`O_FzpM+z9&9rydeDDwx@YUiIrnJuEVCa|qNcf}?lj8|1i=BNzs?E6-!XLg{{Ku8b z1YUlXVgcESg#E>h6efq^6w05I7E!CPHDPP=FGcI`aXw#HDwzYl-sCJ9C@7#H7UNz+ zNaJDwC?&!>Zn_8QE#AM6{GHjkLAe;3kZL7 zOx}hsv~s=YqQIeTnn&++9uX3wr)+%vs`z6=?RRniw|licMTAN3>_VnVi>rtp?e2RIC!>#0l-BF?OPCL8CX&6 ze1X2|We*031Dq#O?98=}&$%{(nz>w{-wvt$OPv*+;^?*}XjjwaLT~Ury$amymlB6i z1g?0D^j>h{RN7CZQQW1E<8pi&%I+20;Sj=|y?L2I@4P57-`&+=opkN=QC%0Ok$INy~^8 z;TafOC+yn|&_tn~3g{S0Q4b?ivhK5u6l3L+exfYeUr$81CG;(6Rgh)&C??H>(hQMT zAJs5rpVl!j;2~b+ELBzQ`@t0~ybzb!$57hZF-(Pf{6=PcDuOjUaK{AjBm82r?rH$7H& zdpVMo{9I%YOYWF-OKrMs5|Yy=Y7RMm*mEK;8ukXFjh@e%v!O?xQ)+=k^dfa4Qu!UZ zDMEcWetIGj$=;w<)7ffJ{MVqOh^&$S)dJMH9Gl@Q&gRdsB_FKc2;Mw3yG$kg836TH zLp(wR1{S+#GKC&{5c~|YO;I&i(TNYHF+F(bue2F{2oW|b7bE|xo04s!P2BkRxAS%0 zsmTImCWW?G(g0g8`a?MJ)zVo;#U>C8bGho5e?hu9V3G);s8KAoOwIhCB4- zht!Mh?0ysh+g|Ws0RhT)^Z>W@VaqxN>I6RQ>g3&1uX_(H*W~=;QSixBH59P6b@S-g zP5Efft`$wN+sGbQn_0Q~QwOU%D@&GI>}UTbJn-Kdg;MI&YbIE%BfKGqV;Vgn4ADW2{kOyrNWI1<+?LM_jp8d}V~kY6ih-B;VrCYSxLO43*pKo%$?uWvFERs=LsnE~*T+6lRzqcUOPw@4wTZDIf=GX1| zj;<%~TCu2mXaj5u0%{m~4*m*R+7)@$-1l#Q|ATm5ht{TmmS?~-Ly#>W^>saBaGYNi zwNmn6Jez(%khnEE=A8$3*<}ij-dVa%jm6`A_*I>!36~xsFcAwE{u8FDkafBL$?jaU zW9vS-2bvW!M$2PQ#@Z-~;UjEg&09_MuLM6Ue9mH|8!Y;4c)f2qou5FFxca5w$fLW8 zT@_K|WT+szu1K`W>V4A6V<~39QN!u{84)8^vpT_o&p6|IA755pI4gCYPcJbTnO%i! zoocDiO8DM@=`Pu1r$%i4@)+zmgS8QEG$WV%m>(+`alW?$-zmJGNImA z{UKfDI`N+kBd^0zgsjy++o{wL$s$;5=_QDl&))4cMbNrb)K}=6J|0Gfg0XeNqya~& zUrGRs{;n>>zLTI37z%&`A-LmYHe%0G7*g*Yp`0#bt-+pIgK~}=aBYevjtD1%!Nb8b`iyWBeL!t0PYJs!QosG>OImo(7wrcFTpUv^hZ%%XC%6?feZjdp z2!YLU1EcdtdWa(?iRn1EzhwjO&fH01Ro**KJH~U!V+N$s!oJDAzMZr5OE?oKNb=&; z48WDKn80ySSAK8CHZ8Y&U&bf6UBneVS`c$$`C64d8)boS@5H0vM?mnE`rzp3>gtoe z_M=QPPm6uCU3%rOkk2SP~ZstM&Ga?n3B2b~KwarQSUjIbb2?AeujUY~+GGL+#D&#hUx4 zi;jXPU^<05lH(~u-yEKGXt9&GD{ouu2bQLT$}YOykB<&)(nfe+g^KpmDqi<1Z>UaJ zc;+SJBwHrVc|QM|G3EFcI-bpB>grD;VrHtNcjX844E@yx$eH-P6*L6M(ebvbBeBdg z);xIgeBE_RY*8S!agr1kbg8ojw0p^B80IS;5%r?E zRQO02CxuN2c}sN)>h}GFJaupA5rsYwZ@-!upq`9TAMJRkFbCtAro=;^%@K@x+YikbD6l zU5fOkMNayT_h#xg=bKpydLJe|+SV32R(*EBjqh~PBGgTJZi9Uhyhxt1M8CjJO;Iu{o{U)lU+S ziTf^~xOBd6YtLY3(T<-k`}5*6qt0Xf`7^%}A2)Vw2csr&mEr8nK|JKgI~zld9|B9_ zfTd;r9_u`^p!Eh5sBBXm#Ds!`8nC`9Kmg^8`*iND%EXfC!uAa<+#>zxdr}0=cN=30 zDaoK}6fkWZ{;SS$ANP}?`W3X)IPvw8&Pyv3%q_M~NH<{{qz+^!mm!&iQ(bLLL996{ z__X-@<(1cVE<0v*%dY=uXPCzX_xSrSZ*@vCM*Tb~8ENH;&|Wn6pEjruCEy8{%)54~ z?3EPp>$7mJGG=GTE#Eh^!DYcY(vI7=9|XrEw91xZ{?2-!73vPJPnNZ3$dKF8BDum>jVvSrq>+zYn)q;6~vo^ zJfuo36M?6BaIYm_pPnqSTmg%DisIb$)!vLg@KljNp&=%32f504d5RLI$Na2tAu*D8 zl{q1p-V-)}azld_N=ILDXk;njr&i4WEt06GhG+rHzNg?<>+M=+y z?I~t-KU;rAqhC)cULWPA;2w;V!Q?f{- z$A-}r6j6o!eJRR(dW+;Cys2paJq^Y;H1E;;u#h_YU6zbE6U&N=C|aIO=c^8Nnu;@F zhZ(%HEkzfNOU$blPORKZF?ZjUk`qdl6S`0FRqQT1EHM|Zfjv5r80Ws%qrgz4`r{x; zR#D1`bgYN9r0sDa;Fs~utmFzQ%zu~v=rR*E)aXZcW${b#FoJbTS1FZAP zk}Nk@f87@7(U2=BI~#=l1}Ci-h~t*H=mEeJFTtW-P(j9Z-@jmm$w3#h1Mq_$C3sB~@D zrRwOkbE=4bzE&{foyNKBq#5LKQpmf8P6Q$z5A0&r-0*(q*YUIE;~@r(F;?qHUI_Wi(-gwJDD}pWa=O zg4xyMLBuL$15R}};!EebL`OPmWCq&`J}B#E-4zAvqDJKZPMmO1OzYD6T5R5T&^pG_ z%JEJ+-MZbEk@zg$bQ7@?BT)eHn~cG|lU88zn{;*@XK`$Rf>uc^{_&*Gn?1T0t47pd zXY&I*I!;qNF`Ub&O(QSI)HpzgE{pb5O~+dO{q#ChCWCUJVEhO2CE>W7^ay5=C|3`9 z^u1iqLkpXwwB%KBB$Nhq`cIy3k+UN0BY7I&Ns`}xM{$n&Z3i%#!0S3* zandm^z)TdMTqzG}<~-rstzt$ z0f2_mR}<{V=9I>}o0#|cos+f^xT>K>4P>Q2Nv9@4gP&xsX&cwgiWr}%Ko=0PaYw){ z$rDW{eFO9=B{OC7at(_f!c$iYGnqJZ{u+~L*@k-b#qZ!c$zF1L!gFQf#XbK;1oOqd z{H*km%IMt->>pHWxtP(YmYejeZYb@+GT645i>`bKdiI^*cD|E*jRL1=nAUG1Xzro8 zX>$BnE%@7I$;vi$WL9WdHWl1!Z9?cn5m$F1pO5x1)smWm4axBHj1mHtCLPfGxe$b zG<)~atcrOpuHApppr+PAWK-f<3jW?EKVhp3`=^ekJ?8q(aa&ThUTjR{=|IM-cKBYi z)Lktse&j^L?^NW}^c@o6A+D!VFxs;6jv4dU#|+6z%@bYGT(v07NW|p9KmIq#O`XT7 zyD^k|sNTo$^_GWX{y=&>Y2)dt4ZcZ_&QhGAjp8<2cFRWN*v-nayXFhjZx2TY@!q=a-wr@`ky8-E!l<_+qKFyAvWq+PSVVFS4z0{)0ltnPK8eWyIN zVs-kOVfNAT5p$3t;k}EHs*RycB~3gtDo(zQCKK6OtH?m#z)@{MOU-*h?=i%DqG{|w z5yQF-3{*tOM*Dhnxv)zXn+hW<>1hez^>)Y@PpgX@#ooQi{S*{(7vH<`MyM4hBnSvd z%ObEx#AiL$a)z$xFAK!e5_T-uncN(Qa^h&_22qFBTX+^u_Xa91v3 z{_lcN7Xi6+9<=1N{PYYl;_l{9nc}xyTNXd>P-{)Be!*v9S>jM+T<;Lb5A~dU0tdTA z_y%HTQ;1Iif`S6)7HoGuv5?JfN)Z!N(eoQph+<8Q9xq0tJJsVbuJhcab9oix-Jh=( z_JED$P9@k_WODZ01AK453$4V9`Q^gLhRVu63PXY-95*QsHE+?01Di4-xP3T7pbg(w zE)nCD;0G*UD*)|wnH#^#nYS?KrSuzlhQYPsJ&o(ly{FxPkGl~4!$Qh~;iu2z>4BE! z*4y4!d%It>F^S^{y1x=A|3#r;5e}Cw?+Zg!ngD+d z< z`~r)7HKDQbrspc8Fj%tRg1tx+MtQp{g%!Fga&EInUv8W=YP?*%G&<|?vl2%qedV~4Le#%2BH?CZsF~tV`jq?1IFkj2NBm*8C9hKdhjO$2}?LhC#@A}b^tiDD>0bl|$tv~JW z)CL}1EUF*=@2&Y{oRbJT`(nm{8IWSnX)nC~@rg8__n!cAQ8aeje1uaURxT=9(<@FS zwx1r2=q1}Qje{FX915IU^9Wr%2&YQq?JEn;MM}tzzL+pUOu!)RGQO+V-cKP;zsNw z{2k^?2d`uxLs@u=CMpn*QXpD}MHVX7!7Sg*LlZ;)D*w6)Rr#OO{eo9LOUhtD@S6`k z)qQ6~QlM>e6W%t+JbUD%wAphI(ptKL_?Km@`N2Yt4b^;lD|9SqyWI4X>1fWl1e16xuXmm1rY64l7<`!)&23djF9DNdR>#lCn6|zBwfDbTB0jO%7|zx<*tZ{7 zACXM)O|$}$7&M7PJR!+U%G$CwmWRWa_J_l`A)fv-hauBFwXBPY`I8yrl=sf6RkW>z z(^2^+8B=Y`&Z_Jr$NnK_P^GV*D=}Kb_&g=YSu2r5cLho%1!^fNIeEEA28Re>N*Q3{PAulWVtjVs@ zxWielfjE9y1Vin!EECC&8i;vOfop!%&l77b`-to}Vhf5>-=rO0tx~c}-=Ro#(Pgrj z4x{O>H_tLje_z}Ckp(&zZUmL9SI-SkV3ujYMT?L?qIi zns|c_jS*%4882Wc0^dJzDXU2vjj(ABe;~0Pb3Y0lW$rHy_t%9iY>KcRUX9O6$`k?G zCctQZ+8;sx?|i(^iO?GSnxY9Cy7nIWR6>Gw)odtD)7)t4g*Je20Gf5|kTP6Fy~>bb z$EL5DtgON-wd9O5>d%`S07hwOaMTmHlykO>@w{v>7$?p9%;T06KMf;eG{jyiq9aLV zPX}{hVusrxTOCk_7LbRf)yR{g)I^O>)duHuti*PrNGnB=N+zOcv2Co*7;ZRnjJm3o z-q!9NC_k#PB_n;+JQ<0?xi&GiV)cq5e}xx;Krl~;kQ~@2Ek2#Nnc!g@wD=Te#_NjK z2#chd;;`=9ZE>WzVLmG3=;nC$+!SEdy~%q`cgR=6`Yj{>1UJ0I!T7D=$8rIwx}t(s z>CC3u`??~^*6u?c6T)&jZ?6D{XZH8zDRbr6ej9mudbDL3mARmS^f2Pai zVpOej7wgole5pX*Cl&;r=v)%XOdyeOAihV~uXp&=`E*Xu-nTE9;s&rWV-F#)$$mSF z?86vAFP>VcXxQ1>GX+Vo5Eqb8pXJ(t)VoZJ?2`FQ%%YkkqcuFlrX z{g>^J?a4rZg)2~3>gX8_$9y#>@>bMs0WVeSRVb=3M&~U*2bzCv!D+AWzcq*L(qM}1 z$uV0W4eUbo0Ya~atZ4V@=Zzk>C~}Z4lDKXPU|t}30iPYRQ4D-HTyy;XWce|Zu+Sg% zri4?vB4eISTYiFd!^j9=7nwSgJsIs4g3V!P*;gW4Qcr?qr4!I<;Ja&-fW40s{r6{n zN$!FmM(z=*d9Xl_=KnwB*%3C)=3Yiy`e8sI9eX_hN-J%brfg@2_MWTuhyvW_6&u-tc3Bgix$7nQjum#?Z$z> z;|>4G^1bASp{W-xyPy;$Dtz`&<8^I&fX`Fo!4e!z9usT7Pj<@f3q3Q&P!)q8@5&e9 zWd9PAkXg3SugrwMX^PB!O4N2qj|>XkA0xY`{I&rX$(gTzloyBA?dUoeQFj*Ka>ZPH zqALT+d8Z6LSE~RHh0#XlUMB#~_6vGaDx05Dk~X!9=fa)8!vIH5bF%r>gpeZ`%@IHle1Rw{zLo@m>Wb}nxA72h7 zROj4arz^&_9sBm33Rpu32!2r`fHZWdNjHgH?USqopghe8KequXx@Pa|Z~PY; zG7#^X%|~?mJb&XS35v0dmyUf0UIm&n%~diTrNo6l?3TJ>p{R{OhB7riNhf_~ow7u$ z6+!;h56Xp*=UfEVAVHMjHs7eG9a7q}cxRL<->MU5<&(AkSI0vT!Fg;bll8X3c{sM- zljD2TAIsj->&O7@3^lK*3!5=(Nk&Z)Iv-=uKdnxuz9jf(9t0dZZYi)xv~)FMP9`cm zLQh)V0t?LoWqZECyiFOgn*SpD0cM)EPMa~_x;)Cf)Nj^11rW(DWhL1^tMw-iy|0;v zsfPas_~H08W>;bd@fMw+Bl|@eM%%|;w{YbtFW#f-nRM0q$#0}AEMTGcM)kS2BHmfL z0<-2a4a8Y@$qDe=_?6J-uY|)QHzPdz!#^fzXv^7wt-zjktEt>0L$H`Mww9jON}d{) z>+NNZQ(dLNEX_b`vC!q{2Ff_mCuMf~O~R}>i?;Xg#Y?mNpdpORm-Cy*-!S9uCho>* z?MC%zbXW=jQg84}pQ92umW|}f#8I~k@5PevBYhlcL`l%dg8y+bg_pEbhYto&mrnx< zmF1bxF@%!iNSDV0dX}I}gc^>r#(^d3eIHZrZ79agV}svbYh%_yRNb^P$-b`b(pCFv z?C0ahT(e`GJ?299EDEcyHoHIc{d=L;g%mTrux&o%hd~U)LeC9uhi^h(^Kb3S2Xi}v zG?CX-?PmuXz^8`YlGh>3=peA1H42;pv#tb-ux;d35Q-ggEjn>EIzwA&xUgt8?vz&{fcx*s=j#^S9;R6*F-6T0&jd`+gI+k;1O(| zkme|i<}3`9ih`41o;bZM#WClqESr?t#C@i~RNv}ugLNZ_PzvI$5;rjP5{6D!gpVYb#J=$Bl-)b#@JBplgQd@gmO zuPPOc9WUM0pzU$Py_BRaRTHlc&M|KZVS1<=xa7}}0C~(CB(rx|ijkN~3`Zo5uqzXK z#yw0>JL9!-*qW|=>N#@nJSt<&0ipj0NEzbssZsf``ny!WXWymOMvv6Mk!=w-;b>>G z*^$G0^Qvj{!(_{B#-3&GwdE@9scFf@actlx07@44381JE&(d|KVl)7QZX8?T!D6!cYc)r=vm<tJPD7Zf1x z`iVmg`0l03PL{M#p`*jhmQZG4rhzDQZp;4k6r{6)!ajUIQw5uFX%>8xztmhpIJwd@ z{I%UWOfPXAI_glxJ zZ+S|LnKc5L($Y;>+E#`6&>0JrrTgA}i)Whb+U3f%nZ+%0ps8y|v>etu@p4t>S4I5L zhM1>Qm2L#f)AOEI8d|T!Mss`}XR+t@;+q6HKVl-V8yXAQm7hWP>b^VRweFb&y)rVp zHszW4*8AO}t2DG>YKkcllnyfnOV00~L`hKg_gnljLv24%4ZonPW4L`_92z&zsPMd@ zC=V1@)9LbwO6_M`2rixq3`|$CsP}8geY4dS^}NCN6z3%fn}TES&uQ1i@(ju{%kS;o zmf+NUrPpYjWOxQ*N|WNeH4l|&f#)3a{I3?^`hh>jT+aGBM7Yg-+&^}z=$poVFF+V% z@LH~In#btKq-&N9y7&~cLtl<=W(22STFcH5`IyjG(co8~RA1@&M6-k|)fo!le`cdh zUJ)B#yt7Zy^@iL+2IgR`*}n|^d>u^hCmt$2U*B)p9+l0!qeUuK*>5v<6aGhr#s`yo zUD{`XPZ)*5=?CSQ_(!d%iG^Fb@H!>~oG!$gTF z*?4M3tBAjKNv-M1V7?Mw-uEo_-bad6Lz;b*pD-ww!>+}a$u?)Tg>ds(7SLN*R+6qd z*e{gTU-o+w`nUJbkB$HSHC6;S?TbB)`?_VzC}Fe!@EUca%(}Y#%XA9p4PUEXWF!0L z<{Ah#W7=)*+imuN`j0ngX3{vke>TK-paI+!Y<89gWnF)elw+hg3^>?5mN$Jl6s zG$InP@Av2R9-1kdPKpL7fGRUhd*GuwGUW$!Y1&hszTMFGhcg52lgu*)t7NU>w#I8F znIx;9#|}ec1+zYh-1$ymioSKoJ2p|THp$jSpiim#BBhElB^?(VoTgOPF+{`5M?F$7 zMnwqGOz^^OQF*Yu`w6C(WB}5KBUU@)PwT5KW_)7bQ9IspT>}}1r%VxP{bpt=Q?DAC4Xsr|DS zDdoxg{5d?@H=jj?Mcir4=`?=gg*=ye%ejzE#;r(L-Lx%Ir;@Uu2Z}(Nwaocd$3g_1 z2vUsPf3m8k@KHAJdd@ZUU~VTUoG_v~U~bzdiHB3)dXicFH@`p%^mT&AaPM7uk8^=k zIP<=~tEBHSIf_{(pKE&Q_l=l1@F4=P=cpOzkrmI7FOOq|92*XK3;@NfEr{n^^{EGPN_P*PjNU(HKZBd5i!|0Af5!7!}>;j zm|?T`NN6S=BL%&{+aL5ir78#rZLIIi9yg zy2mWucnjhaS5B;aZRCeg;W<^i7|NK3PI%B?Q~DUdUC|P(2F$84BfT9=4WgXm>3D@Z zB=!l^3_dal23#r+$Luc177a;_B}V^r3l=85e2`q;6p2rHP(=#9LZ8n}S>e2{tNc~= zaZB>zyvOKnd*B}YEJDaXiPrTEHj7+xZG?njDgDocy5I{!^($56f%2duVw0kZZ_-1U z`t%SKC;t{j$3As2%+3!28b0R@PyEDff_Hbkm!>%~^jO>o%e@%5vV`m4r`M-C@t??O z5%R&`^^#^@RA)QEKp(QoAR(B*W*7tDNU=b7N4I?!ln$mmyCOLk3O6 zHHc9EB?z#)F`tLqR1mJJ(IMs~wQRQa7Ps>~Z8`nB-*c2R!SB$neo^>XjC8s|iYeQc z5y495YwYq?cP=rTmCevEK#8wo20^mZJ(4;*dgcVaSr&xHD(AzvZk~S1Un2P4MGge?PFw;Xjn^ zK2Z6SmwHL(EnB&1 zEYl8(mBu@>6csu*2&VabLPE!!6g}`Ti0=ICzarE5OakP(Mp{FH!pio0g-ju<*A8n# zTc%YUG`ABXQ>t_6OGE9vewO8I+Is&M@AFx~T`=4)Ha%TAPn(&|mxjW0FLnj-f>)I< z1b!9%!$ZaK*AQcYH#@kMx%wU-Q%F6jBg@V4hQIT30(A&&t*0(nQ@APifs4mK=7va-92WOJqhH&WZ$yJzEEVL#I*5n3OnI+#Cnh+s}ixamTfQAlW}b$R_H*#17ufrikz(EpQ!S3ax=ta*AiUVh+G*O+q;eBObTh#q_`;ALz?BH5XdA zm8<=tJU3nk_{@e+rGD6>B>YHgHUYG_V&3F|qaszi9JILwHe%tMFd;jCVazn_gct>0 zB}7J#S>F(ZS@f|8|J_yrzhWqPV~ID*b$)bWm7Dz$S$LpXIu{%CX8mkM#EF#{Sz~3+ zA+n(pZmBPqI>-tssh1ZLYM$8q;PKXGC$bQ!3rny|f%s;$11A4C-UE2GgQwj5^g99x z1x|y5H!fI^?V@zu+pqPd&V;y+MwXOkOYR~r4HA*EPcN%i{X|}bE(}c{(7pR76prK? z`h3_=n+r?1{DxTtGZ<|m$fS2XT+gAKmRb$j=uO$Da&%-ZOz%k^3P?oI;z3T5y|t# zDntdgGfTb$?Pd6!4*5~(s;gx~Wu3<}?XjVEd$b}Tv_!RG^j{J&%;KBzzy>RU|C9R% zWpSQ9|?_9r>3yY}1d=jVV;XHoQl?+>cGBx=^ z$qwi55$wTBA5=XN>9V6fw~7iub|KpQUEg0Rk2kvPT)o^7p>8>6Au_q0GXew%gYOmcc;~REn2!+jFw)vl)Mr!6W&{hRRB;o6gR7#1o7LIsZ`)FmB;z=; zZ4;fIzIhREX2_I^Na+bvz`M;>7N$uK&*`m zMQpKL*kMb0-fn*_O4uUbNXbNq%VQGGUYk$!`m5(kzeudDyXWB#t2 z-k^17wC!Lqu0r8%N%K$SVj1sPh!>K2?>sHsi16Bwe(<)KKOdC8RE%WG1s-XsqyW#d zFAnb?4B<}&P{3UB!iN`e9+PUfpIW`uZhC5wxCvZV736B+091VL^>)J zxkI1$9a^LJFZ@yOpl$IXP!&V?iWk;)f;hZwxP68x1v_b!xrw+-x~Fq*~#t$ zL0!q4;*stG4nD#p>?m;6#MQ9-L)%zQ~ z_k)kXX!04H6_a(frQjNBYy!LG`9%;aj+Sv4(8|g+tvvZ-?LP_9u-aNB{ocNk{Y% zhm|y&smm8ECoJ^0s-J(#!eHWwcfGsWfPAo)>2(*x@^7}(D!6!aE~L9uGO4EeF6~K+ z=0#lYu26SPi<s!X27)pQh zaX~HOFt17K5+-h$D+=<=K0>HI(!D>IwhtM6_ma!CQd=yTxX21#xEK;#E~iNMZ+B z)=|i`EB$4Gekhy&fEiI#0iB4tLZhd#oFD=B#s@6Xf zM1UI;mCOi0io_A0Uc9RD^=2$cri!#C!w&Eea`zLOiKF_^wOf`nZgf`g zL^YFtk|?f@G|fLVKYsP;LManxte7yRfU`b z*FlZu#`U&Nc;aGrE=juE6v6`j&h`54?$tNL0ZGaypZ1JHIToLmLeUE^fOlqGOQY`3 zve@Z+L%SfpoqowXI#^Q2+^4kus`5Z5W`j$%iJSRZ5C4 z^G3cK^aXUXzT^kbAN|Ne0Ki+;{gs#crE^S6dEis&5yn40)2*TeyT4T@kds%o82V2ynUqlrY;`1l^F zTmbeOK_(tRfd4*N6p(l?rqGDj9$NQnu2gw098Out%imGmpQP&st2wy>*rZ&)bn~y! zkK`AqLSO|qQrR|QZ`G(tP)5@YR|2#DA*SCAu$I5@Ijk`hUB^%5qvjz+_@KD3+$+qd z5F`$atBq(tnOnSHHf5z7~qx!Xsaa5h@)U- zRo*r-hev_IQf_J4L6QT1d{cZYRIc-(#)<7gg2t(pZ0ctkt0atUOBvpu)0Cr$)8{}G z2p!k;;~3e+aoi8)O5XZb-OiXhmq=+hZDI8=$Qc=DUhhSoxbhaI71f=Mf+PQ~DFRY2 zBSs}DXCd-2LWre*gjOPk2RpvOaK>dvMfo!jq@QlA%1_vXO>`z}D>x&kqmb z{(91RX)1_Pe8RRB$H#fe|7$Gjl2JP~@U*Y=s3)hT0TLwQi~F)|Gq&Yui(nViDiBop zb6dzk>mxb>M2GZ_^=EGd!Sf~>U9d1Wa?ypW3|J>BdcGoSU=jk)-c5~lbuOm+j(+F zM^VfZRU|c6$ohE_$S~p4H;`mY0|@$YImTnPND&&Bq8d`G$AW)PHbDf5_SK~jIp_wZ zi$ta^Zg$az?c7lA#MM-H+s6X9K_CylXm-CpRuCu=WFT?Hb9p*&BAYJpOuR#IVo&6w9PS0sX8p@&i~RN1~l;yse9-*3tN=R z#QWuUl{639twUOPk7|LY1Q$=nrf|%Ny^nN_#<-;mY z_P3ALj|fFCoe!NxxaSfRP$zdV%~j?N5`b_Dy@`nZpd~PJq53?zD%&ZNTa`E>*4X#3 z?r4jrkC^;TodC)Yp5-NBhK7G%8yg7rM4)iFyYerodk+L#vT-b!J7pC+nN$$0F`aYy7i;1nA2wDxixw>F^#ec^Q%lRH_zWt3kx zAE@4tiZOw$zi!mzD3GlJI6C}=fe5AVnyYFxBGo|Mza2vtddxVR%uvot!yMbb zNi*s!ayPq)J$hqUMu>P0CL#R)rcJgycK#n35w~LAhQaF5T%g@1r9^ehy>}UKf`s zs!GNn8qVjTt(O{)neX_n<_cWp!3G+Dc_!o$&fkq5i4 zj|od6fjje1&uiZJd-wA>`6Vz^@I}oEuNO62RP?^2C+U!fi8Dmw$#bNbbzi zDXD;OE|_xs*zbD^XckO3`~ziPfWYnhF`kAi`_4@pA~ zp8;)u4e}@KmK*Rix1!9;k~}i-==QH=yVrSw+^{L%-97hvVo;ub-B9CDs`)F6HGXB+ z)$@2vvzPm3kO2+Ts__5Nbk$K!{%?CU0s_(~DJ@+}rw9m&Fpv(VySqUo1?dK9>F%zL z9w8vz9Rnsg2JiFzo%5cv!#{gA_Q&&i?)$#tzT9KcR*(FLAAsANC57lo+0EHy;@NWNj`Mmo>%IKO0DjWl-EO}0M`~a#V`&`E z^yzi`)Ar(b%ENn<+8r6H;(JyAV-6ge2Jb@Ex+-1`+yEWvkRU)%TwnDH1f0zt>!UQUo6!NG4=GH+h%_cihG-c zYbh|f82YlXSHxB0NGVBGjOY;$nxqY}CS@M6-fgkM?u!%6o)3^P=q=PlPZpl|Chrya zL?)w!{Ru_MMXpl>_D^0TdEE>3=S$TSz4HDNeq5L7k*4<9(Sdi!$I15E*J_QVhx_nu z(*_L*3VHY3P)o=~wmff6bncz~zk~F=tM5R%mo&-;dXXD)qM6C7yf=vsP5V>ZHgT}U zpTTlZI8|b5+d|YonIUqyl9dr1(3N&78gAr&a6jOFZCzoCdclc-LubElcYmu;9dN!( zZ9@67D^>a|UA9$n_c%2B^EFeBEeUQ7Hebqt-#Pw1S%XBEld3dB2vd(GLq=R~cD&Sx z<71yB68r@8E(q^4;mWZH!9o2Z!^u3dp`4ZCHx0hR+=sd#O@>CNL+U(5xnJQl5-}lx z%uo3rSt8M@VN2{Y(P4*vt!H# zvIOV3>CNEI({dlRGLq+}I%X^yxOB}g1kwSOJUuPLMtF&05{cjIZa{+wknHu#*64t! zQesrO=%pHI)dfPL?~pJ@u*TQQjrxNufTVO6-8jmeH+xld?&CYX??7Nybjbuj;3g1qw=oiIa&b13#Mfx zRuc8D{yLVdyR!wN6WW-Pxq$v-=Oypd;wRgFRKTcOCflnO4ci|-Kcc#=v#Y~KzxGMM zycgR)MYU>hU5oRKCPaS?nfv}y`L;&awb?x+`z=RC+gI?s>cc{eS)%w?8Jc4xDXfuI zggXdR5i{Myn3n}2!Wa}+yw~>-JVQcUMFUwFZhmN3UZ+{Y()VWhCN1D>gLrM#g^6A@ zqk2|`qL}XaiN9y1s~pKc7k(LP@P3U*5yQ%qACg<4;@MaIO=gi8s2OMspB!V6Z$#7p zGDHHw>ns;KK!4wo=#ynC%Q%#LtjZSh35H&Zxz)({{HOt^u)1GQW44~>iFWIlpJF2~go}oB8gfxqd z9e(FR)7rsacT|>igYOf&snmC8{sLAT%Pg-VHrCH#0Ux^7S{x*b(E&{f!o7H#rb7F4oK5 zv~c60{om5WL=0B?*CPJK+Ut(0m>pLb6xbcPA)1zx2gYx4G zOFtYt>rFTilZbdZei|y-@Vc4jEw1hh*zHX8va^G44WT0qDWea12mYSg?R1 zBJg41*uy}rn4ROlgQ2$6cpa5HMOBhw^gHI|@Q`MoJ3kOi@IoN@Q{z)d3;YXa*-g#4 z67N&3Lr&tFvR5NQTV?Fl`r|-HNa5YfJnr?^p|nwa>LK7#DZOaf{y&{SS=P%iX7U8j zayFZpI@>HEG4&^a17_m$$lgr9x+37=S|Tj-r4e zOhDOt8SW*SJ)<{nu72SmhJz1=!%Q-ElnRfo&HB3R0cF33y-GfFsYaD!t02+uMvK|w zVRJciz0)@;0z2RE;BzW^M{*cVke}t>ymRU+ZJXT3gkaXTpWMj* zm9mYIu6C-yBt<((Mkgc0E=e;ft-zQ38$v3CE6ZwD(p3FJ;XT{X?+>^Kdr}Z$^`0mA zT*+6pIfbMyLXdP?tE0mOZ}%Wb`<+#=CT z)?}qm$njxtY5!z{SLEJx;xWYUg})y0WME)|wzTld*X_F)vN~Us2r~?W=l$NM@oD$b zA6D#N{I4^@MO3o1!#^}#Y57X^4x4l#c&v7{O{$5@*aCH!GWgt zZ$W-Q2X_JdiS55gkO&OLlrhlaQhwTA6$G3iZ0LTt2G%hCb!=U$_7+cfZC`TQI1(~4`8k^8nMtnElDKYAWbmQb?EN_~ z$rNV+Or0YIB|FFM-DZ@6dQGKm6)V`LGF}Pj3jt80;kCqx=l#`2%3IM6$x%TM`yawQ z6sQ%4{_ycAEv@)h!`mKx$EdJ+*frf91}VXWuA%8K0TG;F+~@L^(^FpLAp5*4DPmBe z^iyp)5k-F^L;9_8ObIXzArU4{#s_8Dnlgd_foz{neR1x&FfhRbC~H>M^Nw)Uthe6^ z)y&js-#x<+H1~Y^*VaMIO?D3&+rOct8QA%I^a*P=oI6%^o{l_p7kciVJHC+jV$cXk zcj7}kR&?Esvx}z1ixbRJvcGO}ce|I~B0D-c;DH-a-+NJ{(c?ntpEXLTRX(^ujXsWm zpI_cOhLFmpUTtp@NgIkRx+#~=*_$T!r`i(x4m57zS{al35h6;31)5SmR>EBOFY{asbxkTc~Xo>G1HJdfmsMV}Yu*ooVMp!yED4B6=oDxO_Ql zR|x%x%e^oVNzIQy*4>p~1JQ3~EdRd7TL>z|mdc=V=bDoRIKaGkrl<6#o8dX#nqGVZ z4Y9L`>G2IxG^dVOpf!W?LEH&;kZ(aT^QbpUprO@v9_BJ_EMfr?baujGQ19zsY}uu1x44zHN+^`@F~%#mfC8aS zHEVT_&jCJff~hO1@Kv6TUch1%U}w>tC-+YHbdchmg1Fp_nokr>lqgUlJ1X9FXwO$7 zGXXs3boQdN5A7$A(m0jg@`Efu$}%IdXS}>}(Xw;_J6&zZ#nrfLWNcaCxjo)YfTCgn ze`Teug?A)L!e`rtS~BJLrcX0{;F#R@GfzN2ULgK3S%yVB<=s!UH~jP_B_H0XQBY)`>SG4p+wf9Ymmo&>O` zC!VIacikls_l%53qHZJn9Sh-GG;JFOB5FloulmOa7E__O{aJ$DC`;25+JZo&6OAo` z=3ev z_TJO0)NXG`aJ-$@e)D2?U7=U`;~%f7z20i9(&L>dN1xaS?9K;$enMv}&UY{6NdzBN zzhN=q#8eTxB(PqwRl25gl_%?toUlzoIiu0QLQ`M0mTN{uNR$rU@7GqsprS z>EBlvOIh%30{Skhl%IE|e3(lh#Kly&xKI>dE_+opyYZv9L~2tiZxvh5 zv-PG8PP4?CpZU)S$)fe7vQg^vKGt1J zy-+!I?WW7jTJC6}BJ11RmVNX{4FlEptJNt0%d5fqvEWzJ^V#>=<0hcw_|0Df?~#;S8JxEx1k z8>`V?T~4eQl(KXJ7!|lKLbN*fC@J3n#U~bKfxl{>%B*odKFbME2UmLas}AfP3xnqT z2kSYOEIK8-4`14QzXE`wSF;g6KlPCf3^30HA(Vr{42l3mqRN_mCdPjW*zs^PJFcIy z#EpJ)xcQ$hEz)d8(}04d+y7qXC5xjazRXXSkhew0WwZeOxF+FeqER&VxMur0@cLP( za}F{L-!a_tH|0xs*kpGx(g3rPP#rC+U%KM#3-i-DtOMFz_tk`s>%a;@{F+^pKzccP zU_+YNFaO8aA~d)Y*Gc#@^UqcCG5cupv2nBH<;$I&(?s5~@of^D%lItHJPB^(88EXY zxLXK65yH<}L*teCiN5eWW*)s}u0?I-2L9SPgX~q>Q{3y1UW~!$Humi(`6rf-x#Nz+ zGY5#yH^P4dr~lNFTu+&y6S!qpy{h$~hr*PuN~ zC%!*zUwq!e>uPJM{|VO zGE}588dUmp%cG*_^(Bm*kzynC5GD_1x_R=MPr%lYr=)Sc#H+DL5^4GZ+kMFr9C<56 z1^8y|VRbdt+7TVixz9d=(*KStXTBCAAa##iv}6!ljoZ4S$G?>z?u=qO(F3 zNUm2iE_8c2Fy6V&P@`jF;RXw%+YTz$|EcZ`zm8PFfaM6om?b;Wq}YDQaZ9D>(Zi?EFF8g%l`pw`?_KxT|8Z0v0a)U`E#=Ju|>@{-iGy#qbL0P<5~v*X4duWE{DeO6CAJ60TTo!d8# zYS`l$s;bKS79MQC2Meu(I&tELKJjIqWtZWshYq5Kdy$7?b zuA}M8IWY9iKj{o>YsT;lm!$r#9jz5n>`*&#?$Wir<1(1ppYm!H_=MAh^S=J68XNEm z@I^(rOPYQrTNc8naF@@f)byTESjpm^DffYxD84?4bf5tc+rU6X)H0P3SIYgy1Fg5k z4IrNzbGdUik+fw;Wa+B&iH+`aIeLl*)OT4(W`cJ_nPRnswY`D0kCj*!E>b*<>>LEtD^?#;>jg^OW*|*wI_+2}}aZ>r0>8?SV3XqNpFVAH#CHenSvY|i{gu}JC(-n={ zdU>=h#ca%`G}pRjR&OjR!W~!k{2fmJI@P<7@L+^2Do6mzG1}h-h7bF&CnXweskn7_ zt^38^9Z6zcOunH~%%RlmMzxpt8KWa_#9!-H3390DM!^S!xl>X}jCS-`=LI>~M+)b? zxEUZmTW#s+hJ3iwDaCEUh6)Fw*IQ1%wY1lVG z?nAyrB&WG93G9HgBaQ`pGJC@-6}QFkkkT-GZThITQ6Z936zy54aB&-<{`K*N^9K%M z=BxNE*{x8`1QGqs6{S#8?E%3`p`Y-9IHX$JEMk%xa` z8o#gIz1!5b{!WgNq?hz?_T(b?lzc+cpsyLMR6bDIXQ}<0y8R57?8;#u+FrbS)_~>V z6d-u~aA#X6sKuebZOmKr?`fa;@Cd{rG6nx|fq z9Uef??XkYH35uYkJR-KENa$(@P1*W`!%x6rNASOGUGpv#n{h;GO-A~*qyuYp`zsD#@Zu!)j z)i9OYP+Xm8)7-^UBFIB&7n+Sk&n&ukd};~e9~H}%aef{_;yuG@wQ^kdeLwoErI>Fk zOwhv}7aG~Bc3oGGoBUe4is`X=Yf@M~=sNS;{=Nv2Uz;LgLev%WsC1_;iJivb=!t(y zWofS}8c_kx53st*xqBmemL;|Rd!|M2(YT~S%6a}d&tr2qcY*8jSn3|raz_64WTVc} z7S1+n0WA*jUmdxtul0NOGRavGb(>}9NcfWTuX=!<(4Bw!27v{P_2#1CT?G?>hHtQ= zOjk>g=a9NzB;iH>btX zT6l~2DfRNu<=0{0NwBn)PW%ej zvGR-~3u;Vw3=k4`RkJhc2|Q=2DR^5S){JE^`B`I{Yj!~QsFy80o}riVj~KZ7e$2gW za%F4k>js(S0^wW;3v@uSK4iHi6`6{~it{NVdoaUp~KjUP}E+d?8;HU4dleE6l zKTh-=fCzbSD8F#VHLikR!aB8OV>o`|M1vnTRaD)GekxamJC8>OiL;gxCwDMcWj;R| zy3{NtQ;Y8qsv6dirL#_ud$-M$kPB&~cYa(}--JhqYJ_m*-^&^l5mO+nq<$}PB0pX0 z+I8;+YMi-8dTMlNaS}2UC5I$($iPV2Q*t_3Od}S*b(>!^512MjUl^;n$C*WXi@iF@3l1rDm`R%C2qGMi+-(WDd$S~|>;{A#-IXPI#q8~8ma6~DDuF_04#`%r1E z?5|jAr!-X-JmU(I=`KI|smvLzw4JV@GbHxTofiwF@jg{^+Ern9-b7$76&<8EOq_@b zs$DKEuv|w4{4qHe1rZ~T9IF>i`^3}v`FvPpUT@B8_iy=7Ksw@Wi@jfcs}VYWFo(po z8cLa@(miv3`DL$QFPD(YRD_Tg*~HTC#GeWD z6GSDXpz<;R*%K$ULz1?ypZ@nX9TjQXt9lO3ZXRap-aAU!dNi1bi~d``#~-w_-HVM-h*g) zOq)uA{Vt$=qdu8vPIxm)W8xe`*9mi9 zWHHh7?_nV(+Un6++q!+Tc9TK!F&T~o4*!ss2!kvk8mNz3?^s`7sh348<#auq5UPbTG;u@7Id)z6)< zfItmw3A_o`c-|tfpSGRbFE|E$gjALEgu0YC5c<~)uK($h;5XnF}M1UhF=T5wM zFX`FED`=(AsVavXGeqdH8Eih_Q{3TP7$QGHT^Ar(+=%{4c*0$h1NM{g+`zPt?Bqfm z%A71(;G@WCxw%;AJP8^;`8V8x9_a^D?$+QyTkU_f)!CHoPNW9uykTlulFU0&j?26b zxOczA1nYwy#701gGLyCQF>RC&JfznX%6>sQs)VGUABhTsiQ3}RraG#Y4Cwuav7X zL3OIS;SD4z*pe*lP-KD>%0GY*q%>6pA9T^Mk32BNG%HJ4o1)2Z2^OVA!IeM^ zi3XEk5Cd!Jc;>J2n~>K$V`%l3P9J=JD&lwC8{|Z`_Kj+l!F#UYxlX zl{<)N6^A(Acb5HKe*Kqzy(i9UiIj{tzlg9~?Debm@TJM2!H)3N4LQ4DEv=a!Ok0z# zRAM}qKZrhx7&{4P*1xjkvUqB4RAZ}F0c;#lkbM)Y8RmJg*37MSiowEDp3w}IgvdAu z>}rL%r`Y>`)%|1E)8trMM-vW=7#0BZP;s+`5`S<*emB>uFS7wv?c$%t0!9H3DxEn& zx;E12viw&r8Tn>Q&ByI?ipX4Hnzo7DcTN&_I@mx*?6Q}pP0qWkQc-WW$h2m#T5}|p z0T@hY8{}cd;=BOC($CYkVoyVJ&3R^Z%fQ-sN0x0`w!X+}EVSUAG)7$0qx7 z9kgwh!ao}o*2s78R@WLCe#FZxB%ru>ab#JS9e?$wS#Bk+uPw(GHXfHGLAn=0k;ZZY zc2$2NSPc1bY-#@~3TC#9_G7C^BJcbo*(Nv&x3nU0%_t6rDHZT_?B*}%8?4CWZKVA$ zJ{69V1FsiPD_U+6ZL|Kt|p4ylUC z*(+Hb+7=!DMBgz*i-T?dip9{kk!n0Eqb}qUG)K+K1*_7dgDxCTa+1+OwX}+X(qjJ$fZq(p0MwVX(Z5kX!f8w?M1IzBaDL@hMhayl=1^987 zZ95U~xxcZIxwlxZUASGSg(fUHIfDrJa+lcA)~h{qP!S*hSIHl8YFumEUpRxF=*L7W zLcBkt_Y{=K5W*iCO&9*S8HV@rajJB61oT>t3iASczOrHs##4bF5ojhIsJvdh+zj-; z3;|33G^Z|a+xUCEoK0xCQA>+0Wn9Hkmo;nmou69kvlEq=haq&It2rEFeiXl~%)&^k zqKulTgem>J7L7vdgCln*?GXQ1dDPaVY0O4*>KmX6_x><=jX*3MmGsgtXj1|y-ctIN zw;srz>r7wAomG2aqpZE}=J+lefuFPYa31{D0V$({xm# zIncBN*{p<;z_aU}?SebooO{3XajV_#aT}?&U@rZ7*X4=TtH76XbQ5xI%hjzn&`bss zaiEi7b$la%U0*NHmx5_~{kvz$Dzu00>JBIVrX$RR$}r*|JnUSjDlQrjP`q~>h9S6; zcEZkHI)pw!ZybI~e8>$nn;|61ihT}^35|gESp1(np5F<^ZgWx0Q+Mpts zUfK^-q`eEozXjQ41kR$wMSLkm_L7;i8}|yBV3%LmDc+Ec?tr@}zy^ZM)swbu6pM}M zI{0Qkjo2E0F-p=G)o`818bCGPtB-@hBWAiD1}`pss>3dCjBF}DaD;(xzv?c63XnW3 z%L7e4V4|8Kd2omwmm-4lLt=&T-b~d=si&Dy$;H75vzRptUI*{C3X|&lnm8e=vFCwM zTRcl^nv9XXw^F)spDR>k->^zHe|_+pMeTUqtw@{QC^v$4Q_))>Z+!@dFN9Z>hw(3v zBS``fE4`hLtUkv!o)7K+3X^t@TA7YuVHK-d$6)w^$?NY;SYF{n{d&$>I#fUJIhv&X_JD3L`dq@XH@B~A^fNO?dbxY+s>M=wu4C06 zMQ3k%W9L3hgnT+L*p2g`M&v1^5QatQ4{fq-b)fiTuAE~TGjSg03BTb_;;*&=W@sHt zZ+_#cxc*z_?`o8`fm!RDn|>F4OZ_=>ghsfKPup0jmoa7RzoSFixJsiFq8DO%e*i)1 zqFK=`E&s0tumwR)xTDLyy3OHSyT+y>$WeTgPciEA3x%kqkLiZ^#zyf-H+hcRQ|5sc zK^`v%H#9?G+Nn3V7t~rg2xz+}sA7a#JDPm7>?(&!H=uYYj;L8vx3|jPTi6(j&8z=p ziXK>e_6$d=$8@*Zhrr=mpuVX=07U@GOX$;0u*DC`eS49dKJz%=)s>*;1{|#JH(oDP zMX+JiL$2};0@7|zEje$ARquFo{vsqWPw~&Oux(LkL`Iks-o4AQacmS4>aWa#rS{0} zq<)+H7K%Q{?ungyl)SM`F<-Se9y8wxA?fu}=;M>(sjv*|*`IL6`u>a9NrdlahBAZ_ zn{hRm-iP|~&066+ajV5Q(ML>GmDOL#odSbMX)VSKjX$)AD^qhVt4%6W6 z@{878Q-K4&y44$$hKMa0jG6G`7j4~uF<$OJ0)d*E1J}$(arQGeAMp1`nuh_&cO};9 z9}ivB>7T2ZG@-d9fAYW*r+9F8kp_|a=R(t0AK!9;=ZVzzf=}y}+6UJ4Ai@9Q!|u9- zvIfLR6w#$b-$^+LM(Be`AdOsKDgt;H;)Y1eqqz{K<_B;2!9)+!ctPF2J71ciyRNJk61<46T91OOSC;dFI*52hX+xqj-{nsh!EKkP8`6+6GH zF`ak+Co6WCT~uo8QIaxxo0U5HAC41_6hBo{ER)zf`Lb8}#}oe7le=HU=g4&pO!4qn zBG50p*~1t{$yFZZLFHwo_HXV2iEJkG{^*F~^gz`$YVlE=aA4gTS+!G_J` z!0pY;!^3frrK?+Wp!c~qTMEd88V?zxk}|GbXjBAZ3z!AJOWQm2cU{I)j8 z;3}ZS_OV>J=3YA8-g@T7*3}w7y}Se9)bYGLRV@zp%D6-)@l&FNhR`NS=fFi%z}kvV zY&5ZZ@AcP7g!TXuua;t4m4THx6R&`K2aKZ|C(cl3DiH!nclEPtUL$8gErgr@z)A2(LH*w!YVA?Vt{K! zv(}s^;>pw34ox~Y86m7^CF-|V`p#~-XfCFJ*PZkC`#9$4F<-8Mpzy9!k&@19j0qe} zE@MAgBMLQdulHX#di_*gc%7qCp-JdR{6z*~Ox$`p5e?P^2_!PY)w4Qi$!f(nr8^ny zPuUVuN85_lE0bYh3FC{tB%GoRlq1RLls}KN@wWSgC|f)B#R6|U-48af z2=}aXEpMYt*82-zm9~GLlin{^zxNt0v^D~(EPUA%_fC#U=w6NE`c#dLpb+SnR(%Aesfp&^(# zy1&hlkYs4X|48VA(D_ND1YXQP8;jZX@1Yz0xhQ~A#jcnf^D^@Zd4H1jr|v*O1wSQW z_&yK|WL17>`~o!e1kgi+lI07Bu4F*$+nxFKFKKRzqI;$;R*86B~?F z@8P>7ItNNj>W_|p9j;&pP6lN(OX_%*S}LVkI?=vj4%k8}(2(rkgy&fmLJ@zJF3zv5 zgW(M|us{TeqN*s%STkC2dXD{Yut9MaeEV)s z{7fq+l)pp##Wyg8gU(60q?;z(f>;hBQG zis-LB(JPbKy7F4jrE_qea1Io6ku(lEm%IP6e>1ZiyRl_OrXUEF)Sujv@{3u!+6(^jvhOcpenI`q^O9nU4g>&3#)the=Je4U_ns13+50JJm9#H|dOX!w!Z5B8 z#6AD2l~rM|*dVhZUyHT0;RA=YX}GckKRK~#RconCYr?y<8KT;^60{uPFi!ms0_j?%vB7PL`XfBb@*u7*(d=bh#~|8AFSS6$3FI9hUA zbe06^uQM9X1XGwAc2#M|nJ!e<+5O<&g5PwS1*&d%OY;8fNc0m68wGD2wYFB&;v5>; z#M}vEhZ@vn0@~jH*cQ;0@zh1HYzULjrfsXDJ^DN~$cS-GHvFUe9%3 zeAk@3;@U`r?s#ba$Trp=+JDfKXUpLd*pXGw1W=FT2TBeCZL2s%?Y}MTmPmn$^TGG! zXh%t}f-pcb?kWt9-X_dUo};dEJZNs5o6Xhd#$4##AE7Jqe>3>@MuBbEM7|Y>DA1lC z-~h52B6e~$c`>k8MtBE0g`PAg@=IbO`DMQ?sZ zztjUu=1Q!$`uWaXl<68`GDl}txV@=}RSGj+eyC6CMhtZ&RI_9l04S7Pw#gO?0^h29i>z#jDMueZ-&hwLZ&p0pvB+bLE{5Uv ztj~R|#lYPq1mxh$;qIL`KN^(o8f&sMz|K`x9c<7T^?9BeT-`= zt-GUb<_lmD=yGBW9TX!$1k$jd7P9S0)X=fUu7aazK)=vt)<{76@FFyshAWkIra(0g z)c`zDTE70OFCl0^3V#vKbu9<=<_tQ&jtN%fA5@+EQU_gwKO~z=Hyk796yM+wZd?;B z;jEs}r6se>=~hvwh`p&3r5&z|S&faE&`4z|aOD~u!N2j}!+0{WtEtf2z7-5~jqU53 zT}NVF2F4p%TNchh;iMwC270$20Pft$Gl=!zE4`JlP+waW}0q)*|c}F6b zk_tvO75*kt_{+3JG{{*0`MTdWuTO&M=^PH0dye{J%?)QXS;FTWT}A!@6sNPgFaXIK zuGLwq0ho~1Y|V+D&k=ilOc~eji!9h@%70r!9Tf+VU~?^J-(~e(ta*I;{&nQIq==X2 zUnFCK`B+p!_{@~==Rd;UDH~2>BO(2hh@Hh|TPLQ@dV`7GhH%ODt*KnX$wo5Py~{bM zY8p9tNtdNAGAMFRv`feE!s+4`uI78#foa;}KtTfy7T|T7OGqa;dWV-7W=iUNx+o7a zjBO5=)UFpriJbW$d)iSc$L_uNYDYDesPm!D-tVR-0^QvK1ii@oAO~*Z3xI?qP{n=l zoO3s4%_3e8yY_ko+Q=l0rD%{bx=TZQ@apl(57IUhJ(ETDZ41aQ(rwa(jp6(x2&caB ze`ICH&HvheBT#qDzt13B8jmFgn7)XX7r=QDQJ6halLYvhry*MV`j~h6K#S~Q>KzOf_D5IJ`nfwFH$ zuFGW{{IF?AD9qMWe-rgbjLceiGxzhz2m8jHzMzN>@IgBF4y?#|0P63}lMW>{PuTf5 zK4{E<`tcF%Mh1^w3%-NHKX*KUN_&|hnVQb7O8BzCG0MlxFk`q7Q@Pa&8p(y_BAS;I zZC8po@Y(*F^OeM;DG#02~9l$JuG&8q+>jIhU znVdh&79l?bc#^oOC_rFnpgO|JC$kMBU9&MF4>{;mV1SmWrq))4p#}Y+nO=L(DaKqd zsob<78kSY4juAv==<*Q&X^!c~t-w3?1|Z^5HQgiTmbt3>^UhDj)S}h)&Tt`bo!3w& zA(I%KPZ$3*gLE7XC?6G+Z+V|v01M(IYi&Eva)H}!mx_Q0ied6Mix8kODt@;}pL%z$ z0U<0-PAg;Hu7799SmqH}xkf9WD6!V;PpSJ>AExvGCC;Wr*DqQeo=j09sHAzy`EHH5 z`saIRR~S`A9#!8H47REa7L$EQFzmDK91!Rdha-@fO-`w>d%gL1x=Rie+v)9!BkEk^ z3RrG2y`saluaOY`!&WAJOAfaP0c|#|P7?Wv2;bL(V z^>1AKKUfgF8IQ7G(nyP;R0*7x7r!!tCI-ybC1SGg3N68>du=g1mANECG7M5!S^d^ka zj&^IvTe_2S&p(^OPo{S)E6m|tVCBfM^F&zV8`zIr`Sdt?K0PSk4dO`*!u$?Np&CJC zVh=K8^}@dKFoST)wM~8Pqrjy6N-rYON%32bZ{+Wm;lXRkY6q zmH1+PDBx;OECh^IGuRF<^;n1H8x?L_*>^O08Go>fS>LmU>9#tkeWBw{Q<37~4>`V0 z`XcunWcFemCAmhjX8oM`A<0eI$2@bhC5NQg&4@b-Vi{q{Q4kGzoiN8?M~N7MC)n6l z2(pUCy7L)x{4RlnJ{YNg;q}hjzRj2;TaDc*tZDj8GXQ5rzAn0%6cJ2G?yJ1WAqXgO zepd+YVnZ5FvmvbfN_JAoq>|xc=W4N}D0_3zz#iE%vEdsB%iB{XU;)$zFKHM9uarhc zu7K2I-$AN22zPYa8(-!vW`tuIyFXlC+PMH3;R7}T)zVS(Yv9);fH>X`_>%lq!S^34 zSs9vmix`?*_y3|KfO+eQ`3R2ojH(S_C(f!8oFiSE-8n?UI50Joo0aEEFw=9fK!wr% z?}=kCm9X>BWNgq9Ro^u!v@=^Xe%M8pf@B1a_k`-7gOq+W(zKuFV1wE|)S);a!i^@j zcmFnX&_Nv7`52#z$w*5dZgfBlK%gB55D_Jl@E6?)U4QO({ijEY zSs1UV#x}*a=LOP@z%d#4IsEI+Si2#84Jplu4-kXcT#x^pfW~#mvC6ov%JPr5>>C20 zjGBq&R?a&^p00iw*O)o?&OLkG;3|KkaXPR?og04&E3=>)B-_)qK8raPTP z-g-WD43EW0F;_4oGa3>$k{S5`3G_pLhr?GdoWw5r9bBaxS40)x#NO9|S;12S2gGDp6M@ys5^eu-8Q&5V zQ=+3~EbH$Gn$XC7>f}n;{avmYHfXi7#eq?h z@NE}sNDsQx&)LHXQkJdM9RL9-j~P4{w8dOw;B||aT#we z2F1H^f`7n4BQqcU+Azs03EEswCPk;iX{9)1n^*B=V4O0;`fSiC~2DtK`o1E8-m|DRYe+HILX1bN-Jsg(hXv)6_khDmnF@ro zIuGu;ef|8Iraed6;k2|a%lt1auwJJoW7f~Z`#<8}=e4Qfn~o*;1ahXcy)}-v#yW2A z_YH`bl9O(cv|j`Wkadz;ysN89Ph|lZcOz~;h7K)VgiS5_`mBq)R4OV@a#DOW&-OOF zy*X&>GT=xJy#_N`gE630_~`H-DOs;`yM@d$rx04uibw3$VEj74!Tvv*t~x5J?rRT7gMc*B0>aQp zH-aFI(%mWD9nuXVB@GhN-JL^+bT@-E3@|j`eZRGS|INB<=5kr$G_jaw&E1`)REJPVriw$b8RHUznFR%(>GSOqpSd$^Yepm zhP&(0)LceUw=0+rUKW)4*cGspyI$3=A!xr$t-4%9sg1sx5e!1;BR<|q!$~x|d%Q-w zeLnuFFePDKJD~lv*Fq(+Unqy)0jL{4oPy&VTu+X8|TmE44ORpwpO&LQOj zdiD8=WSFW(7xZ2mGvK1-^tW1Ky9-mb?qOD1eXk*ul%W!w?wYOj>=K1A%N475y#b~l zPzxBgEP>MzWvbu}Id5I)Lfi|JWG?gtuuTP)Bk;O1h+bT0dd7A@ix*U2k*MV$%EgX4c+fL2w1uq4qrYi^oVKt?Y98cxEGW zu%s_pBdLf{ml?95;YHfdaZkYQYGW_`zhQO*_tMd&-1zy{rNU~;F6yjsFtfmCR~FQb zMa}mmqyhvmZ_(utYV>98C!;PvH!p&w&(4FIseJs<#YTnz*L9~!b1sOPz)I!b5pmAB zH>?|IUk_{lTjki0;xZnwW8T%KbrHXXhD?+$@lzctnKN%M0HeJ6ZLU9Qg+V1`WT3%v zm_EaCes`ESeGm7ed&1{$UUL9T=5~z!YLYm6ND*XTdd>R#n&>W|Jpi@4wp74fR^4VIhd%5iXw56Xe#4%P4j1RncdFvf&H6bqDU{TZ#h{d@)a z6h~;Cyy=iXpZk8d0P_m%y* z9GJ|0_ZL;PDZwShI{n72Dq?L6MC5Xi87=dAr=9V!5s~j+JSc@DJD6}S)*!cO{_KUX zg=X}_Ht7^m6;c^MTcF8A;XpE86M$AcdD4d<>s7CFwbsFddS-01eh>MHnfHHtLcyo@qp5pY)VDJpM0@EQ%2NmOf?SNAd*>i3^ASR>3Zue#lK`nQy_6n_sfhC|M(5v zzweHcbER&=32?u5nJ)d6d`IsJi1Q0%XC5lY z*4v6KNk>~PwC7kx4t*bgxx@jKydU}t&If|)c2rsbv9=3X_^pEM2U)M*T%#IL`OXGF z$vYZ+J6nwmQdmZ#JlbN3nZ8GROKTGV$k6Cgrv8RAr_B)O@iKmu2blqzMtdf^-_`S| z4SC$d&JVs7)F@$y&S(y=2S9SWwmsEcZExQA@oAa>{TWOe-cHN&xYph_37BR}m?`Lv9zgh_J!3f>HI`Zh&+7r3D8*StO z(DH`pYE30sdcMWn7*LFp9LqR-j(ZZZwF~$m57(z5$B~m)z!%jRQ7AL}B59ZB8 z*bJhh*+;L0@MgsK&3l%iVo{TEaV)`{wx_O^RoD(C?ETZ<-RqT=hB@B%&vJxKEJLzF zoX7%qto?n@d!6dzmvOL=&XC%%fi@V;&S_$@hhre?wW?cVkygb9YWrIotMziimAB0{ zd6J?(?^Jt}`;qdjl?k>lM24B^93rp$(WvF0alP7En!ZdHC+_y7ciA9FZj*Q}Gt^`y z;>Z?;SE`z%Cw<^cq1lMkeCkkv=ioQyn`mBQRrpS~Yrsy10=OsVHw+sYFht*Sjb7wI zG>$*Ve&iWU*q~p}=#^5HPMB?uzvY4Y9Wz2~CuT%b&WbyO3m7yfx!I<6X#dh6AE^aE zBLM<}nr_481pVcR4f!iP+C$!{4^?zf|c>u@J4r9vA27(D|c?5a?-$2sjG-fSYyPpW$$p&OVh0FAL^{o+e#OF)`)m zTgzSKlGc*DB=!_87a+V7Mh1b|QP9mc{igEI3ipF4Uus!tmAihM6(sgp6`~$yUv1L- z_&hI$tYtzp(xBt?J1X)y7yAF|MTTO z(fA6G8>cdhYL{Due-~uukEPvkp!yS?IA->Ea~U?RjbeHCw5I#P6u9IjvIFqKjXncR zI=}vVmYvdHbtk&*kVa!y-gBhBRANn_UTrl7P^t9VUcphIDIICgWyFkP*_oL`7CUWv zEN-eoXtwd==zWj21=P&*+_aN#Tp zPkxGKk>i%M@um9Dhb)%Waq4Sdc7bh;ykURJe?{R@tdEAle>ry_rth&kGnZaeTFvhc z!H3nFXs7}Ec;bfS)tQn{=gMkbxn;YKo)OkMth>8Ne*KOh>|9%|xG0r?^NEz|%+V7PBknt~=XAhnpe21IMDJIMZft$D|J)6|Aqwh71 zPS;-hYdW2U=v?w@Ph$UYxG0CQ%|}WTRQ@9oL=2dxtdThLWQWo3V%J{jLss~4N~eLR3yJ??b;S}YoAkCn!goL}fIw}lHELeKq4G`8(lYTy?j7ns6r9bMx7>ql=b z66lYm^VZ+!Qr~ANB1l_$q$?W;bRuQPFZpti_0StHf zSM_S7lj!v|l&}XYIK$Z1xJIG7@Cn0i2!_|E4i4xZQ-&or4%7N{Ju$R;6rQ*D++6Gd5R_PfBxDOx z;3WN3b<4yk^L%<=cqU1H=1vB@O4#HM+l^)@z4)^|YxKSnw8RJF#_g;$v7T@kIn7E9 zlntc2iTpnC$#hXGd^v}7nyxhZuSlPEyaVK=0QqVdaPn}VoQ7n@pHj?`Rxx`;77C@5mqIndbFDZxx$j9fxcq>Wbz} zJQO;dSon@uiOk%|$GwHUL%|~dK3ew3F4}RY@p+Vd0>ddSXibw4Orp$#>N zjWiP|seLz&Cni@Ise#u05XQ3PzGT4qNPGEhQmbFRi8Zuu&m-pPE$431-lL!F-P41# zzPUk*sEi;tQ7YU-Of%AKzD0?RH6Xxt^ zmHT6Kkm`n<&Z9Ybpr=?vAmp-slUVf|D1g!0Se|{Xq~Ivht3iUg2!L)G(&iTn-`635 zRJp%NQ?C?TXP6L_Aj`7Pmkd0#IB_Ji7l!&PkxY1Xt>++u%>MeZu%5Q!6b<&ya@n;g zBY<9Ss~9jk_bA3jYZX17s!p2etB!3;g2YqaoXLZ5H)eoO!0*xpMv@Rgt@afpvvSPZ z@r~&Ryw)Jxs!0R}FGC%B0uVtTcNxb>%leadva zX|<=I<2s(+K4}$>5Y_I4PHDT5`Z!PwORbSeswTPabg%rz@rDbMUo5Qr(1g*?3;f5* z8C^40{$akw-;wEl&>}`5P|pg0Pn{bg=O#7V+^%EHU9tadjTS9qEI^q4FSu>77sjOjW?5RGoDHKH3dMYJF=KKVA-+Ko_xE0>Or7AIBqhqDIM3>onHY;E$N&1Q_~Wx9}-0@%FKvYbp%MMLGRyH*_N>=8<;;QfiRRIacs&d z%onzDV44TULeIqOf1mieVu*_VpAMcex9Qa+=&jB5;jYi&~ov~Z|{8ZA_aGqtN z1dhIOdCL1Ij{uUDA4z0o_@bv*KlT1!>h*5BVHJyJD%XIPO3*jC=7lHDy^p!pO4Gc^ zz~H5XR~D3j_bo>iTNrX%U!D4P5}5hC69I?L&XQyS&{P+SM#C1m~ZHIa?*Vzbd%RWWPrm>J=lqay-vLRV-NlC;l$AV&Ni2sOvh?21cZM2VEODvw z%DO^vkI$#9FXyK`8*6K|m1N6}g(EHoXMARZj_N-2!+W+$d^dU%MBL^BrRl~oTWj`` z#vk5l);xM>eu`Z6LAZT7f7&DoMznU_{0;Rg^USu?%B7+#@W+LLem#TUz+XV>}Y{tM<6(;mBe4ZkZ8x zl+-KWDbc3Wz@yO7*t4sh-~`+wIYT=76S35Qy|A0+HBU(avc9ZG6spoR%Y!sVYC78z z72RP1#x9gYRd2LVLLBCN2T%G!{BNFZt;Ba3K(?e9fEtNjkF=bzKLU#lXjeXI3FOS9Q}Fy+oW?=+&G zaY5{XAO=F)5mnCq-RJzamRkz+6jfIOuq(_KU&CT30er`r#!?)aAaq#*uPbTZoM2!&< zR8T#r3ea}e;?NHa+a-A2Y1KU9G4Tw=h@hm+ zXGnV6%=a@YdvJ6nA4G)2aK6#5MW+H7dOF+)u^on)F8na%ccsW-W50EWi`$@Px~x*T z1B$OQZ-4i?$=Xbyb8TL`r}mz}Y|bfXvnq}9Qa7gfcuOgOO%YwRHSRC3PXv^zw#)bc zO!ajR(83rQs2$&@T$dZW2&xbx^TFtgJ7FuJ(sY0{FMvf6UMbYpOm)AGdRWGkUbd_Y=BjFubG9EmtG`1uaW~D+-+AR~zk*G3q?OJ&2%3_xm%p>l@<^e$i|6tjt}vOD(dB-QdO^y&S2(?%=D zZkET!8S3Z(8Un>@UMAGQY44^7VH}@5#=r%Y5*f$- z@g+`XeBjl-H}N{=UZ@94Ugm+*rat21Y{UAp@;=m9BSbv>+*9y7VAUW9*(7h&QfY5h z3SI;I!^o-r9#v`h${8zczp5Y!EVjuY5-ke3zHK7Hqxa*q9kXZjE5!x#~ zRa;L=q*eELWbG8K1neDMjxg}YZhgyxPTK6%!s-6y43DT z5Dbbx+LHb}8n2o^U!+*u4*v`*`t3+``pN&t0x+#Gf6vH zF9PA>#b5Wj#Ywqr{{a53>a}RG>o2_B;zShCeRd_EXew~05A?5f&~vS$fsbDNJbWsC z%pb!}Q{Mr)3NiDOjK9al1X46zyjNroSJE196`ubfMSLB<_7-_=BGe4%GTCrrDFqs; z9UPA)y)g>_Y_ab$ca7W7(?dQgGG6e&vfmvy9+f(wy)>M7n;EPM2vS-+`ozd@C(rx~ z!1DI#0-?izn>uNjt?eW-ZdnoP9%bzEv>-Yq_4IdP-q%`PA|S$)_0IBonX7Q9At4-8=Ih}#|d&e|Y#%~6LP?&vPU#LC@EC*wz z`2HlVp3-K2v`jOS3LwwbPBHd$ZR2~d<@nB%`=h|-y95}+EB=QFFs1V3xm+kqHga#GV6iFj!mxgikw>bMVvVI_$F$slz$RYWM`*yD|8m$)>(;)?EKsWu@=BL_QXrzyz zbzrs}7{Np?EgNsvd_5^p_q>yXWno@6x41GrUe4f0FLi4<4*GO03*c+?!9=JCi%zFH zRW2myhZM790q!q6Be#rV&#!Vns*u@!vCjo4?WOkCs=3QAJ6|YZ6qG#2!&QUUW__G} z3pp$lsG`GNJ1*7;lzze_Fra^}iU~h~_mbFjnKHLsDug4hWX<(Qh8SViQW4KewO#p; zz~}XntCw21fqdfbx1e>@Zu38C$?B}}X-hnb(p;DB4q?5X1N3%7e4AzGHMh8W7`lJ0 z>VK{M5-B!UFT^-O1hH>YhJNZ15iWaBfU`aDjmDsPpq>lORt1E3-nfNQ;&M_t%M6p0 zHL+I(i*-2=POd#~?aY8g5tRO5jnj(FLNyt$9?p-xfLyiG5bPpv0>Yit6nrDWuimtV zUlHPFxT>A~+`V?YI^|R*Hmn5e#oM=uqS5G=#t)`@Kq6ZM#Svc) zLLd}NT;aQuQpuajEf=|WUB1Uz;pNN}Lu!Hb5o($3ASIV|Uq)KQik?XJTPjPsUqHep z_u%>5VPcuHQg(suQR(RRS$VP2A1)RcB0W8jAwRxPVyaCCuH%-ZZf2CT$Xa=P@&}x_ z7%0nLNyy!_?nBnXAcx}SZvhi~lWb^x#VOv(p6|5wxl7?Kq^*gqIFM%|Xq~^zzm!1C zKD+rB5U6X(NHaZnzBfQ$H6l>r%626uyRN5aQoi*tj&Gj+?t7{A)bf`mHYbBQ-u4Z< zA$7U;_yxEuwV4jtr(VLRvPs*et_+yIFxSZe--&D6k4*T_o_?cc&OJv1iGC|z=tz$a zyF2YXOZ7es962kcFp3%)R6CHBhq5RFk)pu&v!Q4-xAsp%C$>E&zDs))jJVw&*tN_^ zVPjcVj<#fYg`UEvUi`41kTwf{T0V-P$hY zX3R`gVQ9E@i20Y(CdFY1XKU>FT%qReO6nuD_d^Y@#P^X6KFLheoIJkew5=Y^n2?mQ zFH`Uy2UD4k;mxp&ou%zJ(tR*$4`J(f)nC&D?05gxobN?sZIn;~oWlqrDYm zw=oMl!dOr`#0gb-?@}%neflL(@139FdDnQ}h9OgS{+clELKSrluGyJrm~o`U8e%rywRf!MP+!G^iZvl5&k3MiNNp ze4@Hcx_odysr)9XNR&7Qoiztq-fb%JB7n%ZZ|pR2Pu0d70wa(RD$H7}Q{CVklo?11 zPZhVd582`5W*AJ;P)++uL;rWyO%PB7nXqCm7>~75Wk8_>A2~B8K9<|cj0xd(Z0Day zAP8zg<0TLjhw~hEDT*(Ox;6WXC#Zd7tMPLd3Bl|*K z-nlo?dn+eEB1)&e7`0={ydJ$?QemA!F}huuHWdzmY%dfbyK2Dt++)QP=zy8#>$Xy7 z(kt`cvZbISlO7f~bOcN55tdQ@`qwY}TNDd>nP>Cy-H2kBaOD^XKcm3597d|Tv}ku>1~RK z16JNFic7paZb!}xSJ}i*%^hp@)$@RnA#%6yu-2Z^(AdwAGd^WC9av}GvlxZP7%Z_K z6T+C7B&OH7{cwUG;O#jX9}fqN^xMX1>*#*VsH8U0WH_mht&!|=5or&XW?n2^2KlWS z1zs7B*Tf20R5by71H}P0EGoNI9Gjl{ENZ(|t_t2v=UZK<#zx(nBKcY9`%*Uuf;-zI zlM`O+C)aGyem`5YRzPH8R_!KTxrbcjI&F~)+!c(3gqAwJkyWsThtPWdhHv4a**_Vr z;IQTdisNra5bOR0zzOZ{&c-nx-YO!xofWmhnY0M|y!K52WjL?La z^H6)k^Jp)*P@_DC! z-MQu!t7Kp z9H=&6Ut0L@)ZBkvW~3`HS;$V9x`7ZNjYZ#dI{mx_mlE+y7=I=O+fUCYjE1Z%1wHh| z0dIXEKQbN_+y5@^e_Q~a-z5$EkZ}0p=_Lvcy`h0#O<5dwg@rEFaAF9M_Yj}K17opF zP6bXbsr0%LIfL<30@*>F42H@PQf2+sXTU(*t!(MEk&%X<`*qnW#hORJs>`obU#IIR zhlP0HSM$x~4|UwH0Q%npoIE{@MfTRX?sR)c%+rNVA}`5ISAD|eBbbGVp1=@r+)HO) zyL~HZgLQUeb7ME&SX!+w(IYJDS=YQ-q-RtEnCrO;;4eq%`|Zy84mXNkI<5*hMD%@M z=C;jv)#f%j_u>{jhmt31wnp4BDLheTNESU$lqcAuJ3R9bVPXVZN7n<%(9%0Ki*U_# zOxxx}_MiDxcuR$uVYpObzA97mjm}9NdOZqv3d;|MQL#Xoe(+I%2Xe3~feaG*Uy8Hv z0|MNsMr<0)`sF=jnF3d@*WSWOxt_2GoRL79qFee$Ss7Z$ja*j;C;Y&`Dq=dmMug>Y zZRQ3nAr+tc@(;6G1e^2iGz_CNRTYPG%d$K`lE!w-*(;7sF)zzl_*wu5D<|OEs~~N= zuL@o$EEm%`aNAnr7)g^i4-Yy&5-PSmAMHO4l|$>Apz#(d>b@Yv%`>LfBjc2sC+WW} zbO{^JdlrB=;D8wiCFD(Zc%_D?#khEtO!@l>(mGMaaq=XnQD~q3LDObv_solNm0B}- zyCIiQ2TITBikM>C>mR<5rm-4;^zJOYj5>LWeO^iuzk6mb;GMk zy#62^fEXwPiTuP(kPCX);QubDj0Y^b@OhdGH45<0S#3z!EA^Sn=jt6m?^xcjD?8F> zJXu+i24(x2BgC3pIDb;ou@FrkUexnezEqD4u_w>&)*mMG4yj`Lv3Z5MuwQ3XU3BMY zjBzV{*!~J72dRkE72auFZuiK)7CN8M6VqMpjUPQT9IIg|F!6>a;U|zO)^r zo#>=J@y9ioz)`*V3^?Z$z^B+kP|8Z6pj;3>oo58ghW5J#-J6J_8YL40_SKOIEOkL@ zOqG^gmC>YUjbuc1q~S$QJNY)eZc;zYY>#M@?Y+AEw*koV$#`;sctLNUezg1(S)bcK zJcjowUmI9A>?yxU)Rfso(HUzljQtIwYO<+JP4)G28en4QuUA@17zvR|fi3Y>4ad1u zPgw=?%+tU0p|7=?9)`G*-hZ6pJ(ZDewN$fAZkoS%oev$vW%!tL8a3bS>A$ynll3+a ze`+O8k)yIRM2Uu)`pu=QTF#+-qkeA>z#z-zmkgB6?9OqeMC~y64QIqv$rVKY{0_Mk zYL_UmbW(;BZ7cKr1uyhbo}iFZQe5HBd1%-(`)W?nC2yXi*p;tB$=i7srSD{I#H!R- zqzPdM7X${w5Yt1q+ce87;{!im%Hn{c;vO1`!`Fy;2?z1aJq?|mloF}8juSDYTqb9P zOy7FpJ#4ytN6{pkl`@h4<8PL!7z2Q)X> zoj<_s#G?4yc&aM>jwDRPT&HpLppdV$E-a8tjEg#|b_AV(3m+Op2GT3yFhc_b$II!R z*^j>;%;D`+m8M~WA69YA&X=0VghIPDv2HmE6r-Q88N9n_vP~Byw{|j(mYaC?S|~;5 zRNmqxklbEq<$MdJsO1fDoRGuC>#SyZ1vjx%n3KkC;6jqzGkiyK&kShzsn4ZkF)42_POLBOwb;Z1ywE*Mnb@L8al)Vetpi&`g+?|xf1VXs?|OtT z7P3(Y&dCXdkuIYSHb9814N$xK5G3*;u|HL-(Fv{VyYPa83Kr{ z!gsPi40{S6do^uLrKJEz+LQ6;nZm)1Q+R>OhtUs7Nozj>|F%r(9w|{To@tp0oWXTd zyoM_)tF_`3$15wA_y{8jd>dLNFnTQ4tPuj-Tt-cJi>F!Rf!+}ZRs73)Hu63%6T0>d z+m@$|b)yOqFHoRV(}an`Ywj!_NZ(!&Q7>tZ@?XY4Em6y+iUP$IMO61 z$%nKM;jiUu)ni{Oqbcu&5k;^%48{2TmT1)M`#qm);Mj!RrqZ?9;ewF1cGt={FnM@; zl+Tvqp9;<+F@;1RiJj^YSB$F2JL^T_eDpVs(u;J@{VNNGk=89F+#a|A{*LawVIt{e zCa=(J*v*M@Bt)Rt8k43z9kQJe<_}rMHZE6Kv|umrD?Q)nQ`;6U@;lpt8i5|Y{f_>% z+!SkRne6Z+BiyQ?AwMQe-I^ZglKb}Je!H7&;JbOyYvD|-O>JE3OQWYpF&56oQ6&j< zNk3Bl$`q4)4b_PG5z!8pt#1@(E&88s3aUv3yht~IR5*?c2(P;jpqtGKcc#uW)(_i~Et<;t=1B-Y8hKUDO z&PD(VzKnq=(-}wYq%qiPi<&NUEWnD4SAX@f##*Led(IX(jE3++af23M=BZwWQ#!GY zDdq-1-#$WTzM{rhI5nU69^Ipj0vI(3)pM%ZjpqHgm|0{Od3lINPi%)fD}gB&oKCK0 z471){0E+8v+X$U$ax$=WrqdT-gbQ`ZTze+}&Te$$?8dKsf#<;hBPGw1yY&Gj z-Ka1WKr~YdfHWf(S+6GjpEAQu8J}^3#0)yNOEY0@M+>{M&gp++?Y^owak-5AkjkDp zB(me-i^hDrrcwab9EiET=#FpW8GO61Jz?4a_0ViOPLHB$RG!r_Wd(NUDUR;*mFH)D z3K{k7Bo1@gcZ5awr4E+z-L+%#yB_F!E9%6X8v_qJ`&n<44i-)kTWgGvBJtSh3NwN$ zYSJd$HnbO!Tiv$X8rDP}X!Z zu6tTgN4vFP-V-3{XUyL_|0EV?%Jc~4Npba({E#DJIHOvgNwXt1=VD#DiPr|jv|?|V z?Q&IyU&MHRkP58>`y8dwdmh8p&L3#@wclJSs+Lc3#*cz=@z29q25W32<+4lWuSy;j zBUaMhoYHUll!h#XpOfMTJ=OYm#l-?jSPfgf!Q~U0qE$2qukTBVaJ1q3-QYM}RJ!JKD`fG2w~97{S%% z4+#&(r6hF1_Wf=>q(Zfg+bA%Q{62&eh1zyS`^##=Gu@GsMxMdNW#l$h$b1P6rF zz=TVc``lPN2e;F#Tmq5w+g7;IScUyi!_*7xtPDEu=U6#g%qqC``$?xj|GWJe0m`kP z1T2TXUK$8yv#_3M>x(Ns6#r3ld>Ey#?qo~GlH%q@Cr{8Y_jtPfmL>$h{|(RvFNVG@ zzYG%7TR(Sivrtc2ok;a zAXV``m)wO4dMVHP`ZW&U*rG`zRfN@1T`fZm2?X5A?o6Cyf0&WjcnKf>Q5Zd|bdXI} zRiTr}Z?I6MvTmcr021@?3WwToqt!LOko6g^wHL{7r2r%hx-7(kedD$wd);p{xk+X& z!howxww*e{mp=q{SY_>IbsHCRdprx#grC(3Q3TLHfzKypo|1Q{mGImM-91jtRRBOKz6Z1J=2+>xgRA0vq<<_*v_1Y?-lJ?1Xam(1IY+vjz9tOyR z5;LvQr+-Ah*XQE0$>xR@h-HZfP!kOQo?T%T?DAhWId@F4l5jeV+pVuOL zd~*j=Xle>;pb|eAOr%)~m?sL#`_G(vcbaHY0C?2AIn*5 znkc&qDzzT^NkC68C&wn2O8PQ7N9dJaTZzp`<0VX)R!nMG$p)T@k+$hv5$2TtkX=f< zAtrL^-d1F-ONs5HAtjq#1n~Z}(=VUyBbzdKi2lp1U#rx%kSadlKey;KS!gzWqxugB zH-N8D52yPg^Xxsn6ngVS=cbKX5e>Z*t~kZM zag&dEc%5FYrW>;?wLy0JHD%09Yqb^3Z)a+&^zpimmTE`6Hu2t4HehBgE)cHVM|?Vm zJ~Y`!kp%Oi&>*iA^=e7}DjJ(vRWzp2()^s1v^qg#W%I{1HZc*HL9ZM}5S}}(evs$# z)@?s-XN!wxjeaQ>Bl^+|26uRRjs8Q*UEed1k=>cL`;LF!V}5gXi6m$q_)kK;o=dd( z_e<*?8_gR$@ESRvKAxDdSSC|~s{u#HgFh8EbzwJ}<+O*>V^HhKvPM44vzjjQlgG$w z8svr=wkm|i1+RoP|5)5NL>0xfPwP%fTCre#Q&%D18}g8truftif910<;eRHs7JoWA zJHgU1F4wVZIi(ztb8k-j{3t~G4tJ<_{KN)U?1o&fm6vdt6;FL?62(Le};Lb<) zaA7S|7yl@}F0Ga|O}ka=?s#uAr_@N0i;VJ90Prggvn9$XDZwTh zCG|z14?3L85sVK|LNSVZH}Z1arA#_XgFKW_`wdMr6oEtsU$VczoF8_Q#vpP0OyV1K zy<1SgOT|)*Oa9ub^-Ca92oY6c?ac#J!ts_&>Nk&^-1=Eldpc_~BVbve<)q%c=uf`} zZYgmMTKvG=%w(ZQ$dv>nywd~k{!-eELzy|=f{`prBSASfr(qb-q!jAvti3DPwC^(A zX#M?!ZP(XX0(39y_6z0lpV!YCkJHoDWB`xBzRt?kY2PEC^^-;b7BJNBGMU%jEgKvd z3}|cyM5)76mG+*(l#6^owpNzV0JS1GF-CvSWY=abKWVpBiyD`FC8g|cJ1G!1^UG8C zs3vgOWhMjn4hIml!){ZLOzQ5^OabNwPDZfyK60`>fB8d?(_xx*gb%G2zb2~@lhn8eztDjZdrmHa2^Bn?HK4t- zaC+HG0qy(X`DyhQY!8`OYPePK%X@+O-b#VQVlZ&B{2Fi0s%QphG7LUVXAwB@=u(Gl z&TVu_s7+vsPcguP*)}Mp`Hi*7^lH23-}PkSQX?x)nz@}mO|j;wcIM%rBe*@#kmm-> zlVGm5i-o?{`I4n>c=h=j{ZFhD4MMN^r9;Qgb2-BA@$fHUY&!t~AfpsH;*KfwN9p9< zstsYoge}o^<|m0l2iuKv!dT88hQ^IS>izfRea!t2^d}v&a^x5r##zvtPR{5vwRF-Q zV&71)9}hJ*lH>;OaTwT?0+drdqcs{DrGy$I6!!vY=A zLGdAeYs|$WJHUX_e;rn==hoZyg4qXf5N>A|Bfyi`iV^|TkL53)I&pf6(WXo#o|7&m zvBVik21q~wE^+9hs-%G=*+7qwS`Sw(?^o?04qFBysI$swk5f6f9R9@8m`k9s_wc+I zj(>Ah?J&s+ih{Zv>vg?u5a5k3o+a}sq6!CN1qPgCs{3KI_nYR2fBBA*vrf$J$9oX) zrvEzn%_PYWPYE3=3{XJ;S8;0td3wqu5%FWS?r+@V!*o%y<<1HLZk~+$p?nqr?$-~E z@6{6@`X;ilzNnA=&D626-*>6)AK2O$O9jjiwoj{RRVOlX?Rfwen@oWO1~5BJ9qw|E zyf1HPHz?Kz%maA zWdk5GLqmfZtMS^p`mU8qua0JMvS@-tz}-i!H6$q6+H>`6kvx8Pq-F2yFp69D&q_+E z^E!g&-l`Y2R`g+rBt259`LJrS>lYPg^@$d-W4&!r8o)XQ2sIWSz4JH{V!I?ImXdu> zUt#^DTX=K{h*@nnQl%0&3>&m6=OIszQa5lfX14-*lrA4ndEG%6xMq#dYOv z+Eg>}yD_kyz9&?^*b%=os!$cIUv4NC;tMd%S^(+u2bSq1fd83CHd?VnVJm}dYS1jY z9**J{c3PhN4g>~Vl#6Tp%p+dVLI-Ge1_V|o8o1m8WcbCPuQ{YjoF?Q>cesj3D9OS( z@>Xy&U5amVgit##%_C6B`e=4}Cbza>Fi8zK4VtxU3?%0pe_H-0bg%6kvI){!14Q;W zH^rW^3f9FruQ;o+^J>M^jklXPiJb8;_0jTjqMxS&L>0#2^Vg|i!1BeQhu#DSI+*M{ zFjwC|HUTq|?$4NhiIhfi;n>!JgYSc?a5;VmhEdnMD-VD*{15JOdO=tX7b*aJ`#}gr zjQAA?-FY^CVlgAn1bQVDX$H4AueAuHT6KRdsr3;KltH-Oc9{T3i+1(NKio}0T|BGL zbc}wc>S$pIlOCM~-JH`@Rh<9`&Mr-m0CGHm++X0zN(&_Qzf0PL$N&e` zOYt3{d3vn?rPcy!r;qPs+c^F&wXwf3 zllyGp;x?}fM=~MO8qPwxPWlrqOcuLLd2?aWzJpMYsv*jK21B1|?GxQsgMu#Y(F-hvJQ+>*gi>Bb;mpvZ5UB8b1km8( z>Fgw-s$ei|GDAtH8L+wam4fF53qRT@Ld~}bkZarDYqN+N#OGj`J{+nc!;~*X-+uEb zrCw{|+fDYc7ZYL#QTlon7PaNFIg`w}*Nb}!U5=QZC&@g5ataR$zq&s&mHu7=Ec{#W zU>;``NzQ^0L^AIB0?=iaTwkB7;G}Uz?2VHg_0d!%rh4~x5`^t<2(ZrpZc!faq~9;T zRp)8>Y>$5XAQ1}*^U2DN zR5uq!KnjGP?r%&qZd{sUv^$?H4SInq?SRov%1zBp*kKOv1uy%aN{1EF%DhXwR~F-4is?WslU3)OFdqz1;<&MqXy05SQ@S(ZS@9 zP_8bBkdrmFA*@OY7!iW#imTn}^KVs3pq&7gc0Y&|Q%|Ah;h&swXL#L+8t@JCzpc1C2L1G<4s)sdZ24KWz(7R+D@-mv zH7m`6j=u+8Th^?ro_fZ)kGBBGF9?`xyfp@ZJbM6(6*dL$X8`XGU?*OS;Jnh%C^P-A zB7rTR>N+V5G(wBny2LolSe3~K`%x1QK8T`Fw~U~rgeHn{6CU%3?uL=@QIg*sk4S&? z-TG7HJk}?#OCxKtIpb5W%zMimo*OH>*<+GEsQItpAKWpXgz^PH>ntTP+`#0@VzajK zKPt4)zIs{^1U(Czqk1Fvhy5Xr?Jy9Oy966QT$8`N*Hh%~R8PKgcqG-JCC3YnC0(2+ zn+xKJPO5z!qGFjR__N=BzO9>a4*^m=tK$nVSgd94RG?+rs`bZ{{x`UDEl#slATuk! zfgmrwN1nhz8IGH5fnP|9Fg6g$m^QH`%y)xVpJ{lC%tSr=1FC8)o>+zN2d&hV2W=OL z;tb-n-tC~f;*@_-WSL0lR*)65CjUuu{pCm&qeG4uiI!o@plH-+>XzB)vS2nC)P=H`D@iY zb~(aoxrm;ZW0AhTZBc8k-}L84ycanp?-K?v7kGJ*bCcwtb4$@|wu$4ai>SGD7?%pb zB?Fh4hS%RysGX_)@}ZAV`*wRSYiimL8#jqwAYPO;P6(p^tCj0LfCWwPy4W`j(v225 z85`n09NtSB_zjaRu><|M5m)t`)IrNTRkxIm7ZGzN0_oZ`rPGG0JAHgQIe#71X!{6I)CjQ zlULkg8o0Iq7vS_vJZ6~))uA-rOv6{q;>|=Z8JQm?eeMPur5LaNg3&X~*Z`K}m-~*x z__j?Xd1}j&aCG^O0PjuTB~eQQb{qJ&lYGrGmXRS!bJ*rTf&+e-z3<-BTuF*SUTI&w zvl1*{va;bF;@^pIm2!oxF-NX*6r5jf6Lsu-7Rk^9D39q#zJD3v&PJlRtKJouQg^O} zccPqdeU(s=jAr0SyZ>o%Rujrl@&UsrnFMa>Co^=(W!RS`Yth=jm%3V`-=_oxe=BcV znTBd6YnA?sS3ROYpQ&b;@f&)f%0gN36f3^DViGel02h+C+2gr^C5Jzq!>b*0J9(n# zj{=>57ca=AWE72M%kudpD3?fN^|SoPuQyB;WCvO|GxNf>CS_#a(}+{kODm##a8DaZ z{Z9)p?r4oqMAx@rmdAY2yG6q^eP%ZFY;Vg$>atCoqGY?kM){K#a{6<^w%S!JfACBJ zLKisit4+}s_6Bzs3Q-%KF=O*z5y%Axv~C1)s5OWJhcpZp679ZkX(RiHb$$5?&8kyZ ziTsrUTwwWm`3w5^ZOpG2{4Au-JOvzF_k(CsyhulDv_T4T`E47g2R7PI9`t8wiwxh9 z1=;BT=4ZtfBVZ2?K~`uM#TTE=B`aD396GFJ1H49f^4gQ1Cm^s3fsvCK$U1}7#uBJp zS&sc-BnA#@V0Y`fx7UEwm|w?|b}3KrW5YIf0~aJ8L1C=qNVvbJyz+s((pTCNNDnv8 zcIz;z|AlW-ax*i-0JPu(IW63aG(7dGO7wIyXGhQ?b@1Zt=Vm}Ccjc6j8Y{=vXd*b_ zo|s1(Vw}Ns9tPiHjAPRsPY5z+a;WxxyTNw!O$vmim^w+L;UuJ(dUn5)vh*i4uDI0M z6r2vAq2B!0c`9wc!1W4s9PgLMse!@syMy>|Vd}`IXE}4WdoYe2wBPNKJjCXBq2%OF zz~(3b%>T!8cUOvw2-gA*Oi8#8Cwm?o)qb!g=&>ANZx~SVY@ymA;iVS1{sr>{n2`i^ zB!ull{u)T`gG&SbhrFp|&k_yY%5-*ydMkv%PDQC&c)o!a{L_mc!QvxMeWfvmy3epR zKJz&avC?HA14Ao0)3iIYYFl}z(l~6>)0Zhc-a8ED{E3ZwMBx;rOY$4xk!r3VCt-Ug zQg_jJyY@CWy--d5HcknXEU%SxJEnN~yo!K6nJ0X0*nG`9CLGltltqEZmfYL+m@`iQ z)2ah2&9egM)2t@sx!VSMMPT+-_*oT+qP985>q`8y+U{%ULvPUjayLzIFuoJshVFG@ zWkB2Go%o%_2dUGwfcPvXFjaIwfbucKbBOm*YjzEC@dpPMJwizPd>oV>U8ChUZ}h0? zhvYMZC>J^V-Q+$g$SbZYSj?2)_x&y~Sl|OWB-wDUIU+4jd`p;F8T*Mj^q!6Ov0_}@ z@(JlV{z(yxx)WA_rmeE9^C`c0_ubQ7(E#)q8d5P!4&3|5*Y^{TPZ`_U@$$|f1sh02 z2pBIS?VCceYD_lgZ!=@#SN&!tMWrGJAA%m4cK7Z>HuTP>$<`E}liN4*^lirU9BgTK zTr0435O!W<`34o}o-!ZFd)r4@<6u0aJPt+`p_AKTYj3tqzdj9w9QI+Y-*|L!syzPE zBZ=;Q2=38JU^pGUIow+hK9C+<*WJ{=YwtU&>;8PH^Lg#3G^b3fmrPFR{c-nEw;7B~ z`B}aQQcCce-sCK+Krl*6Xk|6K2G?qL4qb>c8|1)`kx`6WqZ1y z3@~0N?YEXmYld}hSMZ?|x3#E4q^+D;R6d#1L=^ov*`x7(v)YohomL~>=C zamJ~0%ZBzmjpNPcCcfjwf`}QFVE2*?(4w5ZP_#1kQMvrE_0nba$$pmJyOC|8>)X9S zHkW;_lMw&bt=W%nQeGV7WG{_6uGSO)O$juY+jsGJC{0d9#i=pQniruAQtseFsj8;( zM89zoGEOawkR(2hKBOxls*n5eXx;Eq7e9PEE^d^EH%yZ1v}n=eBcQb?69)Px_eo(t zLrX`(xGF0{CY4efL#8&a9eW7h+;lTHytznOaK}L@|Ao{uV z6qd#PN91h{H79=ALKEeWs=vUHQ!W91-*#U9)7}1q^G@6h>ZSucTp$HR;Sgmvl*&2>@d#S~>X ziHC_jXZn9w9?TN;i;;L2Z!teO>VR{XFLpZxI3PMJboTLzF+7iGn&FdlaYb5ZNNb`e z%PQCv)vBMMVG0{n54}sOY(`jrG9L;5FqB+>)S@fFe6aX?%)N1YiF{vxes!GGM7^YD z(^&FI+}Fr=#hB8yLwiWPJ3{01#BINzkMlV5a%nPtYw{dBA9v&}WaFHm?g5Dk{#Zba zT0=l?&zLRICw4uQU7~JBV1vqS3#h0|;x4s#ky{AaRf1OZee*5iPMEx8D|qRlHR^bM zYiWyeT!Tp}o9j+j4lh5iw-zOZqPh0iM{KS@1eB_dDi4XebgyYgv^S7n(Y?Rjd)&Y3 z)Cx(6AYDE8t9CvzBB^~S{rV*r&*%xpDdBxHrM<_&B$(;%5=CP^47HIHa%b7T!);ai zQ#sUtyc$bF_n)54G}af6sx%{Zl8}pXU8R4l*IvG85jk+I6FHqNUVcS_Ll?B79A#Km zyzxyslS@=ipK2m<(htOn771Z^pAw*v!hZ10ATw%L-QhK@jYw#vnuEa%#?m|d=FVV8=JxO3CJW_2Q*(3S|lI$V!2e7{g89@Lj-BbubA!QfeYZj=97w= zhHJF*HQRXcQPur_)JA%yd9CC|yR)gtW$2rynC^%J}4S0*2 ztBokwLKc^GvZD0ZXdM5v2;1riXbBql^#nC-Uj_~oxijL}rnZ$}EV0_A0;5^VHfXe} zP$av{`J1{L#Ps4ZC)F9>)6qPbA5)*8X~%d-rHsgH>kRXEUNSFPscJ3-R#xh`V4(+M zBJCjcNxKxL^aXyH0v_0G?M1!p5vc&%5P5}|Hn{K*K03GANf8ei=^UlugM+R76@uUR zcisA$YppQZ1Q@l}+&gZp9<}f{w&a#z2Ia{&x>WHnxfIMbOVz@CC!*%%vKfw6>Vn2$ z)B(pd()7h&_}i(^@VS|gZ+*FyEc+zx_JgIZmlV3ATTx!{!4VhsKsfX2O9P&MVXmi% zE>+b~S;#fq-d&7w@LA0}FP1?J zko8G7H%IS?Nf|1|H!aBrl$+Y%&|i=GdWK-!NdOCy4hK=c1p|MF(rIFfi{wu%zrWiH zDC;Bb!LuG}+F$PC|JrV>UWr_eAfomVLsvXGFk+CozNgofd$sxHUR~D2y3Pq{$8BI- zwcAaAM2X@&e}$ySJkc6QQ&{5fIfh#Ht;u|$Z$1?4lNTGzKQ6LMlgD{n7DM^o-tUJ( z#Y7RM9@cYt(DcuCHQ8V(-lYeR;n|Sjp6*J&mKC5wB-YTFmGC)_3*6B}2TsQJ0A=la z0<5rFo|;#w5hdPl*Bl)A2Ep{y#86oSuKLt9%Vp@0`w_9}s*~s)G-$J)>*nq(N*x5u z#O)G=O*_sVN1ekjjEk?`&J~=@zV+Awa0Bi9HU`(-!)8>Fu61>pfaaUJ%A_iHpO4lM z6N`yVqh)^gT0_HM_3^g(=iV^urDtuDt^NK1=I7f3!m7hOetC)zBPwjF(SqYO;BW@% zez~+jyCg!xS>otofM#uk&?~kC&TI4dOX0C#k&>?~E%{%rVYR<*2Za#Vg zBu!OQU`N3EWnd&fcb2+x>C<}pm9zV!7%7!+z}4Zx`1MC_iPMT(6yUAj%yQiM?C05^ zv((Wlt5cW-Yw16(YX+jp41<}rbpnAGDx1p>kp~%ZuExlxZ+y}UM_H3WK!rHD)MPS( zD3ZnGA7k$AP4|1v2>*`ciCDFlJXSSL5Z0@(XwET%u-n}8@ngsvd(%fFUKbiYG{7Ew zAJ6Bl9;I^dJFyq(M)ZOMh&1<4;iTKrXMhA(+`m##pwzWc*x@t+HsgjWu|?x#L|(Q>(dVR~ij-HtH{BDCRd*CjM?OYT}7{=s+O>RO;ut6qYyO zdoJ!h_aRfm5)|4T_2<_B{CF`|NdKc_#f#S5^k3&;6?%4`6cKtf}Jciv}-_OAeNosmklWE z5gf42*UF(H*)A<4M%I4jHKcN`f~HWHWz-`y$ZE~t?+C*L^3NRxW>tNvQ&6cMsDPK} zTSAWs06K5gx>%h10)}=zTr;T}UVv;^M@~R37q@@KIu85855OX=Y1_N|olH(in$3(s zU{~kPp>^L9kRqKwV*<#xVwy8#E!NJCS{2!1M{Ne{U%k>Bsi}0RegmSuI=ML`=fnau zh^1+rix#PYf8MhB3t9f#)vf;F%eSX(Ge?Jr&M1zBiH8{zCgyeb!j?8|6Za}C2sfS zRb%-T-+Zi&oLnP#mjWHPvoV%ULdC7#UH_wb4LgDsTax})4P=4-lRkez4=ly6PAoTp zir^K3G00xAZ9oPkD2@W(vo?RT8Hwaaheb1MtpCOq)pJ=Gob;$muvaD%yvT*ULfzi_ za*%;=nW}C^Y|4Plwx&`TKbqUK5D41lLet(fIYq+V`9Vpv? z6G}#)Xr)&W!{67W1Msm=;$C7{zy3!ufvj1)l1skN6fC9q-L7b=mhs}9O0Py+p|T(# z!YC82o{};Mh~?S@vx>UVUi4@5OgjSsa?egC6A(hJ3Jl-2?Sb0+hyd(fQ2rPSK*G#|mv9diQDYS4awMQb;7f8ko}KI!l$cSUnrt0rote zc?~2*6HyI4CQ%v?EEnNWRZBXmQxkkIZTQW(8{x)=)l^qea4WQSR=^C-#mol< zqAxnvlo4{n+7pCrP0>%IhmPx1q%;1ql&45$TD|ZbLq=ZKopp&Swc_=yb-8Mt5K6g- z)~iUx<=iHqUnijB9pB@QqXi1JtZ%tpz}ZA_H*Q)7YNF5ipUs@CYMWcr%AT?X4t-dS z#u(Bp7m_kXPOo`jEj+fbLxq;pfDCf+gehgv!W<&_O0?B1nPV z2mnd@VKX?NDf)NB4@VhfWXskd6V+V}0Jiv8RcERDe{#LNtZMV9d;XtXM>TI;+RQSj z;2Ge>3IRKLfKnrz zPwR;&P58pIn1Q?KR$o#tF`$hieMx(luDlA9q6-!oAS3x?7lU#CCg$VWRNXzRQNb>}kBlv;#Ny_E?k)80Tap-a_Ck00DSsh9~16I_=n4@(sv7ax!GR>5RHW`UF zbTrc*-_q;2mbb9EXL3(ssLX7?AFIb9kfmK3Yi@oH|3QxcI}vp>muo*!%ue|RXjzTk zv(X3rWhtl=6*hQXPJY#1#6Q@!VcMr_T2yM2MQsoHT1IP|;jTN*XH=RT*w+3cJ+;KS zGJeOo#dgM^Grx90SlBi@UZ!#iOACwhm7|yuA+vPBM&YUvU}{+dzk71dh3Hse#p~&Idi@jFZsM46 z6%qyt58!ivc_#V98-~LLq=9>nr3k6`1{iq=TDG!MpN9=Lnxzv^DJBlMi<|PP2>|PS zIopxv(Bz|145pe_=*RklCrNIwE8jw163AYjXCX`|rYU zv+>MM4yr~I)`<1VXH!NZv-#~#xju5H=SULSRpMG#uC1AP0G`VEGLD+hyz!9ET8fRlgXUni}~RY`kmwxh|AdBAZxY!L~!J-apisc z7cZqr2O}P13T#8_D={!Dlw}fn`zKe$pzRP}+nsA!D`d{gu>nmdVST zfB8wH-e0sL%`|>(PQVY19c8^6j(Uz!9)xirYt+~GM56C-?nLg0=H&kPp`-TOL2bg=J!wV$s~d=KVkN;S3faf^ zO6BezjgG{JDSF7_Mfy?+N}yiNd9Lrt2(0v zSed}O&dtMM`Ep9~9>Dz$^}7e=}wWjZs?} zT=e5PAI+ZR%tnKA-)}l{Cu1`K@%&r@nZKZWY0r#PwN-~%CIBZ3*X2|H?qYS-dWF*m zXqmd;9uL<~n!d5XU2-myv1XZiARc2F@c(6O9Y|J<1d}He)n%uT43Sfdq;L%Av@Dl{ z0O~%|PbKxm9W-QfbU=8ic}JVc_%GXR-N3^SPU4Vr;;NkJ;+)K;=I2;);sjlT5>$n{ zsRH?EdQq~@A7+0o6tpzlOv zW1~wgLhVi()ZbNsI~ef(% zwerx`T@Y|fe%37DD-!iZR>XQ~Vp`KZI=g9HAQ`+b2+M2D(r_n%n&sFgNlD1Zvl2s^20_fY)?D5psfyoGk5hwMJ7M6+DEp(i@H51!T41v(&++28TRMKq{ichz^4B> zmZb_Q2L~3@CaOB98~rwf@Jv6F} zh~hSoDR(`IDU`)w2GI1}E1~9^z?Z)ZT!K;7HxD;;3BmTxH)5)rTl{l&{5XS;grnE{ z@9-v)hawnN6oOHzDXvxJaz>AB^@t;QS~!0XV?L+Soq)CMO@LkRE-7^-Ra>Dv6YX^!9{D<0HA#Sz&*{sib_f>oA3wW2+Vk$e8y?1Z=zUkgy5_64}U+I^O& zm(zv(@Ln|gprmdrvQIo$^X<;pPan8r>V=e)#17VAfT^uZ0GvehSnOXu6P4)2`{#zV zfI;Wj$MHVrv5J*g4$h&5_~?5aC!suAQaK>eF1y$KzJ-e;mIV>LE!{QMnKfqYgFCm9 zKY?oqyPpKKIH%s-3OVR(Y+5LyEU=KVilamXd1nsyP}(M9j5UOzR>e$@@^G?#&s(Y~ zAVXhX((`Wj^r;IK!CYVHIPu5ZaL&<-U!$ox?bbSm6dbIEsd6B~jEcUDi4Nfcq~m*i zKrh*6X;AQOG+TDHv*=KAj|&q$mdTWfn`(WyAAy7N2~;|{n$?}U~#O~k*-V*-)^mB>2HQ)-C+u9dB7>z7Ab7h*oDrfZzoO;5%skAo&oh1#MrnH9aoWD!4qv8F=T zNNEY@^cMM>R>O#8($Pv)*CFgE)smlkw|V7zW|DDhJrlI<72J{dlYIw47*_cctB$w%mCW_SFbt+{~or zde6vpRKpni_YH@R#vDSTb|C}-U)eR8w&=+BUAK+dyWyA3Q zN)wC*VoY0ZbS>8SkConu`(Jyruq><}phr=jZPwD@OXAT4!{fd?x9&=}^%=V9R*Nvj z3;HBLw(r&jwjJPn?uNQv~MGAqxWPlL}F1leUGh6(vu1km@tI=3hsekNzXFR?e z6yo+8VH5y+JbCa@QVEE*#}&4unwP`~c|-LpGp#l?7wo_J?`;MJlsv9BZG4&qJsc)pnWamZx(w+p<3-85L6sv6w(WmJ?~hmmf|TwjfCd3l}7 zDgISTUsvj1q^UFU$VE!P9jQ7u*O&N({LM`9;)qH!yW!R&5Z1Dse%F_-5)R}2%%3(e z-ewGhJ7W~Ay}G-yk9fMDJs?ha%Ix~|kT92^9qU+^N1QpBpG+eX#BxqY;LGtSJo^4u z6;Haj?Y0^=iI%H#R;~b^ktUK`qiy-;O$$hE?@3)ep3?_5!75$V%0v`D7Q;iiSv^>x z-`(#j;-pss?i=l(m<)F@^-I9!kF>m#77;03FI#@uN7{_gYdb}yt@90>+|_=bR!^s_ z#Z#yK(ku3ez_upm>rEG13$ZS++(Z5Tslc(g}butYR*NL7SYz5C|^_h`rCz5cLb2#EW%@f?Kv#=k0el z({U3XBSbyRw+e9u%6vbz{MH}KnF@dXs>h>a+grvXnn<;^7O_13{uP_C5D{Kqpa{Dz zdE7>l&l;amf?leV&wASmOZwlwqb#^{+{lX`8$UA-qktOdD3au8!x?Q~VJ$1HRP`@$ z_?{XPeEZW}c&j{riYU8%$e4=KHeZmJlQ?Li_&e>dPc)g$2wO$R+bN<|&`rS==flw$ z@}k|&VhmlaX8A+#mO1J?ta`UKXQDN4>`zvxpAP!?D>PTbac#LaZp`me7H?YjCHHz) zc1}9ImMHUy1M)jC!`)0-ozJ3@Iw+~$m50bHO#@BnAu6u+paaNmQRIh zWX=UKlAia0v~aZSDtx_-U^I`=MCOpr7svq!NW{0|gn$S?^LoRvDc*H7H(Fd+3A{wh z8>uH7?O{Li8lBg@H;tK6)($XI*1eH zTcpFbSrh4+^C~c}Bd`hr6&dVN?~k^HFL~Q^_MT~6 zaAS)Kd-F6|;t39lM$%JTMQ&?0AstWKgdTL)2;XLg@b@Nh0*$Y_qJfY_01oQ=WS7#R>NRWeS{|IgX{J0WwPd&j(V-mKJ-$i@4g zMu%nt3FRQw_r}B4G(a4MsDAhW)Pdx#(Qn;J+Q)efutYegZVBTq0;#=M9PT~_>1n)e977RgC1}a z?ffv5{x>)nh*AZmc^q3ZTrKMpo!4(#qtt70yAN8PMWI)1ubd?&p@g%*%AlLZH7LEm z%t8Ji66dCnSw>$`n6;#*)bYHOSAdLDf&Ez+tO~}*=?GYudg)B>hN{16s7@@Ow}2v`IjX9JeqlV03( zeVreci$WUMfZX3WApOZ|rp3tGID_)XIIR-SkGjXVD&>M{u0I}aI{mfOTBcp%Aw*f& zp)^h*B|ZM{=&~q`V$N@&M^b3j++hFE@oqCod$<#t%?lFuv`v0MLr8U?;vyenr>^v^&Y1~?Y(*L zK3E#;s38r8^ua7QPpy@MU_w;7PBeY(zhN@! z!&2&Z7PmMECFJjnrG@9N#g6eQMQ@%P+?ChdTsY|FV@A#NH3s<}eh*xBmZ>EvGW;pL zjY=i6=5T4Xr*$^h(aBywOW}ZUtRj*UIIK_Hil&*ygLp?TcWoN8l(eF@Sc&He-#>mQ!r@7jFlwTSgg2fmvWV-b^NsHw{t&P-fM%2`|_$59+ei*Vf(vcAcj zF7FT)avm|`~f_yhmH}&pkd64)#zV1;;y<~m4UnBVK`=# zCX6cTGf{IP%A5Tamx_8n-b2{wuyf$4CQA71;x+$K%0xQW6KS5YiT$@3ihUgesY}Jy zf}RJriE|%H)9(E4u_KEQ0|YAh<|ec9%|mEL=K|@{kIeA#Q{3jcTC45es#rm(63SaL zLV_O@UQ(cn>~w^c%UU_l*y} zp6#b2z{?p=D)rHD+0a8m8oFnaVG9^~Fi|0$N5d(E6nl7c-adqPAdLvyM(jsY{fq@U zRk@;IHQkdG*c`fCKVrx24G!~xCy@j$ZxzR^#M~Ic$n-9&GQxTLeitmWepY>lyw9-v`&V(fGe<=19 zm1|3|(88*Gsem-{YL)am^sF`9Co*BPefYGZiexMiSlT?5Rs)_ac4;Ki9cdw&-m@qmx3>a zVKQ;jXzOXlN)9|C!6TbgnFaN)7YRjl)O2GTsqv%^U*al>xa>7K>`&0v* zjdSQ!>Lg9R&OKFdVJ0HcB*DPw)i}Dd-_;%A({60OrY+5s2IQ{+iUq+wR66T>t!Iv_1?~(wmYf}K>+-2m5www-vO)Huq#Q!#Z+&&^X zk@DwMGHa>T%%S~Rs)1t!`8)7gw{iQdCm}G@F=ZeDkNoXS(~-;gV4C@hNRR!@Ewq_% zMN?6uS(d!qyK8c9x(>|mpvtmfE{;F7nuiqaC{)Aqh+;VRtD2uCHwvu#I zH1nTn?AvH&z5G4_`2k)7qpjoaAn+ac;7qNOMJi|Cg-paqTSx&ow)|b)!c$hl%I*_r z@9BGSIA^ftuqohl*+5cMho=c&ZW?HHRa5%TmF1fl(uZ)3kH86EP~O;{URYug343PP zh{+kXCa^F6odnV?X-z1m8>TzqraU}R#9po}1$V7zX6VQ17VFQ?uS}l)0(R&nrMCn- zQNf}I6DU?%tv-L&ku@FC+<07aTt)l#coU-fz1njBwZ{XZR4QiV#rKSFS#o1(>FIL6 zN-0%8K*|^yAKv&BJWfrRfv`9*Q{KSH;17mdIvx3S_{_Q3AyQ3m(p3um^i*JIYxD_s z26G+pV^pD34=CR{moR@z0L&*B7$`gRF947I<65x#u{GVeTl!S3U6`@nQSxU=e;>0- zgr0eCa4d>Bf)-y1M14tWN%j+*yG6dWz0@Zt(YH(c!kQ_E01EaZSmOFP?@RbDx7lN% zS;pymLbQU}hmVoFa$iAreOJmCMcP*PUhlg~wjZ8%=*)ZK{z%x4{iPKWmnUCmQ1wuc z`P#9Mi#IEsG7Iea-c<<=^fgmhRmEQ#x^Mbj3T`yIWj&hnCyZptZgs-tr-{g6sImTZ z();UG8X&=UhT&g=8*2YNHM%d#<-OVnmu$P!#E)38t#i7iYIbqy_*q?j*h^kNXCDCE z$U+1zRs&<1s%qnGr&BI?nZD2G+%#U8g2SU`uhoONRPPlt5Z&EFN2Nce+fwq&z2Dvw z?OT2PumG<=FL;hfX2241z_eaFmg>I|X z)G#P`=TWL4VYj;C21Lhj^=62^*U_Y}J8qQ3&tRcO*Xz@aJMU>?>uTmZ3LxaCVo@}T z1OXYYRjrVEU2*HG`aXxwvQB_0F<@8rgeB=(me;X-eea)iT5T84=F;dq`>Q(-?sf!DL~E{g`MJ{p5WHt&_q_{zt77J| z2IKD>VpWbo^V)3*XcNL{Y2EM(>z^h$v`;h8gz00XgwHi-!5%KP9+!-AvEwOdK$EJ) z>948r^VHPyt=%Kj$tBMkTFYv(=2AD1*3qB2J1SqS7E@@x(4q+MDeO(`XdFSX|HA&U z_@ZWBPAqiuNpiLxKUv&NX}kZ30F^0&^j)Qj45b{)4)(LBMx0w~YCkgGvIQ2G^ikr= zK7sgD^Igw84qQ|B5>}@&m&7AqT#C&z!eH!ySAM!T0Txv`;uS}4HatF39k2%zeF=z9 z6dOTfO0paIzCVhzjkb+!y$wF~U`KSW^j>`qxBJGZ#Q2xlKYvkvd;d$lUcguDuHJ_e zr;HEBu8b%t*H}}gZIunG=t%~3C5JwkT8%5ntk)&hvjcS@;7c-!4_CrJ{=Gl|SI8Cp zSW-C~t`{&^gv-C``7-kL#d9@M@gW;2TNvIn-P^*&N1hThDhj{B`tN^o^5tx4xF!x0 z{fylVd5xMRaenX{bz3g$*kY@yyjN8D0YHZTp(w`X*AF}S1|;uP>*-DMI~WQ`=q0Le zc6RdbC#SOV{d9^-i)Sz;S9B5B0NU^GXp|xRBUme)8gGL*>c@FlQit7Mr)xAW{;*FR zU=2inB_Ev{D_biAJc)sV7B?;Xk_PvQFL_%l>AIhZ1pcYrt=i%9#6w?tcwHpmycZSD z*jh`sS8N5Wmf00{GHP}3)h^|Hb0&x63%Lm)Olbe3W_QN@e zGz{ookK!@0^z*0a$2z}Vd!ZE4w*v)D!M3!rtKD!LzrALFHu(~3tmDQf9UTmxIf~&$ z35$;9BG1`mYni6zw5i4MdfHb?+z@!b>H|GDT#O8a_4C6&i8hE&poskcB_39}wXk&- ztR>slSXMzPj5Sm2Ivq0z?Qwq4=5q~Lx}?~=aLhL_`p3ORgqtyLbfyzS-Fj^Mk_M;Z zR4vyUXZLT+>cXiK9_cjbF3Y_z`jmN3#8SNcV;b>W6GE3P{MBWNZZ z#VQrdIBK6vo9}nDRv8`naVLU0H&6W&jw1uP<2!!OODKy9XH@)+6*FHpV4h&T&A``P z>pZ9&gr!(KqsEh~`#oB}!QrnuHH-eN^?;uyfJ@;vmh$TN=Z(`CXqRi3O~zmYx;?LDn`HcU3+ZogwR6%A6Z&^~u5fW_{KdtF@6 z@Ni)%-P5u{vV%`Z?B@Z@Sv8x3m_ z6Y_AXoqDLE;f;RI(!UN?Q6X9DYc)!K{)$%*OCF-|%rkA3^P2wv%G)O~fbsgP==*Ds ztrTGq8EfMC_Mj5l)6#Pugd@yqtAu5sZF0ZF?(KT7%wOGx`}wfT~uDe}x!-t0%_bmw$e3AGF%V zD4aQ8Oi)29 zH(KhvJ1Tt?9m5D`9(~bK1d;WW@-!97kkY*vEROh#7<-yWoBPJbZ35C^JJXTw$A;Zd zub>i>7G^-w*p8S#6g;ngH`7wpA)R*wiCDb`%o!esTTU+Khhqi!Us`3c~hT?W~Jp+0b)bzK2cGp$V;nCfVdq2&uNkJ zk>tuiIK#Pkla_F~G9)J2cEUhXC07hW zK!dWx{1X8hf9AV2u(1hC0_jG6SfoVimhoL-ow)Po(qlaD+QtwQ#KMYm7P(N=;^w;@ z_|0cFZ4OpSP-`Ee$ek}eZyg}Wp7$&;q9>6SWUiixlu+j=O}i<4l~;zWgeGxRMMn z%Aa51_Xvi>JyiT-#V|H1#Dtebo;ZJ82ha5Vct|OOVOoOpVfmK?fLO^(Dd3^-k;2`l zi!G*MTX@(h0U$QUgz+Im4(mzc-KUF<5TT+kFMJE@pK8+(*U%W_@r?UgaoXo9kvHy( zseX*%hJWEWxuocO9KsOe5w~3&ruynq8p-Jx)|`%Kk9XWRM0w&eQaFy~NMG;KB~*%O z!Xyfi-lGeU|5?G~V#B)M)0u(C{f39bnoKH$fWy#}Ws1G-ky5ZI-AeM!Y{8-|k-*z# z!H>(gEUSiXe?S83zNOV2U-oONJY!zEL+@^@8QN+xDM7$ZSyN^Tq+qW*daL<%c8e8I z<{7iHB+oL|{echLC!~}N#!wdpo9@7UIpfoQ#V_ke>OAMJZur~#Z@8&yp4u&Y-C=~= zf4-<{>Y`xX?vP5-6a{Z)bKcKySQHgaQM$eMvnAgy?$E|iWEn`@b%py4A9v4WQt{O8 zcx-pPn=PFOLSB#yNuD{wf>MH6+0YFGN=g3q{yhWku)~j=XF8)%O1T|$*04IP>4u)p z8a92)L%XHSa`r>dZuD>%jNylydzNKGXAIxXZ)nPrWmU5%Y7TnfwrnU>PE%Ce)$?(O zpy0VbFv}~peaoslP~`>ILZLE>ETdEf0?DTDc001BWNkluw}47HtLf_kVv%vr_c&((|2aBw z=P|B2`U}%uGfafAJZ>>PPQePxQ;GDK+9Sp@ex8ykF7donf5h-pE9RGs`InwU9Q<#p z4N|bfc2CJSuaOZh!HwfnFfZkulAU50zWw7m#pR{SxWs2KjoE7+81UkuD(opA4fTpp z4>>UyyciQtLMU^G--h7ZI0nzo2Jq#@tL2xxi7zcAUJ5DQNk!kUg;d$Gz7)bq$?)-S zJU_!N3*mDpGX~*Atq2ywBtc$1;_0_PdX*-w({CJp2WJ3$`3K#9{zp(?gEIhf=-1;J z00M+@%vK^`JiS+1f$;A~AH8vph83p+MTpat|Kqoy+TjZI=*A;Pn3Izv{Fd?pAQ2zt znVMW*YXnFc5iX^j@-7(Tt=|9{Y_B*mki?Vk%ZUv-*c) z)z^8#WE|Jo<84P=#0W0~jIpQhUdR!=9I|FDyjK+eht+}mdNF)|_mEq_8mhHIWe_zem4?RM_L%U@^v}oKu{#um`xRv-i%QQny&F0fdBwVK+3SwMxGNDesW8U4z4}7(rdb?2d|s4%IczxSo`dcwvkZyg z&HRR9^d!h+hBlgIQM2hgit%8;UEiXFq{#|C?pMr;l11IH=?=VU7Hs>LGS6A}ElNtP zW3=3qwYy4B3JR66*FANX(-Z}t4{PQ{g|QZmVNuQLbVs3bmQ{^Xini~Z!REoh9}nov zUBBq2fE~xFI9@j_lpBNsaXcqL;5;foyyQ^=YhTiN`M^rBxJ830Hv7D!G#Rb@~KA0mEz;zfI`*O(_fbHl%f@|ph)&gDPCB{Z9bX^;E(gEZd7E4J1hy#_2w zx_ru7S6(Pwjm4Z0!?AuRiyQ$FNjVFs3;pGdv#Fg2h+yG-iU|6$5&}RB^K8d~jSQVO zy`X@N>Jay}c)=CMwBoevvjq$B67SPpTGR;3O)f^fcFsq6j*I(a9gqDT2mmRVC-HVV z?Xm4_uwYWY{^0QeAql-t>xF<6Sq}^!E#eC5JGZO)^u7^WEpqWoK4?y7<%5% zZcs|0F$~r)7|nKQ$)t2!3JzN;p(wMQzq@(MEGwB66%Xx}+iFIoatf8P89FwDQ*z%l z3p%6u@%AlmW=pg&thzmQma}U2{Iq?b)taYn&$6s|KfmSu?3Ufo^W5)Q4;@lS7DY{` z2fn>|OFufuYh(D>u2D*`>USumn3ol+Zci>HTYccy{mPx4P|d-@H;XqQ1ijV#c=MJ; zS@Cq(VuZzDS@#E)RYRF&e7k&$kdoF8{IveeUh^4hj#dw> zhXc7%AS_>YE0FM~)kmJXEng1n@%#qEx<9b#4{ZAui)C3gY=)L1Q#^JXJ|EVUN--Gi z3=u+b==UfgSq*!h+AS7Cp5-)I&L0-vF#k8-0M4$gjZ@wk_x%r}^8R9T7EXwhFLGk8 z+#$o6KPk|US8xYd%9nARDS0I8y|MJ#c@jvOQ~&s5a$crr|8X9Lf^In1MZ}-ZAs^>o z%s*5BOU3g&A(X2CPMGe5pOBy#0S+e%xhZiqqIpN2dV#hb`Cm$gNzqVoTKACV#&j?x zXP%WE!1eHseL41#TPAT1CoB8&N>1v$c*CEM+#W{~TNfY3j8jC*7S^2z-3mvCR zjFGSp&iJoAjX#qSL?U0DHj$?@FGV&SxsW7E;Oz9wT8kP8dO-G5pdl*xFm;V9$Uf)whG)34FZKZqhl5{x#evdS;4j`T5i0ZE92T2~ zb&yYvg{nw14EJ`8%DxH-?D6?U00`yn2& z@gQl0ipECR-!f7<hqU&!=kKs*gkVx&$zAU+}1O4>8=p`re1Qt zxOIp8Nx|Oq{N4Qzd^=mR>G#~uZ%|6`*Y!uVaeMb44qJZSJh3-DvvP(u8ja@De#M)5 z!Jk(jsI#19*)V8BZ;dmE2+23|8-CtCl1ar6i#Oz%;@xb)PPc6OgWIYgpeagvtNDI* zPnl)hR5NzEKysdp;gkG=;lTZ#^8? zbO)3a4C7UQ%jX4)FK^tKQZLHC8R?TY)_J0gH$7-9T3gCYG7Oe>FpkxYyiG|#XPj$) zHZD0j@j##>Mv*J$Wl+5P0V}*O?#moHq2mc!ewM}u8>NZDaa!3^8U&~E(h=QQ9th}v zsDP^I+nFKNIbbVmUEXhp1k-lz3&1AXlb#|gP> zicTNl#>R=M{G{76Ng^-tgs^O%HtNF@xg;=Pghn9`_t?)VoK>iHXm-`)U_97y6Af7}+TICVWAcyZ;K>b$dMIMVypQwQ6{`)fj_ zJbs?20&qUz!fz79stF5-qqu`hhi}DSYNTWhHjL*TZ^2UlpH-y30 z<3x#{5CEo>;4mBoxZ$NueEOq7;r{~x;KTwSF!5Lb(7{7W0PqTXtaOHkkF)ZFdZoUn z1b{fGDBiclB>^DptEu)+8Anc5?tGgD^D{QSoZuD-?MEDf{BfC+D=HB^O7-sv>?z?l zR$Mr(>q%P%AsgqdlmcNP7rYz@7{Kxe!s-e>{>MAMnJp=@oZb!;D#K#=)9MrZuB9$Y zq!egtSd=yE*6k~=%LXY0-FU#?m+hKjJT!3Iw={X-zMGD1f8e&N$%H`S6nP~iU-oNO z-5zjm{9E19nSsr4KwGyLzshpDZqK*NJD%DtnN)N}^JVje+p3|nn%&SLm7vZFhx^kn z)OpT;bH`tGJA@QeSuO(6Z_e&Wl2TQj+_6h8C7px8v8%15bwy2+JRC-|}$S zuo({g<>?nT{lRT55E4v}7M8cofY$Dcq_4Qjjb0!p279 zL8&Bzbq5A2$?>{BE8STRLb)x9y|Ls)L-qFEajwPQRVPMMKSw7Nyx5?7LYsU@@tIiZ z2^6lAIULCWv0|VD{Lg4}h-<8v2X#DCK1uVqNkuS9n^ew1ye+3~5~P`2nfEwMly`<_ zSY9X-hGkC3YAJrIQs1ZKP68enDX2%jEnc3_3>+bjp1(xA;DpD9>8C=Z6Xjqn=NG*v@YTV)49c52=R*;keHFn_oUUYx zu{|KE=uZaYXcdq&{GEjOTiAE;Zz`XP(%Fghsk}aT^bw{ z3_2N?DsQS+EgV<=!-?cq@ht)2;dF9|fS~wvp?81}%D&|DofP4#q{R-L?w^OVePQ8k z5!!l?*6jxyzj?atQ3`)O3deNfoK_LM$KlA2e7)nuAr9jFbE=GRuh|)`48O(cqW(C^ zC!@5f3I)9Fi1!3uXCD*zorMsui4B7uuOoh8n+}}eb@CE1fn@&$IK=Ol!lk~S%s+q) z>Up+*|5CYeKTae(sYf(^eVJ$dcA?0)oT;&QY~N@MOv(dsI#=~JlrXFq$SGM>8~^P*zcw=_jXHJ<6v>tVc=K)8I?FtoyElgHxHW@}4QUQ{N&!e%40e z$JZ(hA)gEQ&qWl!2L9KCuVJp25KPD=Col0g%W+{aDF=wtG?3=e7sG4PdO=k1vRE0) z$T04I>uud58SCmct+Z_`dmtP$&eG`64sySJ=SZc z^2X)b1dKRMN@MkN%(s19hULW&5HA9czoC5t9AB{#1kT7T$MYkM0d2-J0ECkf{`Cxi zlVDxLPAroi5e!``0IdT-G^oXK)y350V5XGyOZi?~UK|(0 zeI-19=kZC1t!6~;l9yF~57)y8y#V}a{yxrdc5kfmd-?&D1ukpi#d>`->?!)%M|JCEVkGK>$d`#R5QzNKL_->ho&?z$K$j3VysVDb61fz*Ly3( zsxsJL4KMK?eZTT8GC}n})GVrsCd;|08kW_JRlj3d&iJxhyVZOlSRJ<1d4ZIYd0sLr z3btL#O<8ka&qq8YKd(P=Q#Kq-&r`eQZ{NJ9%5!d;83;=ubEJTWcFpRrrO69c{f@zE z4!UPv*X+iV{98TH4n6DsfRc*3aJLaWbz3?!@Z4?rbXe0F&7Yn>(v&4zeW1*8-qdpr zrgsMdnx6g8vaB0^+I(@>7!Dn?qGUDfDYKlra>l!6!Be~8?QF>)oHs+Asj+L^s{N|l zvo}3IEbnY||2n$Ea`8{f?D zc>l|{uph7YJF+V`kcDt7{sv2L9KUeaQ3_NhUWTN#ZtDt8z|*6)CIt*f&CPuutVL9J zsJfY^J8}GL1nigwOvxJIdn!!vq!^~>8!>N4@uN%hCoc0fv?hh)wfek<9$lk(srI{8 zzp1`Vg-h)gDl)Mw_nN*>;S!gb;v<)Mcl;d3U25}7eHj0y#@E!hFwNH}q*EcPs|n;v zGyaX1zvKJKpAOO;e{piJ_ZIM5T`e!X(>l5LL?{i<2{OM@)I|`ZzPn z{HZpG-&6gVq6z-@+B}?+S+3EQOE^xIBK{4$`w>F-U5)o z_Yz+Jg5h^y02qUe+GHFI-Vk;?Npoy$rB1RQ!%1JL=wsaIg7e8TWe^DSyheEm2lVe) z0I;e3MDcsd7!U|PXYMtUk?19m829%pYmbV5ynp&+AK*wtwI`_dH2su|dc6B1+{X<3 z|3?7G66wO{@cX57sqg=91%M%u_BR#)&VH{kwM*^epTE|)G9@6zZ8HTY^(};PjaFO} z05Ycg+10&Cp9c_FBalEDrb zVY#ascDm!keuc%I96uPte(0I2k|rzp(yn;ZEYMh&zl3-0R$-!1MaRYsL(EQ^{i`whk#loG7_1B2D% zGUH7%r^qs%+AYhfp*1~CS+N}s{N4TcSgbpD;IQV$H1~Ks@r3&rMGTx|DveTMuTvB-IWwbA$jWd?#Mv_ ztM0%L%X?-;#i~8<>99sA$*e4C${LH|sojxh83)@lFDe$J0Y*y6ue&GS&X%mZ9m}$& zDoehZ-SGLa=C+=3-^}QZ;a$Dp4~sYKh8`&;KQ7-fFDmEEr(Tr!kGp5IwWIPspsnU{ zzv0XF3Ef-1`#*m~Nr4vQij1`k#_jVL;I0G|j=c;=gQ*)+b~GGVCdlAv5Yf{>|x_pHi|e&D%>#Qi}GyM)tTyuR|Cqz7XP(Dr*X6oOg>7jjY||1MwN&6v@QX^ag!e=I}UW8K}h3;VK#)-PIm zw{t^Ujt-TaWB&wl{56+%T$jl{6q)~R!da&aq_8jJJ-UANtw;#{d?>?>Dp{#uGYYzxkp<>{|t#Xit?oJi=RWd zsrOWU;^3FE;^z=Ak8kXH7N&g-QHzOgTxRG;m8#37I(-c>jV)`=@f!Zf1u6^gplM)qJ&~e&Bxt} zCM&tA8=en49uF(No!v6%f!>Z++;s=u%ocpvtymNbc74aHKQJ$9HvNH2mTWq=qQC1p zmQ~I3Va=?l_+|6RrWgFMc*}FUp|gfM%h2Sb;X?A`%{#O;^m<_1cL*WKq~x}mfq>_3 zPn{KHO3~DFdTT~^_lzvd*!3+>{hoPN^3Cj)O}9rPDe{~wqZ?IX*wp~w|ahb`}yZ_&e^ zP1iCQ&HLFMPwfVarN|Y9%9xLB{)goo{<8ke`|_6liz0iR)8DQ^xGRH&aEl0zWmvbq zprMdBW3zPI3C(y`g%EBqn(_H4J6ekr0)H_;ar2!s5g9wpM<(XRF^!)nsks%WN#%{d zP0^yboJ)Ac<%IcCFt6oJm6xJvAucKSDO@hm&G3DSzIi-CUJ>&RGZCLEFRsT_+%;Lj z=eb1BUlUUFdkVKOZP-88WTGi}G0ZT}YkB(AoVqmr;&CL;vlt1IQIQW91HD2K&aaf> z=eNTmFr#8=j+X2ZoQ2?; zOE44hsgN>C#rnuKohO9z8l5ZkJq5#ulyT(JIGS474}!{FdQa{{u*O1`S-OY1{LhII`IMT%%}AwC5!HOo?Q1L>Fv_@xB$DIBNZ`!qu$&Zjq=_x}|9FiqT#VIRh0 zdkXhU3dU5qhV!uz{mKZg49{7SK8-*aUakg|Am)PpKXuAmp>iJfTY584+a#z*q zo<&)qWX7zhopRsI$z{fV=#c_G>{dMNH~hMNCRZ7CUhrkVq16LYDpuW|o$irB&}%~> zGX^uz6cwG(CNJ0z?!^0D-*Q(qJhnSl-JYLUpSYRbpt1A}9EP4n zy>Ols!tH;z)*_W~I4bEpBlN(B?K20x@^(BDiJZrunz>%nbDcQ0d+0!G_9!&kej{L6;l zi3dQUk%A~~BShu61;7u2xN|cm&lETbDeE$cxGAgD*I1)c<;KeWrHL2jxt1<9F;aNG zMu80hrP|cTg@nxS_a6-e%G$F&dO7jkp2X=WKHL%zf=^xWpduomCnmm;ZpG(l1@;qH zy;KEG6%}vWI7TIy@Ub`#*PJUm-nuNKI4Qx%mom&)c^PbDInr%8J_kT#0L%CB3I6&t z?r8Ll>K>+-YRoqpd1Mygn*}%o^)&JY?<1err|^&YJ+9eHI^W4LJ|{sY>Z4!qJE@1q z!ME|50Pup7@0Vj*|2;eH%S-ak@jZZbe2&{gp0?{NjJ7-+|I|b7M10J z$+3)+VkVfSiQ4Dn8=RacaZ+Y5SIjZ-I823`r&8ctCItw|cwnH#nsKa--T?ByDB$09 z{Ppt#^SnX`!EWd|3>~-QA$k(QhwUSk%4v#1ZGMXYsTSGlwS@`|@j&0w9fZfnhWNtmsdUs%8&wx+@g`&!GR^6VvYQ|=8R|S@N=2rNJ zj_;RueB3_s%jS{Wx}nnsB?YT?&yTlnS+_fcP|m}m+o7%IpgWrJ+P*J`XR>i{=2EgB z`q2y0(&QzDQe-mc&+AXTu{Uh{J%_<9bA3C%<88fU-MIq+@9PBz-Lo57l#panx@!WB zGxE-h5{qHe?P=}6Q@f?tnnh7F7{hJZ(CVICDcZH6`tuF??^Y-Bo*lCFK=W*f; zDBi_zI39x!nGk4Wu~N8OU<9Duc7g2u-(ZYA=Nkci5BY zcz%S#d6-(wf32?jntUle=8|4D1tXdpXZ81Wk-@VMk+-TE?LsHFYGt5|n}f!CF~%@_ zIo`lvrx5;^55Ersz}R|5#a zyA+)FtxrE?Ah@>TALqGbG)TQqrT-e`-%pYh$Mc$Ze4vST%D*06oRn~f{5g>JM3MJ# zN7w%VZ*9f+9&87>;7=5K7M#_?qd!hs8JP2q40D{UXU(I=G=85}qW#Wzh)=Ih6CKm( z0Pa|&5Dya_mGXgjc3` z3#jKmVVwej!>9Fq5faJc_bG$bI6jW;7U37R`H?@z-$m39VcPLu*sdpSCxh=nqnxD= z<29J^P?;m%Cc^JT%Hb@SqX3aeBRCWK&|^P_?H;tLPk$U!VIzbFR-ewpNsP;oh8=}0 zJAvy6-{O`3QNib>4TW@CzqA~UbO6JDnbG{O1$XtF=fl>yli<%FcFD$EuzpTHoC~7k46v|xW^txwRHtdIv=XOJ>GCDo*bl8lm=n5qjedn$Q zY{$d;N|{p^1({T6ESZ$dvYN;3BM04~q#UpRlPvO_=XMXcRpTbFFyg?+-7{rgU`AJR ze^BAm|IOaJ#NLwR`CXrg$jtNl|Nr+mc3$jE2_+hZXCG7qu9 zMuG(^V2w8*#2O*iutDsA6+$w*VS}&*0xTI}4A{&}S5H^ft$XkPbsm`!!6G6ue;Ft8 zoO7$X`u@8oe^>u|@(~&D*Z22|{Uc~tnwqO-!QOQ|c0u)hRW&?z2VSh7g^FsS)`qv+ zTdwORTXo>ehd0!b8+6-ldETt}cyrC0?LDn+d1ynuyxl+W(dwGcIcj5qm;J?(m#Zrt z?4JAfk>{&xZgj=xkMDT7yk^_&_~HGRpkQf&bHwM14GIkk8f{QYbGv`wldG4!+1_ED z=is{D`QR^Jf5d0^U-F&xbBxw}cK@168-D!wmQB5kClnm`YJbm*<%Z68+_iymq0v0H zI~=g@c5Lk)>pe?TQyI;h{T)?YXqI)u!FAMC#mB$@T|WE82aL9K9vZ?vqEZSQJ6@xc z6IzAR!Bv)bD(uHqU#2-_#WT#+W|(8sJer1790Q|qqmSSB;|?)4a{!n~ z&I2huu?I=a*tDo7rf-^SUA~T|xhT)q)O?oWa*XH1G?&P6D>MaVIYw(rns4NH`Pw7r z-85O)u=yU9J?0^w4|4`^-!5X#b0#Z?%y(S-Wxe-UuCV8j^Oh{f(fS$FmNv!<8fS>GAuJ?lTCc|Kd)AnR{_SeJ@zBKtzhzwEc7yySVy z*PB8b;ah*aAC71}Imv4W&!LfmPmoa^WqTWxr*oj3qSX)vg1yH?sMS&63Ww(@k*|j%%6BRQvWaY0QpSX`^!NhcK}I6 zCIx%91>mt_Z)(!jafL02pJnz~qKYDn>!WiD{9%!nHH-`rn zb&b&mr#zqU-f`V5*|v||?e1x!D*o2)xjk&dae?iYy$fapA1yZ=oaM(4Z`rz*x~jNt zmfRj5(MqAUqV*jwuAYUBuYw;vzTy6`MQOvk{UaWS_l_TL-*Rx4SL!#4FE7%WGN}`~?JU2G4DD2KZ)wPo)j_?Uqlk zo>S`@qcoj&bTMqLE7prO%4iy8s7;O5hF^a1DVCskTbU{xvDof;vtn z5t@&se10v>19C1*>B)IrXYZ%-s|weeRfk6ch%Am& z#(a*R(h4ojgLxcsz7_y?lxMQA>3P03=JTd3N5*jN-{)n_*BG6>E9Hgkxomq`Zkdi; zL-K9ZsrCL`9;V8f_eU8|X+4tfliEJwH@}vDM|<@M{-dvt0sM!(peLyKkJ9ii{3(Y! z#B1l_;PF1pnO-^Y9*4uVo`Z9AGEZ@z*`FYg_MO5kfXa{SQu+pPjQh4j6w@E?Ir2Iz zJvn3O>0(3eNAZ^Z^xE9$2hRKw`aaEdd-g91$rHxOiZsa6; zs;zPWP2oM^77~5Wu2Ga zmF}nN6MCVCQlQf2%%@L$pQY43`ui1|zguBY?7Nm_UDJ8bvI^kutNlGU%^IyVowt1d z<|SI2{&+x*qR|ybYZj)asVc4(E0lstYkU+#rSNy%9H_!0Sp)?_ms;1J0 zZDcfDFP5yTCLR;mva>Ds-43HP>t?~WJK(&dsVcPARLbD6yxTwWa=qd4aA4;GV_;b? zaD&E^@OKYkVRstI=abg( z!l??@S>EmMdFb{$x@Kunsf5`uJkS+ z!}(Y|7pKf$KF?kBYd0!o$TQw>bFQ9lwmCDni0 z+j)WnF6}c;fX^c{07M$H@0a#5^7_=X{F*FmsgIZDjj8sQ-{kwly!`ndEL|7&HAV;Q z((`FV94daNjKZmytOt#!wflI|OjtXOQK$y2U4QXMfA|mnMvTG(PAi;NSfjBI`zOCWoytgho<`~S565{(ahzPE zvbOTkscB!=cDU}7$kX-G9Zh zYIwHT@cH9A02*W1yOz#djM5mbvEK6I$DsQE(C&D?yyD<23sYf~;n`x%>+LOf_h0g2 zv8IbUzIXc_i`uZN8fp`4-z#JB9x7wFJv{Pqc}3?OulM&nU#z)1?1B_rfz^&mS3Fy; zQD{ECe$JNda$_nxb2$=zYgSNlgc&62Bn#Sh>9n5%ll^XV>j{xeB1QscMuBM-tXr2R@e4Jh}tly?RMiH{5OS=)9-(9gBL! zu5GDQhf#*jV!^iSu>KLF6xw!ZrFpTq0;PDi*aX{srD*B}cl$@^;I7-z=!)7HYHcvO zqU~DhrsmKcaCnT;e6hRb)6Gk^_P}FzU{f#IyOx8qxVUk@UR}}IV487z*s^JsTsLa~ z9=l+6P#eRlUh?Aaeae@=^Z(Fj(Ar}(80Gt2+oA$oT%+u;rYQy5dW=!MW}@~r=6fqt z_3n7w0OaE%o6At+Ik%sBl?d;}d>F@0KmU@AuYA18^oJ;hi+rxH#&ctzucYA@PwSC$ zQ9s_(KBJBEK-B&lO_f&6N%^`W*RE_XPjQyk$5Fm3j^^Anccy&j^H2YMN>-F3)k8nu z$uOdB`nt;3b0*SYyp}|4IJDQqUO=`(pD)>`+E@oFTSG-37*(xEyRR$xJj+M=X4GF( zo2GDbJ(2xNq$AVn+a`T$L>}5g9f>l?=c(K0`#5=@uG5~b4_DYbEC6c_w3@?CgHm*9 z&l?4dPI;6i0KDXTETTDDA54n?l>}NHcoPz_Em-ZtQL9>oHYHTLQ-jiLoRH~H3P1@{ z08yLCUqE6I$h~zZis509t~N5RuO))1>QSU7phx{y_BM830hI9K~>?M%WNm{pe;=t!aeaN-D82 z>e0YB)e)xD8i%I`wUd@^9q*skC6a?o(Jb5hhP_s*Or%!gN29QhC{ishz7Vd>&`% zoy-@6olahcsXkrmYk4?X=BYlDMd^JQf6j&9aHvNBXGxjM`|#d-Fe`9>anJqVZdq;{ z{=c`M@q=gI;Sb(@fzgJm#fnw6U~l(4U#{8OmPQ*|*Ye5L3qHGh&B0qb@37vnt{2p% z;<4NFz3Z1a?|HO`2rv~d7uRg)8gQ=3%?79P=Ts^0%mwbNrn$A0Rs^y{E@qD@AK1%!VyFDnyRkLK@ zwS2jK$IW8R_4*oY#~<9i=4!Fv;4Ci|*Sy}|^3mo6@3yxr>)gU+wRyl%_I-R#P(0T#u04zorb1L9Z_K-r`Q;9cxtV1-UrPwJhcH(RIfh5Eba8Xov#&g zY@J&x(m0aiT#nsE;oE$E%IB4QPRirU$8?@|`784&$z;E>Tq*9UIX%y7N@tnfm+B^W z{w&Lv>Ls67E6Q~Q!+b02CeK@*|0&AbC6!6u=lQ#2PS5iW%)2*~7 z=6&^48hLox_hcRAX`aZtk^Las*QM+JnPSX3?H+1&w{-w} zQkzfF;}73?Yz^R11dSr^71*HVoEQNnfp3p+#2L8(HZlS51KPA9pJBfh!+6Tle#2P? z01iCDA>b)&!T zo3h6Fkn{xdG<3AVd!j(ba?nd30Aj!qtWP~JdT*V6YkrT;ad!z|R{3Q-qnq)_A`Gt*tvU$>> z&6OsA{PZsV^`O6VcynNH@7Q)<@zCC4Rgmf!9nwoC0}28DQy)p(*Ek`oI6>RQA(vo? zc%5&;N@0o>c_HPS39(=u#a~KG{+8OQ^i3YF*uEFMw~X^#O3xI+OwpU8S*eex*lTmF_dS?|+EdER6nJQYVNp8Q$f4zf>|Hjbsu@D!cfK%UEIe4fUv77RR{ zq#kiPQYwyd6|XCJ_LnQ3udn#-)koYPf^d=!)U{oyWo&cD;0oq9n1|L+C97Oz^B(Qqy4$!qt$g($#=cxfsG7` z#u(Pkl4pxG_w9}st1Aw!dynUQs^oyysBAY*30tx zFxBrboolAT<#S6uCteEZ&i!7J*L*IVn(yU2JOwCaocVQ$!Ei2}QXHq&sHrvLl63R* zr@lRPpT|>b-*aK}_|NT2F5PQP#eHsVdY|=GepAX%Da|}gseR9pC+rgwXiU%IfVUmN z^LrQmx)wSgv_Lxt-2v31W%X*y^Iv%6>XSXq+VyGsgoL44l1a`ls({KWMXUBXb1%!Cu#0h8cgi}K3JLWzkb zku;Q-6LjK|YTuuSX*&iGAF~tY9fiSTOaT7s$i9F02EclUS39=;EnEMFoxP{^EvAa? zsX`gF9>9T0ZIihAI+&2?lo3BtED5SS=^W2&15jKOO$=)%_8(IV>D1yV7PnDwksmve z)=^k{oYy|ysXU7``tL7gY-EdZF^?vDcV;7CQ(U4fR@Bkd2Eb0`eJcM2;GLi4n#Vn4 zXo%P5@AJ5_{O9eOhn3%?x{%*m#@`mNMLtH?DGqzw-%r-tRJe(EN9~p6xlbRF9~kvN z9p|RR1duAtN`ZF6wg1xx4xj9Jv%kjzFV{D07OMbmoek>vwquSN5 zyE}gPkW2$s?7J3}=C0e(*$#`R^BuSQ2eekae!N9##fxPyBiP2{@4npKvbP6r_YYK+ z;jY`#dW%z@>&1$j#fq&x@X+o86F`UK_^uX9Jf5xH^ZV~U^@6)@ zi_w}l+qZo0=3|Uf?Dr4cw|nmGmV@uuG%Gr1xmm2ZKkS12zw5YfcNlG`s+v``r1h2; z>l>b}uA*wNVtd%JbqAiWuEKGASemK|8U)RPW!jN|4#bU##p;T^ zYx#Kd3=|xE$H8{IT0h5n$Cul;+$=WiZOh)b{OJBQ);q2jYpN<7iMX`~+Nj-e)hyX| zdn#k_cr1=pvw)pu`M-Y`V}dcI)N_HhQvaSzLzX8c*om@qLl|r0Onp^An{V=UaCdhpPH}g4w<0Z8ikIRo z9^9q4yL+K{aZ7;~cXxLukmTdH``_JfF5Zh=p#NE8?<#pkl&w3%U)M1efvOP$)$3pwpBKr)vqT)+>xT2sn=C@ z^5S3gWzj8LRu*!#B+VS}I@|b}BsL-+Spp7O8vo)`0-udE4(W_5i=cQp7J_Tz&e4ua zA8POWBruJHSh=n){ND8gB?6sRHDUQ5CDtFH zGt+eQ7`%+>LnSP~FCPAIF4^OOhZMRHPXuwie-6(D_KL;4iw){Q6YL*GWYTt~Ae{p> zAwpF?W*kd5CB{AYyW(rF$JmbO({=MrY5^ed_#}=`Z(ZY_1F}l#3_r8)ob~s{)@vVRxhv%FeSJL!J_#4r zm7G~WW%18c6vB31iQ*p=5-hV?DfAo0Iuop-U>&*~XpValsC_Jxz+Px|9P4%cVwLfQ z^6RZe9!~i69)u@VWX@p2=1hBBpV(^qq1KLoYg+4W&H8N7m0uGKPI1}{G<|o0X@CFh zq%%%f@7BnA(6D*vIkCaOYjb_`fdV4lGYqq!X34%gK&fcf@HPuKsXVio>)!`al zyFQ*_hIt02H&gpr5nq%{4LX-sUmNxLJNT4?y0xo0Xr2K9!*=Ug8p&TGt~A3M5{kNP zqL`|!I)4jOq=?{bpP`+tLqwo%;0|FD<!%vDDM~aF!UN7kE`z`VoN}I*Q@qb@hYd=Vg zrbTmigDpJxj39jCnhRpIfy>*s0o^^cel}}j*=b-?E#&Vq?sncmgTp(0ZsYob#*DKm zzKF}Ec$!T>Kuh8>k4fQVk3NZDr*rsW((wCNk|X?K=_8?XJ!z=~cEg*Ap4S1F$Oi@< zvjpH^GZo53*a!s3lVNz>G7+Kq?3Oq|{hV@nj#9+zg5GP`V}oN3_3o6hkCRp4J`!22 zG!xJ-q_3xq^pWVxrdJFy>~jqp(S%dZR%UFZW*p`bo~LRJrKC|U0;!2D5PGFglx>g9 z{BC#Dn<(#2H)g@?hXX!aZmLXV;`)4%zJ}~ZVpy0bya-{U8R2xivnY;JN8Dca>a<`x z6vi{@t(1s3j~@>+kTEP!zl0TF>5G~XG)pC&>0$Wb^&iW9LLaoLPMHiA4gJxLJQF6I zJX#))QPxWL!>}X-rC){1ReOcFGwR}NLzW4iv#gPZVTn^mxpcz{j=GW`0c#NZ7zcclgnl&CReh7-% zJ?j-ufHFm#cVj?`oy`TBk~OwMbMu87YE}x(EfhA6q!(Nzbm;SWXxiw2%VBXY(dHCC zP%b0rXv(OUhriKr5`eNVWTD1G;*oeq5?+ZQs5fPd7qi)aBE@6w-w82D~Uz$KH0;hKr2h0JpowmeSegz?!BXND? znqPRm$**loac`I7)#%U4beU17_g`=lPEX;A`Gv*=@};^J3K!qb&n{m+-BT$#ro3C7 zbz~#(ga3R?gtJDi@nh(7m-Px&h5Arc)lv%z^;(eW!u8ZOIZ~o`!`>@$#R$>dOn&ih z@a`*IDI_#`xy?*)8Tkb$K!hv9Q20Q^ zYphVrW#jC1MiAUayS_xZ2j&kIS>u z{Th1q_O_#2BT%r%@6`)H19|&r&CLyh$@qnaW=Mujo02;KyXkadrP? z%~-ewn{w`wh97a{T=qF7?cC$3ZpQbb@s>mj5l!)wgM68q&OplgH*eO;o*4F<%`awh z$|A={!@c1@=Gc#`TgTrteEk@ZvZuDEauOSLxYhp_QZf@hU%@Uh- z9{n-CUh4I6g?52_>5ILYhGPj%k{J7PV}9$jk7Ky)k#`Sgv^V$JUq6!x-L6mWmhh+$ zljQ64+EV|O60adM#=I<%ykBp;?nI%C@1L5?>=T)_j4;^2-b*p!cb@wsvKH5jQ7r99H1v*OZ!h6qx zq?*hpG~8|Zw%m@B4yR)^Alw6Kb+A0vM!dQD#`CFJu9tbo_&|D{Rw>s`Ha(A4?0%1M znvYZ9p!ReKq7(%A8xLI(qR_z7&{Y?5t%Or+!W2mFyh&Do+EM6+6(Idq6ojQ^q_6&& zuoB%N3EY$LwOHwM25DV<&H#Ug>1yl=94VDQ!9ct^e3_`(`ciq`9>V4aBN5WF3jWE# zhwe#c#VQ!&TYyCxAwg71TXWW%2%uZD+E<w~E2V@leF;b?vn~#qzhMg+o(P=!8lmo(mzP8%w zjEzWJ&zco|t;+6^V^d*gUgH%Kl6!vS>Bq&WANJTnmR4a{p2sOTf08OvcRv1l3b_E~ zib>y11o=OcK?@cu7*!WO<4}=LctS9X`B%<+U*{hc;7FmhAccj7Wl}-U0lC(L(YKTH zE{@LL(9c71z6F&OW)~Zm+dhB$T3Gl~A3C?N*6xC`9ij|Sh*@eb?Eb=eL^z1ndC2LJ zt)J3dmhCwh>rk{EP%X2+V|Sr^T?1B=W|6B<%$;g!y*0Hqb4c#|9=l~Hd5$lj(;sv* zl-2yjtLtLX;5~82cFQko|2Y?@x}^@3H1@ym9~cc!1k6vrc$?KPQh)9IEX&+c_Hj&N zLI_K0y=Tw6xvY}#VL#rcw!y=1se6Ctg#EljtgafV^_fG>)O`XWTVtr$e=sY}!9YiB zv2ia^sZNLj`z)^+O$lJKaCJyiF;BE^T<0-l`pc@+k$R;dY*S3_r_p3{v{H+Q0jSU8 zG(}0lb;*qCRUgqN2g>a@X!ae8IWTNVxI$g`%LPpHr2M+VQC3c>z9*G#W?RUF^NkAI$TE zYRQ(B>iUj^{&uwwtn&AiU6)-}npt-#{NPq)uC1<=)Rp+%QA;|%*Hl*mP2N|mJjwun z|G+mRiHK(ykUL*S`0cNDIR+l*9$KOq)QsnRCA_eQOxFA^%u6@d_-fVm;r$gACggXd zPqJr5x0YuiX}wbAhH7~t_vw2y%g+Q4bYqK1IKCC@mfu>1A8_wSsTOC=d})SL;xR+o zT8`rjj6b1bhM>}(c&h!hU+Q`P1BePT$sXNt3SFlD>=B?w*l2`d7olRim&LO95X34i z%Ii|nz&?s4mw)UNWC<3TltQjdsFAbkx-W!(?R3~ zFR$L-?Na?)wIcd?@X^eS1)w+4Bs5tFP0FOm2P4SwNSdS9Q-)Ez@@8>%WT&}Oqy>wO zUm6~i+zsF?t;Eh=Ph~oAr)*K1i7IIZby)S9vt{cvoG0zJ)w4_K(jA}hRLR_LA@&-o zDnykejNikr2najec4Z~%QD1O~Jw(PG%-IoTimO^CmGq$WR|&x;D_k_$dLrus5ULD4 zj**YkD~?4n*zpYu-Vxb_=rZ}}3B*$2_U@%|n}4&(wlB+EkH}ff@IHvod+tlK)2@9! z*%6i;-A;+maYs28TB`4aj=xGkZdKKZHVD{uMZc7#2q*3C^dmbSPsW?$FJwn`)-*Z? zXrt;y-z*J2Yut+|$gK#Q4L^(Km7nFFT)=(t`{AwOnRV?~6-WCvHR_@x{$b%tXecp& zvBxEM=!Q~T6=ef8a5`kV=-tkYg&+m>sAFW)&9w_ zDb9@fH$S~jZFkVwzM4yb0iQ9v?h|=V5Zbs>98X{9@$&DctH2LZ-DV_^sDFO(6n+4M z)i|<6)$Xs~#>H_}jZp*WR?p1C`;;SP9f+Y5@Gw0p9k<%DHl6bG@@{t5K26u9?O*!D zBZ$8<;S@ufB?DoEW-;HU16bVXHD+_q&pLh1)s@?B@5|)Q?KoW%HZ&1t;kDJ<;zWOkZzK3cdis1Z5JKKd-at2s~jXZ#Z7%j6DJP z{_@YXs*)3ZriUXkXzct3ac5k-@admAieO?DnF>ZLKt#}$gc+9?I^9mbV{1y#6IGrq z`WqG9QLCrv(Bm|^!qluHY%Pm*Rus}zRr1c+3; zHVR1C&fl6IU~eK;U}2E8jlORWUDrYMsAH3T?G?xL#j~Ns01~cy{_N;qT-6n4ltP!( zeg`PE(8J^)WIqf8!cfs%ez-^zibOQpW;v6D%2~VogoJ)c-m;+83k=M@6sBH%0}_{2 zW0ioT2SQF!A;$Eyv`=H^)1hh0@UoGZx649Z!+a{6lsVaQ{8+!iItJd@QI-T$BgijR z!@ai%QxUd++l)V3+)7z5b^- zJD9y7Mv21!J*W6CFT{V)4RzDJ;<@}ycT#lLd_in~hZI&dFh%YLhv#rFR8_f~4xTpB zfk87!$JNcSVi0^vKpF@%HwD`vu}pTN<=B_y6O&1#-4e5J#MA$fgd?6=5vsBj=<|&B5!_F=%o8=qy_BlSe|JEek$d2A8oHSlj6d> zw#-b0Ng?v&{QmYfAi?O0aHV9aew34w*RJfN1zVOB=#*I)IpFl*KuD8$4$k zo(8WUcaTg;G~j8g?omLC!|a_pMijGvPMkl8MG2Iq1k%>A6y@OrGsm=t7}*ZUmK$h+ zAK*t7Gy*X~6O%pA+Vb&#Xj+!BYMjLSY?UEGm`h%ekq>f?GSNLVY8rBI%4lZcT0g#~ zv1}l7Q;MG#va~}1JphBlu8N1+fXS|_0+EoQRShfYX!^?j4dAVqbV;LsC#v4@50M`e zNaKtK>G&FdjZ4RKLAgpqLIz#s!0--Idjv13mV_ge9;qZ~LgzEVS<1&{L7TESpG#_P z0|*kR?B64e{%rjw{CR?~0-rNt9h=hw3F%%)_$>_oT^IfiFR=+LAvskWVxWv;NPmGw z*fcWL988Dx!!GI3}URiyizaxo~e#T4nT5bW0r1`1Qs#Ncds_u(XDdHwX9t1FZj#L#Yfi;eB z{mpRJz(l#5iS2YL7CU>E6y5^Qlu5hT_^(jB+lMkhNA%{a^Vt-O(?|cjozWMDGq`=M zU3ETxQ`PMF52i2lrMb+?KuSQVuAFb_SGWH35FXIk6CH~)My_X-6tYpl21qe18uF_r>g>bh`#4|o zH9XCxMrudGh5XYcAkhmvpu%qB0%ueG6TCilX%YLGW?>a==EAblOMSen3XbU9;}RO$ zQ_9h01(b6xJP+T>-5Ix|;Uhz2#V_zd6sdQ&msl~k1Tlko?u<3GU4I%_ zWtEORBVv$QQ~Zzwo9+#pwC77(h-8H)Ll{EYSG}FjDuInGnG;cQA2VECELqYe=SkpP zb=ubUPsKyLYdlfQ1vbqr|C*Q8!nOdDP8RgH-z1Xpw>mZq`&b?8>wDQ+gW-_w_%Nb5B>^GN)yj4bYK5$~*A(0j#lzFqOwj zhcJ5y&OIHwQn3QLuAY`bt;9TR1Zlw^i22OqI*b$~BEbeJ`sF(?L?R6UB9Vw$xv)mM z$EaG{y94A={Z;Q}g4jS-(XWw*2`BPMHbFdc8Bn9)L)IC^5uomaRIwT~i>r@~ z+=K5xUH}^me4ePNa4XBqqc{EWT@*HR(6M(9;Y&Qr204AqE3oQn$Fj{jib{MgbN9B) zn0@DULp()fH?m^M$qHO#L)FzAEm3qADr;8I}Z3tH7y^{K|Qi z7ni6|$#S*QJ3XT#=1zU})Y^s6a6CnRo3?YLxxkB1Wa&{WQDnU|)&)T>QH#f;nb-0g z>+pr!I`P2(F-KDM(cmMxaJUtmfPX0kSzqOyxL|bCx6G6*=A<|uG|(~S;{+L^-Ua9H z1gYX1HxGt_zjn&0*9%nAI`SWs$%cR5KIBI<{b^V3_&{XtAa|Hgy zWAj3T9_k&OFwIRV0E`=)QWu)r1Lwre_L3F^`~uY;{5KTPFDx>w~Rq`1y0{@ThX?B6s5s~81I`H;!;`$nCynx!fyIJn!BOy z;;>Y$_Gbtz<^HlbaLo~E)Cxaq7QJZ)+`e<(9B;Eny@BN7+y$Zx6h&QbLPvQeAjJqt z2f$%?9V_v9n?3bs2%m}8x)Nw5UG;>kkDIXnLy-h`>i%;{H5rw^=3&15e;YDKxR9C6LI9C)ZpG>3mG^n!vk@ z@H>#IL;pH|J?bT8_G3&p;G=Nm8LkzorTp4`>}y&F-&kMiC+gX-1@9!Go;vpWfI@yH zB*6+eCJ%*O^<_pcX0ckpjz*GwJ3E~uaP7P@Ya6dd>Uu~=J%T!OJ?Kh;g>TYjNdket z1J6-B%I<;spr43#uN?mMNM9dE2g%!Ii(?vJx*tvw2Fa<9NIC5p4=WrJaQd0gdP{Gr zkHYMXs6t+6hlg`q?vQF2T>=m$W!VZ5JDi^}KyLzhpvQs=wNKJKSAGDMpABf5KS_)S zenGRWPB9hmf`J~G(HNZGGf1C}d3e`m9x|UE>J#Qen0#nB;OwXUDsOu}`AArjn+^ic zF|kQB0d&2wyLOUK0Ob-riW1b~5s%RXH5-I!vK^E%oC(19gR4B${WX@u0#@3-U&3W{ zuAS=FDI?ygkI?r^I%T3Cqjneuysu6_MCAKO3ec)PvF@6tE!b`A&<^}sMeH3UxUV6q zf>ja_#Jlz5QzjdQ4HaK)EtacJnJC18d?w_4F-CD5{z2ivqcSkk{c-)a5oo(|vRC%F zzDY(eh?i8aIQEDP01|}i2n^!%n&UViH@K^e?=2vKf4=Kg|A%`2>oHTL#^^FlqE8*> zk(j@&Cc3KfhPK;XwpGj?fq^M7X4_Bca&&{8)GlX%^d`329D0%=1?KYdf@~B!i4WCZ zPa^RUBWE3OM92|@ewMTYSFzb)7W6qLIP?h>)ZmQxgzFiK{;;utUmT?Tt{$ZSQ6d9X z`K^v9k%hdTG%QC@8tqN475T2oqPGdb`&;pgvU-fGw<1V?WWbCC5+YE7c_A zFe1|8%l+2H6Cm}8=UX$~rv;YM2&)$ls#2JzHcZIVYXbooGA;TwWATy=0eZE>vzhM@ z$;`|9&!zvrGzFtgi41&=pUO<{7%`?Cz+;xB=#|%ilj?L2wbeXV6HpbZk%S0wk6pOJ zB4CVSf zhN(S2Ovi>_rJ1O1YHj7^h`6w}u%Ugp5x1Ujr*SSW9)FXn4v3BNYW(yuZ#PbN(^rKo zAJyLr84zGmb$5KqtPVJ~fY&~{te#H9G$g>y_&(t4sQur<#Y1ufWxWG>^E-J+13am*4O`6s}AtDYpFW-!P|3YkGBFk)_ zQ|!qo1fdei#~w{Vp1g_#HOiIyoJIMj4qIm2O{zNt!y~*#9UGjmB9|)-qQj2L7-FDn%JoSHVzJU!)sZDuuR*%FNbV z$xP?)a)}wpXHid5UGH*CM}Y_!Yq?85*b0|^Q}hTq}geDd*DORNywWZ zDFesmIf$^bx+#4YJ(O&qDT*GzK&cYQjUfyV^~q39@N`s-4V-?id{#SR#XR^AF1`{b zTd=6&Z24fl-XFNFN#{XS;bOc%0&I9j0$Ts#sZpf%%zH0eqwclSmy`c!0Ul!>AvoS% z5TQv^{ssYb>poVTf_uk zqF%QEnwPJPl^oV@U;mJ1n%-~Tc-KAHw22r_{wxtQp_^$PkbDL#!~0HI#2jgV*`+0_ z^aDD{6ZnQ2;dSyxs)Lgt$?xwq>!#`%{98Vm0c6^-&~SzLDAZ7V;K0e0aDk|SoU5xd zvXL&jHCrI8#N3~DZR{s#37T1milLVM z)PckH(=+rxi1?b4w`bD}Yvp^-673gb`8r;ji?(dJ{$s+_=>Y~@EEpC{VQASdwS*OH zc1;ei{Z-HC^^#QZjU~qVvc)@!`yHx|tJ!Kgc2oVO)?5^V7uV+b2icZl0>aeAUm%Di zp-bWe$+P-NOn_>$1jxJm$ArjgN<_PMxS>Zhr;1$^GtjQD3z!xW<#zm-AF@s(ZGaQx zyPjat>t_ePBnM0utD*3hiz;3)NQ)=4QH(&PMCxa@sTvS=1?iYYYq=v>t1EI|HC!Jj zlFZHVmFV)h-#v5L11QD%A}!4AjA8{?{9rwII(IZQR9TV1UK1@V3N!&a;CdpaDyXBu zR2DF9+&s)VC3+nivMc5FP|s5q?p3QEHgAwaN$_3BDhl?y1*kf0zCD~n=C5_>cl^I3 z%(!r0^o7mc!#!7{>;5s6PtQJ9fPWOi&pbgcxcZJ_=w;b z`VAw%#u;6H>yZzU&WfLC54QN^&& zf@q{?3O)g|eb)2-l+23X%1kctX2 z2ltYVUWyM=3WUAcgb7H)RKCOH7v29`ZC`s!zG-f>YvxP@UO#H4ERxc7((GY(`G*NE2N$Q$@iiBo# zJ&c0yy>^WUUDCg;op9&7(wU@-9`HOgc`(Y>bDue3P|VZ}T1Mt8UO!P}ZG-4hq#BP| zU(89fv7ckOxUP@5?}i%7r*m1wk1eVWI^v!pZz*!2eOa*D4wy zX$QZX4se|yRA!;w7##bSi04v*B|VJDUEqZKsh!R&j)n(L{f^oDJ_H!^3-%y2{LP~H zxch3ev$VZ(Nyps*XXVM7c>2bA zs)Df+Kr=l{5Q_mvQ}!HQ;k~X2u#VhxqAN|9-0&=;7CvK zYIdy?FzY+Qwfft&vuROzLi}nstRQN=JbS^KP*utRxtCxPK)drj)MSp$Ww8lRRz)p& z)C{p2_-zGXGz95iFkiJ23V*=&IG{QwEUzkoCV?dGYW>oI`8>F)4zThJliuF8Sx`3~ zvK5iSfy5|Y6F+@AAEhVxEDrVxRn@%qQZHiQ~M3*e4(OBw)(PM z9T_^u$OfyrjE&9CjQD$$wB&4q`p=l{-!~ERd6MyUA2+tJ>SO6r?{D>jPvp4yxx8r7 zC{BP{G}|G@de^=j>0%eQfMUx#2ry~@_SAZ18WET6zY`)C=wKzh>chRoP|sHR7F3L& zGo{r-4hZ$!S_VI=Q)+mrVr(8Z0ldir6Uy-zvijNdrVfm3U#)QFU<5Wu#L;Oq;WN{) zsY;Y9k0z+{kOv`cKZ>-vdnn6t=jW=46N2yEQb@?s?Xo#I0{d_E6CL+faFyp|`>8S= z1*>{*%K`G68#MlOhlg)}D>KWu*O}2Dq$Pn>)-!L~4^!r)RZuy^zG5be`|m$#=1y1G zfq?pEG{@y{#x(*6TX$37LLxA7Gx#jA#k^uXv8^dh6cmGXbqtOeSB@v{T~+6RrC27} z4QD-dOuazo_q}_)K=j-6@Kwf5C@bdN{|EKsGmJ?+tiNEvxGMQph`(*88E9?c88)f# zerbXb;uTiV<0=DE_xJLx9`=C+>0u@~!-ct)kPlavOtd;$fbEu>sr>Y!GjWYtc+t4v zHe99O?-CHoTG}KB-OrVKiS6qplh7q$Urk zGFZHMAI^8=FN{8U=*3VFQ#MWZHOXe0$sU1kHKBiRBb8+#h3OZQFH|GNQEfxDlNeZj z?Z6rW^9X%8H0uWlRKUt3OL~Mso7aW-O!(s=eK_KU%+gxY2((GjL?y#GSX)LSeuE(m zAtxSGpt!Rl0%_x-q~!>^%StQ2Sl+GHu7u(HkQsm}G9Armb?I(4RBF?^)2p89-~AWjq~++H;qV} z1t_r{A*7`em!!1|Q_Q~j0eP?g{3Z2xMrA{ey%v$8?&`A^$_( z1zDLJ!%mFG8(%UIDnEi*aG*BW1qtf~rc5Pq^GR@_DZA^2xp)(#o~yZ9lLLxhY*MlX zes4hAUpMNKY7MTj8^jD&5wcXH9Ly#IHnfxwXmx5B4kG3?u{o2F19>OEYC8spbY!g$ zl%DnO9>WN#-l`yMemvJ+SDEnK6+fK?WJX)D)kZaem`OqbkSi*c8#oJE1b7$5RU{DB1}cqpl=>mz8L|tu{vc|(6TBS9-U(ot{bbY0+7sopeJKI=7SGd ziHi4^Jd~~s{i8`R$Fohj=>CUa`0m6q9zj}x8T!iPu|yrMi4A` z3Y$&6R}c6+>?HVy%iJ_4?# zLF6|ttqAON)M4>hXYmpHZ0B1@7xa{zn+@X;-?nq^HkG8spUJ--bw#;WA(Mum$CJi;k-Jnt2~IBxCFuSAgIfjR+5aAxv7^E_>z`h49zMC0UA{=l7Hb(`M^$#40s9?u9h^pgH&9fX4*Oc46pt+TMP^9zc+!T!^GC?j|mU{sZ>qL+2aCz8 zO4T4@M$ux%8>PSxzhgxfGD}R9i_kQu6g);(Ek+TgyCOQ&+&(9=wYAz_dDUyjA4bqI zlD?K5-^Dx3%&1>kCB%W8;lzZs<*#B zfVjyr{y#F{9I?79avM}udST)RjORpHO&?6De$e7W*5qN3EXkCH^Pov&F~C{e6B|Lz z;6`&{+|35D-kpcZN$Uuazj3RnbPh;f%ifUtMjZ=Mz%nP-HXxjS!ZmFyx?i?RCFe(E zsL)uRu_XtNT8d(lj;b+9Ck>~UK&B~81p8)x>Rshd55AMBW989_e{kbCqNW%<&vdb( zi^tp5yYrjPHAg%q3D_I-6b^* zz@@5Q%`}{e) zrCu>6G)Mp$JgE} z4mI`#u7t{$^Y;46Hxa9}k0R@!=kNL7i>*$jmB{_(AOwB2gdUXGKz_5pY_BtqO9YoD z4#`5+&ssN?@vK_~tatuTZ$SpH>T~0a7s-S-d)Y_6M*{_3pM1_~FV&xNnRBMKQ^cxd z2G)YcMF?mj^Nxlf1@Ht7x z=`5mmhXYalcbdZS<{Ej<-h}?&{`8?Ihu84Zh^P7_oNN{p8^jP3e65GV${PuTZ=|?c z2-E?=S-?h!kR7vA`E`_b6?E)D^p|F)6yp_4bopOelYgI;dA?@lR=PBp(c$^&o0Rbd zWGB}JEl15IzdW};eN^x-;pGM|!6v@HE9;cjMoP&;c?KX!lN$~!@x=zez@U?IAz4IB*PD#GfDiEuZP~rGMNxIuxtM z&%<$GFAm2q2YldNi;H*pEo?FpKM;+r&%Y#vw9ok1YV5US`L>+TFGx^#^Hf9WqsaN} z$ktY+bH%hgF8D~>nw~>01={MRFRtWKVO~sZbcdjmCnF%u9>*jM(0^kuW!Xrf0-LCW zn!`g4h!t%o@`pG75nug_H3UF^Z$0<0vIRZrdPzldyzFVIS_bF>iG6vy^sFbw+*R4P zl0=`svTRVK-NzlZe2UIGsa5NnA4GmRw38Ee;$s6HEE zL`pe*co(WeYzA5J=KasJ${}FjWer;foahqPtsJx9Bxi7@129ZfHsP+na@9Qu`*#YVd5`}3@Au( z;f{s+SB~RplOlf}Xrxc8ombP0;^23cY^ik&A)ae!P6aq5nU5JB8zFP{EJi2r44M%HF5!M-|b4dtn(Hi`^9!Q}vceyCsOG;mf zGQIjF)yk8?BU77{FwLo(eHb9IAht|OTO!fILd6~nFoE{iAXc2*5E&b60m4tL4@|?u z*8Evo~NO)16)Y_JAKqg$ODRM9%z)lsCQ{Uw4et~%o26B&IAy3z;ApovJ{95Z6S553lLC&pGee!) zf5Ax~ccgGh$7wF8jsgS<*jXcxD3CnfJ8*Roe)wr|6x7~=S-`Zjj7s}P?>M<5g|0C2 zZs82e^%twXacqnBI`b|~!ZvRLVGnO$s|LS0)^{2**#af2H!RRdUz|mTwaD;5qYP=; z<>Z$hnb~Y(SgLCQE$pbMsz%atlLlu^YG3}3JJr8~8O!|pV5D2LRuyG*BVFRt58<@> zze;JA2x=cf&D!D`v#t%<4n4kARRrV_KYRF#@bMixRA#rWow@1W*X^R>?sp0>-;P1k z)2IdEXOZFb7m9T9SCz=%I^Urw&r1fA1uzgSjyrk^^nDQehUZ0{4_!S zo^IVFk1Dwur{ibnLXHf$^RMz{8eu|gIa_G1N$QJj(zH_jG{tgp`=E{0udt->Rbrhf zg`qn-r9?lpuL`AWknPk5%buIp_{~ zSo_M08V8h(0tmOj?8EH~v=D|bkA;~hpR@*~Xn58!!R{iB=wR<*Oj&wQs$V_+6*;`| zQD^4t#b_NVtSY~L!`_DijiK6qUK)^tN)x0s)v+ovDAL2>dW3Dg)Xq<41r(bV;#Hmn z`fx@AX(uPV?X3do_(Klehg`?KK#G9&x_5B;Ec<8OqPukG(YnQ%Cnb2KUG4A{+DXd1^bVRP)FAwLLW!ht6*d{9EQO$zoKPZ$!J zDE>+0bk^XJ{SNSm+atFh`Ml1R6AlMpF%Xna#3ir*@%8js1m=!VT?Em$hbcCc6|cYh z0r=pQ_;Dbl)j(#=%)e%@w!RBidt&`vXd8l|07z&``&0^TTxbM3A*w{-_;;5Y0Z=Hi zHKzkAnYnJs9yGqt_wI!&jfTj7zKaGj9VPCfB;E;3g2*&3VC2X38^})=3_ZpFiXH49 zX%!bm2kC?2Z^f^hc{jCnyVT3L;`i40p14NZ-b<8Y!M`5{41i3k(VE`j*^wcf|3>|R zT#GAghiDXXzrQeEw`|n=N7TtNYZPKB-q}ivo8ymLa3U>iyN-t|tG|9e4*#V`ZpnVs z00jjX2~mkQt%A9+J2-5?924`-dU?K(@uiu*g9*YeFHu;UNBm{lD-R!&4DEnqVR1o5- zEEmTczc-cCf5(z>|?w6#?henFnkKsp%oXm@Li_-eV)*%8jZ{1YN~!{#}z?O758@ zFLi!^jOlv^5tX@jEzA`+d!{RAX4}7^xM96AZ=}1pR@kp$Hk<&{iFdid%80{Pmg>*$ zlKl_|cG$`^`@Dg4y*i$F7bbx2eR+Cd!63&($#lynvlT(tWoW7A%w^UGQ!}zP!^~4YeFW+Xap=A zxEU~2OvS+WFl>=_jTo9{Ot{+b z$IZA$0*B8{IrSX+(MS4oqmKBD(mHp`j`-1^is4@|y~%BdeGrWl;1|y8>-!WF!Pc1k+#roXS-e)~7rQAWfh z!LkO^Kdq8b`3|Cj-BwqZ@=y?uSbJ2~uDf#a7n6V`Ho zMd5<{LP5k?(1|D#=+zrnW{4&R6i|q4=XGRgi1eMe1FPc$ho(6nBA|du9hLiDPWllo zI;scZt5t8i-VGRzyMKIP`+Dm8o%3bsph#PtY)}o+%pH~oQopaqFB7_H0a+_w4UQkO zF1q7Q@A%$Vq4bzh1zmqrZ?&EJ!WJQo1BY8NUXA<#)sFKly;lt`Bkm%G0lr43)6T&U zj;+#PFN)71Gv(u7^to&Bm}~A_qh~I(sA;+MVUO2b(#p{y{qG+fvR?w{&$RP3RUs;C z)3Gr&BvOAUIdL&)l3+9x&po;=$*}l;hnW0|Z;wCJXuHQuGCg;;{qB2U4Aq9LM&6yc z=5ck`-4ZRX0lW#Fv_p>|awHw#w;Cq0L3uYQi*D0RF>s*k2RA%F+7%CVfT;}!ru3-3 zysb{N%HGJZNGr;ax8G(@@F}7Ny)eRJLk+tt6T!z=>1qs8g>~+bPLGa`IO40}m?|ZC z&${l?uQ^2xwI{O&t>RGjgXr%U(Iifs{uKkNX06};(k6y&5bldEidCt80=Z8|g0y$O264IFKr=wuYqB3VQN+BOSiU|LoPL zto!G%iq!-R=(*&)k--yHTm{IKf2YGvUo_#C!p~p7r3^5+1i!68v zN>^BrRl@Qfctm39XFVfe?p$5Dj3C;ZG!V97qGaoIg-cm-hr=?>EiVS-YKYYr8}^NL zV?aG5?-bh5g#+CA%;GGp7288|UP#*do9N-!_oi3Qt%vW2v_I*7M+@@MsWt9M`r%(> zqA7r-5Sj$}R%#}lYm+*p*j1Bf$WU7~kzzxS1pia{UQI^^273^Vo9)M8Pp%A!e%Axv z1w7?}I)DU-gPBksskCuCTFnWUManO9?oPtOzcIaYM7htx%o11*HIH5LW%ZS%;Zb>9 zn@MKl^4bqglOSC;8JZ?#8HfTL8Sr-$_p>L4FdF~&3=nx!?_FTiN-HI*K8VQgzvTyr;QLVV_t&x)V+!$&ZOOz;s! z#53{oLLJ4cM2O+r=D_#&zma^Rnrf8Pn!sS0?6$5YDo;4GQ81+9X$AIiH9Q1a)dClO z&4kzQq8bxl_^-k{KD2Uz?Uj&+w#n~&&qE&G+r{46v!!$X;HVdGTHj=p&)4pqu|hB( z@%t}yKNj+~+{ak1h<-l|%|2*`V-$HMawa~nP7yNTig)Rh(QRo)P^kV--IC}~&dUdI zu?Li1FXACXXotz0K1Gt&y5TN$weyRRKXNl_ZozYNARp0RY>Q^en z@PMS6s&Gte<3!CcL`hZSeuJnU`5koQAdT1ml?hg3-|JsNKMfOBG6&q7Yt86uV8|*T z&dZG;h6zNG$b7ITB|I|MGq&cx! zvvZr+gvMMA|5yCdW~5koUqK$hxtXtm{VVcO6aoedLM9%JAQ6H>Bv2KokM5apSNAh? zecPNwK=xppcp023`gmLhvU2pE6U77qCz6o0+)~E|MX0ZIf~^Ix=|Qq~PQqdzw+qOm zaR-bHj(sK%*;C(nNWFRBaj$doj?UR(sBp5oH+R_mC^DUfCnN44hZO1dNYmwYv!gBL zqM}D_H@QXtqp`TqDe3DFY_fI2z1L2bnFq|VkvE*KM|^pHpNkl6JbkCB5IOlymiUET z$j34lvFV1Zj=_d1)yf5RY^YA8hd(TS-?SJrjCHq$sfF4#I3}MN-LreG0>m5HTBwe! zFs+qeu2`H;$-ZRY{THZw4VG-F&X+9)L6_Fy1jODV=jCTnk&@2Z8vt-0(R)KS*N7tJ z!dFKH`&9870kNcz&GgL84}Dl8Vy4V(1!!yK5xap@>czzFI@W@^a7;85kYVJK9`_PG zJ>_dL=wvKb*0R71f{h`JcTz7QCFj-)8!8(=Zw3VU{`gIGGRM@~b6;re^RU)O$AWhe zZcXV{bt59zvi96Qra+}ONMAO7aJ05uQ@0j}aR-*Cl9xHvB;FMI`AGm^=ijr&aQ5uH zB?n%7afVNzUM|A5F3RJHBEqVfUS220soN!%ZQJjVRc1VFuS);|(Y2KzD{s*#WzZU% zYk&ZwkX66(Ci3uN?A8R?t^cOBlZYO*K}>u5 zk9J~UVA3{UPW3`+v%EyWYxA|N1{T*X>3k`C{zW1AwF>8eW*&x^m|gzaQ?IU`a)TtC z3pXvA9L(JqG;Bm@H(gsqBHeEH<5K$oGUs9e9241UR5t>(3=&pDvr+<@n&?WHdjxdu zuPNBf93mYq^~@Vik0fLaOsuIMx0^vNcx5A4RSqt2oQ%uQZV?&5GHU!=@8_h03P9{wM|9$$ z204$XW!>WzEZe9o7k-7wMAfR8PGuss?QOmKzKeDKjJJsJ!_Kn@mh;m>e%4v?#!Vht z(yjoc&qc?NC&BIA6KhaC_Iha3A8P&H-s#W(_juosj=ZSjIZKoJ^IhlcBdnj5nuvHA2-q_Y~9in5FN1Ys=zgLreVujht=c&oti z3F;sZF(jD^eLoU;quvpRh6s_vq%CMnL9!1V?!gg_`G3LDj-_Uhw?2@``@m(QXoohM zD{L{MJYz-1S^eQ-L`#mZ;JM&br2#u4qr5nko%*Hmk+<`(t^WrF2V%;-b(N2AU8-0` zOa=-_>r5y1Pdw<}J!zYE{CXJB`n2|&8OwEHLR4^qS6`WIu$s)Hbvq6XNq1|l_Ta$L z-`GY^eH_ed1qM7T#AXSL25#y+T-Meyd=cSFJ?{OJta@!*|M$RfvcWLk$_STkszo&% zNCax|nTio&;*VCuaBSD(CA@3g7Fsn|7&DEg+)I-!IaJcROVlrzac#Rc4OH32O@q1(rC@seb zb1!{r^)pe&k~K6t(~y7}^7JGsCk@2Lb+kD%!S&UqQ|`VIuMX%HM`JC@nWq*)^mdyh z-iTONmqcZ1ak*p%ecSvi(IRdV4W2*tDf?zY~f~&dP$x;h`jK^hvJ+v4sV$S2Y|0gW5%j zrD3vIG*oWO-9pQkFW#wzCOe;5$vj>5!an1+n?!kKpdM`ula;^x#Ibw+!nfP)Yd^Qv zXLFg}={8wZoqGQiePdv&fvxwv?V&@RIk+OX$ch#9Nsx$NMI{#yn>U~(t74521xNXU zp!c*TElS}JEffKv|DpQGVGS>sW%6Ce zkPTgp^SK9^xow%+fvw-X)fo1y!}t4^J^451&=Db>$h)ZQk;`zR7fNEs^vt|(;$F*s z`Y&+o>4c4>5OUTra?^{#ZTM>fs6c=NZ>NWs2MgIqxsXl05O4PLk-(p6VB*~D1DQnU zgBk1cJHx;Kxydh9$u8txruxWPwuArrEp4j2KN{YNdaBrjKvyb&*Fh=Ynea#EQO7xN z=ri_ytHGr+2-R{5v0}V|Jf8XFkP1UOGuq_e9(VY8K|>0-BY8zlDKN&5b$)a!CW(KM?YFN6hQ~7*(E9+(2a~1Jc;KTv(8PAh#svFS zl_gE1hRJ0`ENbbkMAR~w5h#E8Q@l~eGAB)wmriTD$&|kw^Ks(*36x1aKZal@%i(`{~o~SUY8-ltF=zLE%uPh!#&L-1&xi2h0uX7?VsSKqqS|P z3*S%rsR*#ekj?8CYtz18qyB3011Lm1S8@|dr$A=9TQT5=I!qO|>qHAYBZ_9sMoJI% zt=ilUexf!f6!dMe{qp9*)z7YDkLH3kgf&V2fIsw5r8CSR^u+jN@VSAi(QlA}IBLG- zXWa4gh&_p%hRnI}F3Qa9aPj#1N4l}t&*WmEXtbsyi0RvE=hc#`A4JkmUSj3!SzQwdsIx5=d` zn8p3F)6TWs;L(KUq`!-LMvyGJS@X!;A}>vaO~zCeCM=riy?(`XVR`4#X%$gA*V{TZbV2Xb~W3Hg+L=1slQjn+4 zITnjts);CMeA9&7j-Hi`pXPv4F}V6OiCaWDs6)r|FvcBx9KU3_QHrZKL~)nm;JFPk z>|?p%FOGZZ&#$IG>==wsO3d8`7U|6$#+u3iuVHw7W_q)7k#!zs-cKrPCKj};*`)IB zWttu{m%!A)oz~Acm4O`R=Wvin{WBPG#XT#Tgj=RY=heMe2y@?O@uyyuVp2rhFG1;c z7CTc{iNp@JfqSd+W?;#t=z7qM)E5>O~T>rIC7jrB6Lm1w33;)xmNhN0Z+qp&YJ>qBzP}Z(TWNO-R$g!K! z)s8iSY4CA^r}Msq=3o%|#gTQk(;uF6rL!m@oy)8iV&#g-?2?T#&O?SerJ=u2e*x!h zP~`x6o{>!){OUv(a8$%ljdva>$PA0I8f93A>XbLdVc&dU6v#V!b?p`N&#uZkH7qP*RgiVA^P|XnCHCUS^td+ zwQi1Ewe6R}Bj%U!O@$64PGzyTXvn6=-xj4?sESFuK*jdNr$fb3*S|p)3fjl`soOMy z0=g_5eyc_pUxNPpw{VtrnG@b$9A2wS04d9Rv|>tnyF-UtLW_@%9wjNApLh1&$~%`0 zJ;KKUjpa&4mdvbwQM?2kz|kYqZ+jukowxj6skf)@M=pmMOMcv4J@C9-!+h#DF-GJ` zUiuqyO)APru^@2Q^2ew4U5l5ye!*_XGbblB1R($P#BFbP*Gp30pUNOI1N9iGPQ2qb z`Y7`IN2I%YJtd{Hqz=+97QuG+@vC0MIq`2(u!s}1Vqk3k>b%?W;#%e)>x0zYXac zuQKAvg|hhklD^e#yC@zD{`Xb%+}3(v_fIMS>0|>JvHU=7=)BK+hyZ@^h5nfni6T#> z!OR1xg7hC-ai!1R-}YPGy8T)KKh;Ebjy@z36scRk5cGR+X(8FQ)BlO*wcZjr_r2zz zi~rH#EDRikS{MBEVCRLoHeRPr2|&mhFVsMzV&-u&qr)Fy=EKfYU zfz?b`W^h2a1Nk7?VfsmE7Ir^aAl#i6ZW?%l82Kbf)>YVv5588&zzqdFOY@yNQOMGp z8_5uzU6GOv76-QPdn8%n~fT%;LXJZ*Wz24rEyg8ZGA zo=3f*e}1?d+V9SK@w0)9P0>GotwDk<6zTAi3FoUcxZV7cVd+-_AQiq8O}|*o!WjFr zj*;pl1YRyK8*D|B8Ec(zMsLUg2oqs>yEz?aR4q49WEPQnamw#s64(CFLRVHyf3FyR zM^0QDGNoS7by}irNmO{#M>2UIs3-}$Xa+#Xy%u3NM_>(*}R5trsJt{jUp zWZD!t`uo-S^nanJNYpwp-|4dK{^OG^|m z;<6?pcYs&8UtoL=Nha`K4`j&Ft4wz;_I;J z`*xeZ+q7}HNYJm{XCK_4?ss2)e@w$Jev495Xo9$8y$uA21hzy~ZCYJlim;b_R60Zu z;F#{Unp!UC*2AW%Ocss{@7J}jHpyDe%&y$^n^y@CnAefEuNWJ8vg${7Y@$vv5-{jz z|0n{`G=C^U|)h~d> zkmMYZYs}MnKZbk?u+3~A5>C(JPe-|U$WgI>C22C2A%GkNuX|Ed)(ZVr`td6n2p0uE z@gK!00reZ$4;0}~S${BJ-MZC4p1Vd986&0k1}FWwe_9lD=YSM zgOCgM<)?Wke8`W$4RMYC98KWKXDdZ0ZKLws6TT(O{2$+<(ZavYIJ{3y99U+ z5)Cd8Z*8V!eG(TSt8<6E5nNEf6}!vOcCSxxVZX|AA61(}Kl-c1?50OCwqx1q>tm1X za~!9fVI1AJ-mjlm^v+th=Ah(O4{E+Y5z3|admjiv&u?;ufzv*c9hi8GmhMEb^*YVp zIsI|>UOUvzHOWfB#Jc%i+qqw>)JUdCr_+!ZR%q%~p(&ZG_fYrG&rl4>gumW&?eD_C zQv~)OrBmdC| zGu*p~;77)z4C}=>)SH+QOC-}`Jcls$96kT@o*hToxMRK>08$fAuQfxAI-K&w$g+k6_I6mg6DyLED-&8)|KQOXuL!{B!SjM)dUYBsibR(e1*i{a0SI;CvOa6+5P`y!X(y?%yJc~OCX9Bua#14Hm;DV zqWfwZd|L+UKtbjv^{pSJP!PwBRexDoW4?KHr`WK=?Eccs`C;qXw`sqMvC7q(^^>lH z=YI0>p$oL*&z=xye>dEhpWeGAI1OPO!BtSOm+MU3+ z(Dj&??di+xd>k9W2mBC@OR+=DB2;n`Xqbtds55an(EtPgq0^cco3w;A7+;>c)PeWX7RFVG0qXh z3y7djGLum8Ilf(iCmg7+?hm9TaPmMukH1Y@+SBgb(WWL8O@AIcO!S1#8Jg&4i`w+`1L$n^wDq;Ppcd%AKCd_;8Ut6^B^go0{1GffFX6y+N`SURet*QIUqrW_(#Uhd4MSB;!aZFlU zkGxhtH}Bz#jrR}e!zcMYJ2@QUb-+&CpU5*)jPj(fnTxelv4VntZ$N!qWDI)GSiU7s z#Y+K;xH}{Pe&!Z+FL-4S^n6bO)|Hg`X!L8RJjW>3xy)q<3maJm1!X#jbHXew8j%jW z!`0)<3;QtZ=mBa9dr5aLtmTWt}pRrv$j?37{~PfcGP0u|R%QD< zW;{2(@(^Wud@uK3CHhmEXijf`C+mWF^uBd3UjE)bz5T^l#x9=$!F&Hq!xBR-$rj@v zj*Ha1O^ig+&}m_QjNkmhB{*u~_oq5?)W-jzmk{D47!dxqQ1ZD*V zEAJwoUzw;;5aKwmP-X+gOSh9LzECp4K^lSFEm{k_fk1%kj1Vi|;l;y~XtrV@I{({T zKp4F`pzx%T8imUljz1O&n4e4kj9Ry-aDOJ`fZ9$;0N%Ys+fMchb9T~Ew*N4BxoYGY z2Q?e$9m>8|vGwHsY3H{EBk7+(2>3MgB#`kW0RaauG3OlpU+68LJ;St=P$dj;09^y@ zvYU0X_NY&xmiKDJPel_e9|}`6$adxSM%NS`9n~>H6FTb< z79l_ATPFZi1&uDX5npH4#rxGy6)c!s``3Fto>Qp_INQ2my=E`^yIBvdzWoP2=TX+5# zK#{y_k~CfvSvgi~9;+|+2yW=F5~svz1d=->xI9eO;vK`kzfY2f5WpI@Tj>{t5*f!` z+-^Z1SAKisslw7Fdw$Q3v%FG#)FA}icXE&BK~B#rEVcsXe|pHiEV>XvLD*)$823Ne zz9YQ~Fl;3(15St~i)A2LYL+Xggc|iFP4?WHAz_#eZz- z?znw4)j>xzWaa-ioxD}CJneAvuk~6$Y3My)@ApRs*>QWX>SvRS9H58XSf2PZm3KtI zgIZNhQh*g~C zLs84Mt{xB-Va6?U@v!C6*$kS=NDGInyf`I}5+}cs0?}grgy+^LBjp5m=XoW8gdKB2 zbDbM7j&DyS#R7u@FaUfyQEh#j83f=sa5rl0bx8SFqbK&f4wr#_pArrMA%Bm?oqO4W zt%2DAX-~?W1oiW@ZccHxEhv^0r91mMTb~Zyj3=3PXmEOkq|HHS)#jw@2#QRR6E6iB zSKE?(02-dlF{O=OZy|V-!T<;AwA82Hb06!@(AijzEc?Y1bEc$k6K<{^Nv@Iiek}B36G_yzxXK zRNd-y?2u}=-?7Wq?u*)KD|26JA*#?d-~we<+~v+JVkxH57q=VTRebd&Y$jlj^9Vo~*i~0d@n|ZX z5mLm2LUcufGLKHayAi;hmoWrb{^{V=c$j#TjoGmF!DZ~(F;-0hllGa!@IIN@8$C+lxwRE+TH z;3dOZm5qa=(T5vHGQ;X*15{HNiHx|aQiWH?MJJx)I6pcP)2(-AI{g1!osZ|J9#DtK z@Vc0k_}@Np(dvA4k}-=fKDZIMofExDb9$V&yUrfWNWfQOqDT_^Y9MJL5FH2%nbWoF z?VzOZzscpvwY&0rXv;G?MrmM|xoqj$_wlzTQ7|ERC(Bo^fc}qcyIh>Jx87!sM{w|- z(2Oaw&?g5rT`BNGd_Z+xR zTR`qAy=Mw=lc0pZ^K&o@XHxW_SI9cn^w2z-=_iN7-dpJ^g7$Mp(lhz*IdG{4O)TYp z(&!M1s3&~gqW3qa-%~1E+!!U^{pa*rLbkcD}uQz@|NY(U2zWJ zu%&WD)-JZoig)To0zjrUApY>R#u!H*F4xI!*t*xpd_z9@yU5ul(m4X{)LU|m5l30t z*ZNPdT2(AeJST4S#@R++4A>k>xT=n0umhuG{nE>vNE~tS@^^b2yTT<8uoNE31rKK> zhs7gLGl{woyAx51{pT~Padqo>ha{zjfkoA%Hf;v|62-${LGhqgfX3@_j^tXsVQKq>1VVkytO`ON88kVGH zrThQd=U3NxNLl>4QWMaguBNVBtC%;GikdfhLC|`!*b;$=TUqYPl3!007u<3p-<7&_ zrP%uEGwV`HwJ|>1$!K7WXtpo;eKQJFqHXoua@#R(#4r%|Z;SUd#L$`scWp{UiHs1>g6jI;r_;tn)%n4d2d5@SeWs z2zc(EdSXDBg98Sc?N?$B;g0)CPQ6lys|*mc6p7tXd;;0AU*u|Z;oTS7m53keWy!JbF@f4snxFn4brV2iTT%~H4l;0z`aBARc5{JJ1tPI9Ws){9kU#aqf zRD_4!-a}pMiCa}n&>aj=#uCGlmZ_f);P;***%L?SjcshZQU=(9T9&*M88>#l9Q!{W zFZ9|goV#pahA%n)!OqI)o|BxPvTU3yjLj2&{h+|x8I|ugn)&5?s+B!`wZtU$En;%7 z>BceL8IQYLL0%?1N=GB+WqT3VHAOv5!Iaxaxm9&fLZ`2!X!9!OvH&wyfls6ihJ6p0 zIDO3^79=z&H=O1SO`Bh(P=3=6DOpr&x2?-jFTdSkP)MQXCi8*&<+4b~csNUW(54@A z!0xY*Y7N|-_lURaB5{#551L`YTl7K?KlfwJzAE2H-s*LKpmh&=@xSk9odU zpmuZ^T3y2%DtnH(Wbf({+^3RO!v|bfV&6S2SBw(9`W6zoh=o*tDLF+^yGi0+QFBp0 zQh3ZPD(?*mQ;_1^GqA(x6y$;|W7jazG|ieP6z&5~2do6~Jt~Q=c!7I2qeG@o(>$M* zaH93s_|5~aX)t4KoM7%iJQq&Q8R$#&yt<|Zp7)UKGcAJaB$q{!4T+7y1ocAlTi6!q z)Eak&Bm#~zyW_9>`zx*f_(i?>u-e+UT6*AkJGyK*Qw7 zk%Ks}|E7xPYCqQw2eNbbjM>8h2Ju*vC&4#%s0( z@Ql*^y|3l$(VkIYo6OUM-f;z2T}39)qxN(9E0OahHBL^KMi5lyyFJhLCR^-OW_n%X zbbPfy7tEOSVpTY)`Y8PPemu}YK9F!-0H_Ei$E~QnN`G0Vyli1kGRgX)C=(dft(WPy zqXpE|aH)Fs>T0&WT()*Q9zEC`_WJ0{nc;i$z3*q;A8Z{Z-%)>iA1M?s9F8Pk1r^WC zwkg}RUxDgp#^pi(7&CDlof|o0n+U8SrJeQfTb@2C(uuM-*N?LA#LphT2H(rpw}T7y zu>AU77eBdc*cNm>gr8(&W3n(CcEV(1t#)IIoWsnW$ zymcP)i}Dv&y|E(~_SoyDga6=C!?+@xi2_XI|7!V}!3{i##Mtwwp78&q;YCC3D{0Ji zL+pbLBS_I}Bl9Z)rH;L^U93xQ$((e55dPi=%Elq^M=>DNBejm$_)s%jQP()#nxAMA zwD;E8y&}bf&yCf}mKk>csx3>Zeq9}dnRgI3(d>-AA2#_g_%OJv?ef{L(zNi|=1Pa# zay*uD7_Ro1;mqp(DQ1AXxJ3D~srbsa^w?&^Y1IO3oVFrWRI}rl6l2^9KYd6sS=-3$ zNPHMev}qh66MX{*4gg*wt=@uhBN@f-J%{C-t6g(7mE-I0&@hV~mY0-$je2MGt?)0Idn+cF3RkGxIHQ^R z)ch0EZj>R0OGOLcWK^i>BfVy=tyF@ScHOn4opt4FeNBV^7j?nzK$|0@Y2h#4dOsyk z8N%L&nH%599wZzup0MGUz7t^Helps51Q6F)e3!dA`Aw0^<86&cC5O|RDM8M~QA+XQ zUg}uK)=u$yH`;Rl4@fpN-CpyH_6sv=ymW%Z<~TF>WAIB7F}N5n9t4|a{+b(>3zCB% zSHy8Bgd^aX8G&%4Q0*XAsU-JNG4aR6b2=8#l5pruuA1B+{iy^rw9Pq3LWFjI?<g ze-i1CiVaMV=^ekW`vCfNwKlu%YA=3>#Jky`lO?f_5=Vc-?|mBx#|m68&_Bx@(3DZ! zkG_{smpnW@@Q1nLv+R#sCbwL;h;ixr9ZT2zvXW0zul6~Ac-7X_cGOgk(GfRv{yi5< zeLISWJ5Q1;BIs5Es{l31&}Z<6f?CL(DC?iB_>SQ~q(-099P!m~6N-oe^r-B@v}(F# za`ipiAW?&@F`PWWkYNEM;Bru@H&BpXEc7Vwu7dT>}2DcX`c z^Nh^OexQoN`bX5}f2GKuE#(&DXPpP%VkfQl0caCzJ6`0Da;5oTXINkM`ONM);S|#o znuiX4guN;PU#G+j;f1=Gmsw$o9{Rh17XWBeP?%q4N>?rq2qvE@E<40dDj)LUqS zrOh{U-rcBDmdVC#l+H6miBq9pd=zOuoNaGtB%y*Gq3DNXF{IAgwSMLz=vqfnvq^vf$` zj32(OiC&OEQ**E&G#`i?M`-?v7k26N!gE#kO;6vs@wOZj~rxa-K;#T1)%R|Bwws)$88 z*kHZso!n6*5dg}AJo=XAGa@JJfE(AdK3h;Hr@?vc?O(}Vi-H2;EVyxWCd5^>@!)59 zF*N}9l62w1Qe}3NGq7A8#|KgQn5*Q#)v>cA9uyVuO8jHcHlG zOx>i&TSuNl^#JsdwP@G*O{^&?XQTg@ABdqnY4iN|KJ$65sqYTotw+|dcuaYWC|ORh zIzalcZ=FE=e@ly6Db{<;vRymJ2Iw&m=f>Y zd2TfPBF zC8!sU`V4V`W+G;su}`%~c7K*XMY1L8a7tS;A!v@eoPF|02;qbsKKnjm{ACUmPDb}p z_uA3d@u2N_nWNC|Z6AJgD;P~l6n#9g`b+g10G1)S3a(8@dZ|5{)qz=HMfSma&79XX z09)F|gD-t{ta596GhX~MuWZZ)e*LYlHC^`5TAfLz1w2B&FKL8UUxf5t%|Jlu8Zx#C z+0ZxoocwvabX;Mfvwm&COrLJ|H31CF!A!L+FPOe*PoGWv46C7FzQ`7~$C)7k+<44a z_UZ;9>v2h`Z@tAoR6|Msr^|$S-Dp>2II7Bs3cbt`EnuV< zQ*C-aMscNqzu^ZV%!%Q;UAJ-0o$b$Ai^*m(hp=G1rTM3aO4&jd3`W@Lc3Uf&AESZZHFhAZ2>%4*>b6mljd_VT~2V~D07 z)vtRglx>Ik4jEEcIa`4YYXiN0nxXM9VOqd&HL@P5T6PEje_ZrEP|(X<%}Cw!GMcAm z9=gm0_p}-WBZ13CG93e)&nc;c*08AHW?h}akxoIO99FNwjPSbom9qec@`Qb#M|8gE zE|9xEV$E}|V>{KtP&@bGf1CtoOOPdj_3eRuBPf~@hb!&1xpPK^v}p6jlPVG>JKcWi ziq%W^U{ln$x3MX32*t_gVo|R6upuQiWuY~Kkn6=j=1?aH@dS-jqh#kZ;Z8l3%!;LP zm|*%dt3mai8Z3Ob{r20|Cu>H6X(3f(q|4)W!9ADHuEQYQd!5DIXjCkK@_GnUeP;q0 zl_5vR1JIIQKR;>UBla&SFGI=y;*DGK_SSwnpTBNsfA0uUhwql(#m^rSS%W%?*np&a zuZlTPx|x?RQbE%spud@)f2HQw?QL1*`ilZU%l)}(55knRzDrzj3zYiPHN(Dy=6T|!Xv_(gk|4-N|Vbx@yyYXN#XvzL^iV$=B z8WOfEw2 z*-1@@CFY%{y16J%{iMVhmTHZ{q)TkR5-#H2?z>~O(fW=g4gq?0TkH-+3I)hys;}1T zML^Ai!)M1V&V zdw{BNY>`Zb3W%}}z7%(P4o<4sG;c1QNcWW-dGjJ5E$yt~AXzS7B{3pO)9kXFA0*^k zD&-k$bAGd7S#wITj4r40a~>xj`rN}yGFCk8gdwS%W?8S?5kMqAMi4%IJrSYet^Bfp zhb(p~|8T4y#fw%rUx>buRD1t9%;Q}ud^+R$P-pg=bv^4hzvO{$MK23{c%Y>KR;Hd5 zFpS@J{qz58?(wo)ojTk#ka3s@}r*@g)Dgr6fPV_S=^J?Qug_Itfq zJn^*VKuf@Hg*n9!xxBWipOpgf6x*2!1hWAAb@(z5&+&{MCUGx=XP#x?z+lOTvtj(M zQu$^Ye{j3Iz23>^uhZ+(J<#D~kR2zrxEEF(vI;l5F|arBY$06r#&CG#@cE9DJ#;t{ zt4y*)vdfk_i?iX9Kg*`#=E6qx#52B0It6%jz25XJwjcOG?IXdXp^1?Iy9zkR*UM3T z$q@LxVgDnI|B%O{#ghaqRfl@JvE@{4$?-a_%|X~rD1A&VrESD5O$D@dQTgQ60tFX# z(pttO&TCU!{7zlE89(@~_+6sHmlk<<42!K;y*S%8C0`W|^4Bm0sUoXRQl(0e$4y~u zFBJlq1MK60F_)2P+a_&Wll{hYgrWIIGvN_S;k1Y$enGQba<8619vx$TVcS!Zrf~vZ zGem*J;lJ(dK(3SE5k`2cllQ)dAmF~)X*z*XAn6DQh2#hQB2)IDz9c|kI#&a^t<>*xlWQ0aMT%+xaD%Z)W|mF*&tn zQw<+fz69LDRPi-N*0LEVl++ZRZ|ovzF8wwZiFL|SXnv==n9j76EbAa)=2*(MKIi94 zw<0_~tNAKVgh9W_d+YIc;l%Ruef=5twvj6#9cxj35Pm{fW(?E$W^j}w@ET<2MTXaDa_1tdZUwv9%@ zo=e1f6YdJ-5fGxse6*Q>3alULpivMu1qhwr;^NR$6f|h_0q8Cz!O0~42gY3Pl0&^T2>|6rsD8(}Tj-fy&ML$V>@U5soYTYNdR4lQ>1B%b z5NDVG4W07MR@FeT_gx(#uf=7RSLHf`kLT{>SBws4-mIqL^_ z^ZR(PbuHMiX4mX?qOIf>M%*VV^Hyz^((AAkeZdE3|3Pzc>GoBEWi~2UErG)C!YptgI%$z|=6T+MSRo=E>vyaBQo{{j(yr2K2QJXk9VRrL?Ux| z%agQ7RO7K^$3c7rz|9l_bA+iilIJZBHHP=uZ+-oiT``2o(~PADU0lCSn!c(PV@}4O z0F*}N0)wexVGmiaks=UjVA-Q)xx5o4{N~nRA#jl%f@@9se&ufYQ@yw*22ip4DB~=z zf!|+`gwc{><2JybU|~UQFhu;jAUgDNI`wzOCa|;Hdq>ZARQP_RwGis#v@YS| zKiF@MS?evSVzy7IVp1Ar*kK$Zo=M5P?kQNSWX@5n;e#QL0wbt8`zz2h4yf8M9fR3D z=`mD6|0`Z!Sx?RV#Z5>dJ|9}^d2-br5m(H@23dt*uMHeHABvJ_R?dBdu_Qv zBmOX7S(ozUj9GLgy_0T0BU%8w!CH1T+y{Sc2zu)a8YBf|-r8UWCU-T=3cuVeFR(!TDr>eKShK%-_*U5NF?YT|PN};W`fYAm_PtM4 zp=@SmP7?3s^RtzMMHNU2&_Nr0+C1k=R}Pcvgc;_;Q_dgC0T+J3ZLd{%A~m&+67QX> zIbG~@PcSsy;ArwgHy&(?dORfOUsDv;K@#Ft4o`pG65?rxKV47xCy9g(-5DWRpEn&4 zyjD6jzZTs@u{9*aF=gWmt7MCK@S`@&^8~2SWmF#;0_!EePzpVPjnAo_8C?Ifn5qQ> z0Vv{6{EsuSsI~liZLcQJVrVWw_3|V-g|%}=d#7$m7|ne`@)uYym7d6CtK99YjR^su z^t7FCEGPq~TwM_b=9^Znlr8j>#G1x`OQ66JEHfnd4I8c}pT&e95IshMS2!^=IZsa% ztAUj&fbx+m4^FbDwY3slF_6B*-I9GXc~E?TpQY^Om%0K)$1JGf&WJW}C;*_J%c6yY z{^teIj04?{B5AHqC*MULJ{I)8+V*JtL-V5{Hen}9jq464m!`k$^m*|@x+xGZli>aM zKdjf|#x@jdv6CVB8ldKCo`;_Hs-an=dyeE!AQHIKYs=Am2-Fh)cSS^A29z|dP!~P>m$$yJsik-=<8wl1Dgr;7M%oc_>g8v#}J!VO8r!$;z zGG`8`?t7wza|_!BDs})|;2n zzVRBG88m4s^1~dFdmBLTqej~#Q3fW@iP+9RZe0xKkfBC1-IA{NP9}MF9FjizvuINR zxSE0dl|)T|8-d&mSpVa{ZY|Arc)hi~?8p~mBQDa@@?`qjyRDqF_N6%sD>G6l`p_0L zm=4eTiIwh`%x}l+Idjn0N*R8qjnIXO)*mWZF6&y$%>H_6Y#UfA9dVO3QnmbA7}nj? zRU~U8?f__#ic=!%NwGTu1vp5qA2#u9afXNJoGms6Hrc6~hGRxgbY?yc}Og5^av z9|7^@;P(F8q`P9|v_ndLF6j$bmxWQ9MM=S?eJ47_wF$ylv1`%oIw_c!C1yXILu0SQ zN&IRsd59T~1+86yF{a!WMxI4t0cUh=a64JFI#xbKcY8a=aT{`XC%2ZlYI$U?A zvHUyH81d3-6_6P7XSRpFM@8;#lLq$x@${8(QAXR>&kRE_z3=|?e13lC?6uF@Yp+cl+#{n0LZY0Y4O$@4 zEo)vF$pc_1!X@x4s3BA#OF`y^4Xc&u zGwy!EfD3SFdDLuo+789H4>Vzql*b!w2b(JGnd&4B7?n)L>~N=fHBJT9g?9b6disp_ zO9oK(2q35fmrzrf%rU+st#7?%;&%Jri-xMpE)R2>{pt*+fh?db!SY?{OKA;uZMjvK zV!TsJ>|4F!P{CveUhC)m3}itOv-aOt0-zexgs%SCbUB z>NmXhKQ%&XDd>R94+V^W7;=6)wq%U{RCNS`i3ocSV#`;mM>YarX=+lA%vFiXs+>R4 zH#3MC_=CM5z29T3r|^$`_I0O!zBeYd`YqxAlSCOQe$*QLaH}GT6WHR%4V!%~D$?;q z&O)0}H$h@}t)Qdd_BQB=UbY`wAwRHV@?2lR?X`7DN{gzQ%JT>Jm;m^iUsr9HyMLRo ztBI-X3S(h3LDHXZ{7P@xf9v|%ajWWcxftuUtLz!PZZ|lOep(>&pC+l8GCnBiCGtl@ zKi;;?r!JK_>vm<}VMt?L=pB}0Kiay0D_KX*(Y_xn2F#|Ocap&Jl2<%};XW5EQs%TJ z<`H|iqjdNePmR48oW}mwx90t5_v6C(2=inIzU~9fW;Kexj-5eBgZWcQj~iWs=w!^g zHOVm12F)1p0ITJWXKJOsil^UAK950_7-e4b1ty|W8gKEZt_#JjY=T2RDgaegjLAt( z*7i_h?)XNY@Q=(cmv2c%Pr6#0g;#k;kbM(#M?+&(_@5|yfZSq^6kN4WuoY4nWo!~c z#?aOK)bvfCO2Of*OnunE(aAzIoKGJZZf~NgiF^Xod?o!5c2#!5@&;3FoTCB$X-2q0 zr=D1J3!&`W(PO$>JKN3520JyLvQ*ws=gTOJjDGOw^W8XQz`Fyzq~d1|>DrgZNg_Op zenT?#yei8<6QZQ!Vdf@v(ID+Vv5j+);pkCZ<&Tf$v%XFI}f>ggWzZik%0 z5u6zda{7#S(Kx(Bgn`ZCUG5JUMEUG)-27w1mqW&bRb0)!+5*eORi{o2-3PTk);$@Z z%#{I1^_i8gm;e;m0g{)A9$I%pKcb;e+lI1oA6kzD{!I`+^zOsvZ*NS+w!Q?pF8L8| zT{DcGldODjz;27`(@gDRZ+pUQ;#~cySq$d#L-T1+*rU zF{Xy}W;cLiM+#qoV>Y#RE;C3fiA>o=b@J13Qsv6Ocwr5e4wrXunap?Vm}H$%_>A9m zHX$k?66pOD_HOsgW7l<-=e2!R9e#K+w+o-gr$yr2saP(ZRA5s%h9lFCSKw${N(FQ4 zwD%`Hj6PQ7xEta6Kie?9nJ|`;|8jck=T%7;mMO+(=oF`#dmAYj&o#J_cjoocveZqO z543eC9hUmp)dBz0TQ#HuOr$&|0uzomMQhd5nJtJWU!&kF28mW>{=5nQz6B+=n2TxQ zF?(Yr-N@XL;N~15x)Q^_<=%P??E0)dz-mW<)7aB(2HgE7$zFWDgLM5%y| z276jP83<3UuSKoO26h=jznrhVPRh^JeS-@LrSaM5_E6y={eIWaA54gR2@ui$v58UT zJk3r5S%3HiAI0bJ_2wam=2vUrbygMkOm_qSEX)?g<?;S(sDmmf{SPRG;Nt(I2Ep>#wCh1jGcImpnt*guISUdc_ZRLV<^_~Y+ zA@>sUkVv=1oGXGVXE6Kk5qs^Uk~^=*AuBDex{Gp^{j6kFT*_uv8j-{Te6NW5fM;34uq0NFk#LJ zY+_A8`>hk?5PkR4jy@pw3Cs|DESM2V>wHvK#lZ#)+G0LybsJKM-`kNkv73I9XXtTk zf}Qg%1x!)kSdPas{f8N?RSqk2+c8M{rLn5S8>U(?khjo(&&(^G(Hr$4MdxBxEbI~3 zjo?2ff!=Wn^>tq)KUhv{=2)0yV&%m7ZaW7iG#yMxJ^RUL6o#$y;$E4Bk={{Ce5PD7 zH$KS7oFwPACy(o&Poh|i0uRRZpdlGWUJXyW=IfsHuQO*wBi4@s@uizkmU9oM&Eh=`7N{f875R3tV#Dk_yveg(0iRw*MsHmYzY*b_N^u>3wr)bmlDpW z_dbCQU#?>r&Fkd(IrpT&iD^%YA+PB*|Jdr|2`isG!&bE?NJb@8_{ekZWOIyz!=L16 z;hJ>~2C-bp5Qz})r0wn45R9b`?oPzp12#k;hVs`qEB6&>>Om6OKKnMrZi3dI>Qjjs zI6)t9ErKvz)Q-W_DF*?A4;WqnOCKH8cKHFgXgMMH>XFl9;}p_xidOfyT|2-|{IQ`0 z)c9pfU9r&<)ZHv|*iTI^NKQVb=w`+vrwwaef{#O+&-6r!B2tdMD2v2@y0W=M()f|J zMZHw))kKX_ugnQ=n=UteDwr=v#_@KC(l)1<46nJ+5XHa@K%xz`V=PRHdus;p;u!&; zivZX$0<6F!)_5{)Z|?v7r{Tz^pBRH4pi)pmJMfHz*Uon|%}bBu2n_nz6$3OBTv-*W zAw@`D<&aXgMdRMVOD{>K%?>o_B0F5AhfMR+Z=yE&@Krq0*}d!pG%5Qgo=gUm=Z8-E zXvtsb%P&sBZ-(9-u^Et)hPQ`&ZXH#Mzx^^ot^F=FuaYF?joJ@tOHls99tc!f^A%P( z)Kf`fw#K(N4A2xbbMv`qp^EzrtdPD4e+4wt!a_ob#fV`No2&dVL;@}_Z{e`>HUt~r z=nC70lzBZ@&dG&D`_uu@6&qF)kKKK=^k$MPmOz1AhaqqpXW^Z;T~< zBm>7|6~*MTq>IWxX4Puu_;@8rc5M>8FA)4S1)%f3{Vy&LXm9vnZ(->!oqOw%6U?`d zcu_yoJV`%uF+pUldq_vX+<>`*oLSQ!=@{$b%UENWeh~92gkq_{N_BA*qWU&|Zr_q8 z(`A$$CG_)!GZ<0+?lB0+A(BbR(opS5vZ(aM+IV=Bg4t6jC(+)M7YoF1!JB+85gi2& zd)(`0$*;RS7CGv>As4(*Q2W-0JMXet($_f0WL}v6zj2)RIv?YFDQ&}pFq|9t4v<9q zBqc^3&?&48RG?VU+w)Lvh=di`Cad#0w%TCDiQe}?xG;iG@GI6vf1u6-;O~83Vfu)E zdA~m?u@C|hbAal!qGrB%#ZtUNMMgw<^4aI^8RBUpkPr^E?+ z3HaLUzR(`Dblw(OssYK6nH?qewfLGUGxE171NQbXEMrlV^m<9W;_Cw%j~1vJ=|DUo z$R?2twuV*t9x)9L5f9Gxd8k=l_FS^bIVXWTDe!K~QU`@x+%1%5=C1cIbe?nhXYGwU zt1G)wuf-!)rEheOA3a=dfK}1)5 z-j<9hJ;USL>F$cS(L%TjRsP|uF;C`rHlqjnDVF`f<0v|GiAwMVw@a`B)a@n2>R({_ z*I`=T3nSn;)h!0M^a_{Dg6M&SbaFC_>)-d<;z`$YlGeXF)6U7Mjf$_c?S3ca6-=R)HTn!1I-9P!L}G?`&zF9Q4HJgI z?B9UhKYffS>2LQavW+2g@Y3by0&B&+41H#|q442(I1X4;WuM{5=P=<46mD##qNMuU zhMAk=2@hNI?UMH?yUKSWrKh;y?}-H5@A3QBL@ULEcuFnXbU&6osY0ALNBWj|?6M!U z)CP_)sr_NU6vB%ewnh7wqa`KYj9n}|CI77a1XjOb*7$8*itAf&vf|s57dUQyFIZ|V zoeyH%BtbxMI`ejXPD10!*Q;rgAaZMLtIMzS+Swe$t@gGpzJdG#$z^0PYZuU#<4%xh zeB(^g+HWMoATzE20n8~f>HdGijCgnweHDe7SX}-D_Unck-&&T9C>LWuV1*S7V$OAS z!yBIEbVlmNK(-EtVc~|h`P|r4Z0$o|e90gue_=J6m|ZhHle2O$giQ%N=8_BW}l7unV;={TOK{2@2fp} z{pU##V4JX1cOL-A*cey>{;q#}#PVb{&xUowp`!V!`UF(S2}@-@mXD+ftKXudjhV|L zJ!-k^9F?nDOg@3*mH zj2^Y`P4fSH_yC&&6pnKLykuDZXpeSAx|{=$sY}Q0twV9tYbRO!{slpZbtx>4i2gF6 zgmZW4h*Uf!s04hia&dV3O;UK*91THQa-CL!n9D2nUj>=MiqV|ud?J620qXyTQLZAo z?3?BM`$df!jMKXG5W3`C`oV5?yU*5nAaZjVi37lNfDryL8eDBW68iw}pnLbW?yI~< zrOZJYN4ZZu`{U7xw&tLIp@aSbFB64Go=Cka_~xyX5(A?@!TygUo}DX^UZFEPa6?QI z*DC{{kTWOs;pHuKKhJQsrqBpWkWCvT`m#P8huOMs$qF(W*w1^M7{`tqslkyFlaY*l z)O=+^=R~ShbPLFH8?E6#oqYFtr^Wn1+xq)4>r%D>?{DY=*7Nt(H~w;+9!^f|tt=_i ztrQE-ynl6OmyP2#1>9ESZJbSS4!Arb5E@(e=WF!JR6Wo!>2WzEn;s3j-@`~V&jOTG zP?cr-69ds>rQMwt%sZ5XDBced_DSV^2_#}K!&n(e^bXgnF%)wMi7)uYK$ooU7e+_t zXlIds`IBL0068x#g{VMK^PB3^R3>7B?v5)SU%+b0$>F*;X}B!IU}x%=XpDV2DfRVX z4e@CIkOu?IVOUqoP>d zDg$%q>4pmW4m^g?Y#T+Jw~ntuKPD;-7K2Ayt2t6PhkQ8-mu$=l89+JmKQR#wu*6Vn zW_+rR`dIoF%iqcXEj>Vplc%@G^X&eHtLw7$J`P)to|xVU*rS zg!3!W0u=W!ZGrxFEV=qc=s+Tu%KJt6@N)b}-|FpLuiEodK9?g)-5rK2HXdhuU}5h) zN85*&?+J{*F#(+8aqhY}0g^V^qq zLm9fIB@vuC1L&f82_^@0{*+k?3M-G$@$&fxeii`t3x4JME6k)bUgjN@;$bRi48BBLBM(&(j5+DbfZOk+Q z8JFYg@W!w&6;}c$cCcxklEcS5^idI?lY@B(J*^7&G2OVwy4Q>BSPwJZu}$ID@11C5 zc0!09Jm>~l3fB;vth~RnD_%i}|0PSw`e@AV-d;ZX)T69xY7lOZQ5LA4x+Z>f{5u;C zqaY8b&cO4)OO4>y1;VhBcSz|L#J>-rxmHK^IoT&QCzE+elF&HA7-hvwcI?;MFtSd) znU|UTr1F#_Nj-Ouxwn}mgg8ns(sNh4Tk5@Gtm2%q5w^EKrdzND5^h8b>TBCtHDt#A zCibu7Gcap(-AMxPe&pX+O~m`e5vKf53Z^9=q{#_L2BM>8H1neWk=(94V zO^!b8JR*5Loq;Q}inBSENdHWAr4Qvw0T4yspdYYkL=HlYaX|+C*hgM6KgM!8yvE{h zM&->v`~9$))_xUq1p;_-$OfO(jj@NQw>FXozlq{DqmhYo4d%kYeUY%vMhL5(U&#B1 z{L{0I&}ePzEk(vmHT*m0hl%>C*mu1cT%7-lN0(-phA5059cbw~_HJ2xc;i{?9W{F7 z4Zobf?rDJIs6aG8g4DPKqNAZ{G*7^Mk_a(R0o!QI7^v>h@)Gm{DJ1vVj8#T;_}XwQ zSd61suf8lL>NLAB%}vo=T%_7JYJnH~r=$1R8oy}~|NIE9*(3cZO6l6hOeM~$t=xsi z?eB~n2;dp6Kv;M|aYtKxTMZ7OZ4{3$l6xl0>kLIhM`y_K>{H^FrH_sCj=#7$5o z*iWxOH3sXyL9}It;VC^|cEF79LLHF}3>C;$ zVKf4L3S?vYD}r@c56GWx@~3Qt(zq`9Q}y_Q*zNRl69E$4%X@Kl7>cfZi>r4UdilVL zl1#XrO6RBodiP@LzVrj%0AtozF5-l*(_}aava!XVw`i4&>dp|Z1j2Z$`gk+L=M|j= zJ-;4oLBt3g;@f~EO#La2%7urrdq)zC@8h41Gxd={_rp*M<{G~B!njf$nwZwXsQTv3vrRTh$1; z#GK&9*hrp6{*CdkTWn_+{Mm$ICbgb~Qh+x-0MIh}nduEC{4xK-D0b6G%+;SAzl6=| z_ds3!9$?N?A<+QU6a*hr;MHtwOc7g^d_~`DE_~%uplSOTWlEMco5e0f{O@OCarx}P zhtPZVi_?f~5j2xb6sGVg8j-};0&Ad$5)YbC%lPg{Lm=s-K=bLo`0D~VgaE<{W|WP_ zJVUseV?3OINDU$5UYQ^0A8IBRNh&-POUq0tX6H+TP2} zJajAyX@Qmb<`UviEZt(6MRJnF$H5$=gtuBEU<#1}WNTz6cT&I2mUj`2-J+W-dTNb@ zOI)D(O8rfDT?Uk<(jKZ&u7udmOI24$V|w5FL{-rBxVD!~o%Gn#cYOSVuI5$qauSs= z=Q}`2(?>bpjAxb8V>y-$-_9KOtTi40v{50CM9Lf88gso1JRu3euB+GE#dj@Qfj zf2@)S3tx+;^jq2n z$tYDL+sCKW*%_n@pJj72e`g9`KBPGFtY{XFB`j!IA8?tEx?QWUz*83(mzrjo@S0Dm z_BpE6;a!az^tl}PmXb1vHtumYt6+Xd_4AXIZ7RF+Q@M9plzne0Sd!~&{$>wPwDk~0 zSWF%83e+5jti`Kgh!OKv0rv>@Kb?sgWj5VhBrH}-{+V-a{ zRh{81C`&6UlxbXFJuj-D8ww`6zOuZ2mRt*qXt#Rg!#Rt2BgSxIBhl<&#PsedS}unX zH4+i+g%1F6w#GVr1|{gldXg?*XwEYq=s!qc<2Ra5QJt)gd`k0X&s{k231S-2B~dt@U@k3G&s% zhrFhN?bmc%kL3fdM2GvKv=louyl-ry^$VZ6#q z+dgLvm*Zk_=DGH_bi>}4%76Sv5+K+}sxtCs8OU#l;tV13NYS-cY|z-%u%Uof{qcY6 zo9jPajdK`}0rc$qrE9+FF~j=!l&Jv$=;yX0_;u4Yq5(~GA1oJ*p%!FSlg9%r0uJjW z?rkx2LtosoeLK>))pV(*6jGSWyPsRDuhn;+BJV`OA>kv^O=DP0Nvi z@kgeQ0BsMH=N-fRCAaP1An+JJpmR69SOiOmc;lcTR5YzTKlDZ_Qd1_T{&hG9t|i%$ zcqfE7B)ZHmMujnCvakb+mqZDht!;^ZT z|NZBg_0j8jq7VcmKjiuNXL3*~h|Qe~6`UtzX~9y-lH+8~X^2&& zE1{oEZDuk%Fgu8uqH^Gizhs^f{Un&V->;YaT7p=k+BxRVY;k|}4orkuVH;EFR|<_s za=8B(irh%Ln})nrXVYbmq7zH^Zm91cc=P^J$VRv&;&dzW=_9t^gXF1T@Y7Tx9|u3G zo;Q67Ok_LK%FaP6!U*E$M0QNF3=LmB89kZT!NV~jKNzj-2;K4CRofb2flIhc`jAcr zlGO=_n&tyP`{NP-$EsYr;i9iGXnt(movHLqQlLsSGF?_cB#D2pGhdTKK|8s3tNJf6 z9G4e3B~;_ z*B26*mr=cK5Y6j*L2eh5w~Wy(S8|-Ed+Z=^HJ(DT$Ws1|m}VF&C+L+Ec?smTSb5sc zFyGKxP~zvC?8B>%7u_g0P0ym`Q`|XUXTH~!-fH76NB$cvqRB?tSmIfqO!$~BKSYf3 zx;~NaB-OdIyo%oXha}Ph|MyS{nuRNHqMg`tQ1tyylh?$Rvrw3IXGOpbcJs})I{(H# z<_W(&qZ)(SGaKub>};xac%HGX4YJ3`4%3(r;8HB^l1Bo^uQGXM1ayOpgJumWBGlVN+?Yr^EVrXztnm3oSVSJK6NlSqv=xdQ? zIETNAL=BXUD8^pk-09pw4{W=64&|H2LKTAR*5#W?$7h=dafJT z63o&|cHihN2cYF-%Plc4a_WaF-R;|J40?UN?t)FN*7V(I<|(({1Nq!fIkyUzPQH{6 zl6W3$Qqg6EwMneSPXoKo_N$9PkXg@jY>h^ z6UDpHj7npLq`SppvGSvD(B3uYmI7!Ngr+qztnS28jqxU|5JIWE`Phq0&jNc?Lx_B8 z11nsJ=JlDrg0L%12tV58Bdq!To>1pQHtu{vnRke3r`^-xdq!bP^inzxv9-5{#6h-` zVEdi!a3ekP02PKzjOHduqT*z353#MKxbTH<-me2?)M6pnPvj&(+JOEW5B+=P2_8n* zq%n(+_e=K{}HMKo}06Mvc z5s?wCD)kod!nmGE0hq18!e+lr~ULDb)gPo4w2Hs1K~XsMD_%Z!9H#)2UP z_yy~kuCzmPn=#859gA7*RHzBrS~UYJp$ZH0`Z?5?2U8YidHE{dy?BKMs;g5FV99)< zJ_K*IryD?Thqsnt;0I)|rJldNz5QHesX(ukzR*nUxWth;neXooE-{>pTf6b+ZA#NJ z%^))cAm=0Rsr4&Cj11BJXY{}O0swIVs;`d?U+hbZ4poXX! zx1mdPcyYV{@iW5g#v}9j@9m!6*-AmSY;%TXNmlnk!Mr!$9{mFr{x!1$sOBwvk_ezo z!``iiDpzBC#inR6s4G__>3>@MZTCKNwMD%}iU`_R|CW}*Jl=U6aguF&enbj(;E<6? zLgRk7CrZ5XNE5EBcK!M;xrkI(E`Y<}qg|ScyZW!|K`7*HUZ>QvYRnqQ$_dMb_VMWw z%MHIQuu}SW_801`s(z9TAmlG)@K{Q`)!nuJjnkVam9+ImjRUVCzlXq_@|bh1jxtEH zi&d4gNi5Lfi=le_XX{@nfB5t|iX+vN^@b(_>jSAY*MltLeD}*QH?b(nhr11gwtf50 zCK@Yf&#O^YjvgMZ>pFeQLZNZxY=9O#rR4Ni2I_T#w9iP;yDwB0E z&r>+V=|pougDY2|+ootMG@lz_t$a2Az15z6V0(O7WOhjh0ICUKI3&FIKL3tm0Q1|Wn0=| zp6KtuU*uzUg>z(sL-8Jo@8hQj@Z9XGT|3{iP;Zz^_Yq;Up(3$rsxTz4Y{0ycQ;Khi z`H>-u`MGq@c{-D~SRSKfd`Jv@zcNzOl2NB<7!|zdpdS9lKBMT>`%txG6d80Q!~NaX zF7oJp5K(zPp*YPoOIZVtrpaUaPZn=3;U41L6|Jvt&I@=RO7$oh)Qx4bekbA6(hpTn zwHR}y@!rr<=w70`rtQ62y!!UXNFQ594JhO-SYzTFvNCti?FxMIq3?T2-Q)Fi$Q9Ty z^6<`gUE1*67Ij(ILK|O(I$YRj;7XMe*1Qdleo#Mweb*XEf`3@?>7(-hFWJ4KNG8l~ zRfGBEd}VHpGz=G~zI!MeVs;IWZxIzC5wa2dPgiB4@S`{$k=(JeH{L2^j7`WbMOLyN z8%|??h7$U;xi@bzIrVp>RX;*m;P!9$!5V)*Q7oKHOrE@kG?m-7PoVo@c?N z@52ugj=t(Y8$9R~RFaXQ&rS?e+`6eVZhT0dOq~8D>6GxRQZuk&Gs>pAp%b~z|Ky$} zXI1PcCY~gw8=YWvV%XQ_E>rqL1Iy%kG6os4!rO&s&dsD^_$&HjdfnznPSkGm0}6-h zhgiU4Wl~w&?L*a~;JLrK5icg3wQ{(jTZeUo5Ma;UPuzzJpZnQktSR`ROg7RqzQrq} zrR_yU2I%!v?SfpRK?y@8O+bzp^5~U|$j+hS;J^1s(lbtVEz(MV8^GZ>A1KQlZzJf#!5s&6#hzfD;H{ z$PwP&|2pUy5NW8a7+#q6Y#OGnD0M6g8&N}?xBgDf{kc~gQ1Dc;N9?I%LS#PK>^oAY z>W1G-l9y!h^puxo?Vf!IN_Iu?6w7L+{~y(Jjfc%;%sNz<(~JA{dpRE6ZPr8w1cEgXxlvTV{VHD1<=nC zuKB%uuYEJ2Dc!ZXl(cMb%|p8V$RSsa!3~8~jGrSCfcH(VcI3;V1uVxx%1r zlsa&G4>z^-D*3r5yDZov3vz=y5Q7uA*lQef*yI1)-{!x(7LJsfs38LCGAli=@dldw z#{nCr7py9!xJ5I2eWYo5V-s*v#}|whOj5l=j}1{vy@qj+v|cWF1M)DrJIG~yhhj6a z!|;R)nVRK)hrARxsDAnCG2Sr?5be%j?VLV6pWFr`#FO}Ai^53)1x3L1FSGYN2z_27 zTnu^I4I43~6`4DO_#c@Q*9TTr+QV(T*ExjMhJ!XY(uR^#Uc_b&FVpPen%%Moa}$&| z?P}mRN~qpy?!Eu0snpb#W(si-o;3%LdY>t)P#9QJp84>~`nk|+VS!{g%#AdhT=1wK z?Ih`&W`Fzv$w?4(j#0Q_WZLG;V7UVf1}l^zqn*wi^Z3xGmxj;cC#f~x5~glb0PoC? z{&+=9*tj_RpOz!)D<4q24oUdxEQB{YQ3)wE00E-`zX@*&Zk5W-v0Pt&d&oh&xffyg zuZCG*9&SW8IPdw_M`3horfD%dNNxrzoHo%{?GEts=UQOVPj8Wmja~Bht-CvRp3rF) zNCYP)eKtx)G4X(MRMPbpEb}mZS{=FNV!)gAex1b@j`?y(PII}}sY>2Lv+DEY#VKK` zNsQ&~Z308OG_}r=%Db-&Fh4+vA7~yLm`UZ@w36Y3d>)y=6d3!sI%H)i>K(pgX2{am z7F4rJKbqeADOJB5JZ0etc8gRyK%~s&aDY}0-VgcIWq;%V{p>`-&$~+Uf}IsQHd6*$ zde{ucO_C~g{QSpay>718z~Fwlrj2pdcaGL9>e&g%tvd`6I)5VPlEDf1ss3>;mQN14 z^jCH=Zuag!dd2c;r--n=)vV<{I!*SM*wWO~I**sXb<{v9gjg~jWx?7h!snK?F;pSg z-onY}ve*B=etA`ua?{|9tcLC$Vr;vi_)FJE)0oY(>)~A2t$k_3b@)EWtt}XjPx8!u zC$b;?ts8nDCf{3;pyvK#jHO10#!m}UG$!C-}iBG$8 zK8bkn=e1X(9OCArL7Ao&ZNdVp6*!UaG!*n#zWg9to`wA|&kx-3<7Me%a(V%*^DlZF znT%f|RXh`N_Q{m0%keh0;G;yBc3X8@Qu+Nb0NshyL>dq)5mgk@@mc*Ou#AM$p zEwEc$N|cesZ)pqhbqOJO#9VZM%A?3}f|8tM5QRFNJA->4%P{ z1HZ^%<+e*Sptk0E9q{a7sKrjj*Fq~tdu2Ad;tWq38pcXKL6}IA7_o2X{wkykFA;W$_M{c zCzt7z(USAwxnKH*1w9A-v?be{ZZAtB~SSgNDjTB3UdOc_hi zF$csTL7oLl!^m1pVycwiSAD&>JrI}Wd$rqlzIMr@4<=q!>3XPcNIRYbTU8tA>%%;L zVti6SUy0#bs!igqa}K^7FcgcpJ9sx)TdK*5WdRQow1I1n?T_>xO`paraF;Dt3BZ86 zgRq)tJq~#v)PNP1Sr5#yaR2S;u^$xLZhD=vA8{qsoE2clH4mrhn|WUEA1lJcjleP> zfLXA}8DqC)cXP1|xBjd>oBJPCDE1wBrLztfMvUatoh)ZVafmgE+f|9~tEIXzys*MK zxleL@+k!jPuOJU0y1y_EgRz4{NAK8l+7MVgYJ@^N%`yK|ef*dvhG0j?3u$q3KXsa? z@xXRGHzUg?Yp?M=ggudqJoAkVvnQRZP3$ZXA2AY`!cWt6-E zuc}PZmj+u*+VNWtN>kP~Lt-t+#3_kzPxa4}6&Rh=_oz1tr%0?A`=aGEmBl26uOL&$ zb@d(3RFmBE-^!F7&;9y_e|XGXTYE0SlOz_!QihhkE|xG?;Bb3pxHTOl0WIUg+?JZ7 zXJwVyvXHf;!h#A00Ja$xI}|=MpyE_gh(pD>E}+dI9k&4qY-|dk0e7-bh+$pYvbD6& zKP>deGFz^hG(fCcIQ`}N_2(60NC4JyVt=L3Fn{sUP*dysHWR}?%4YSB@`8Rv#ZuLt zbwh;^u7YQGFcVpU=4idnlpdJ9Y)hs3Js1VU7py+V9A!g=1Z_Wi%}P@iiIVe^vz~wf zScHhu)GqzK8@du;f|y8{qrLxsii@ zN-t8`1aihg1a^rif59bZTf77|n6yl*-JPjAB^IC6EWP=7{XV{c&{{QT0P$ye`G7bL z&NdW-SZUYTgcs}+wNM2Im}e5dGt_?k_-WIMU$`Q^%)Rl*Y66*J(&h(Om-OL}?=#|? ze1F)>`eDb8=Z5ewbqlBJ(+7V)zQFT2zU4zRh#I0rc7-a{jtA+NC1c}fG_^M&JBO&N z2VsS|D34D0W&a~^=m|1BP*-J6^>9~YK^8E1&G+H+Nwz)a?qgNZ68T66?DItJo=w(| zw?J=H1PyVAoWu`B_n@L_Hn5;W12lXOUYqj*tyJD#D(y8$W66ug`n!}n%@h1lL)c$8 z>X)|aQ3KB`yhiypxj@e|qlAiL6y^W74_6{)v(UqdXlB8b;{+xp%$8J&Q1;%4I6MjD znCBO`hV?(qYBYQRyp?jjAo!_bHpk=t@SxeUdHn5%N;YUi-p{zzY9gzx0F#D;q|uaf ze^u=RX$N|V?RR+-J6V?XmAS1a`4jn3X89P;&8dfims$8L!{w$uUJwAN?c!kfcIeVo zw*p?P0zjhO9#SW$>xp!_lD_;yN`aWk99`SUFAj^Z&*f3t%U z`OL4AL=0DQ8<;-H22?QO%kgUi+QRnqf0ZRY;Y<$9@^YjYbQz5~rB8G2YXJl7FOfZP zlvlvss!01_4hg@e<1H2c3Fr=hmY`V02!nNLYp`ab`L9Hz>dR?|=Y9TKJkQyI?elUp z)hGvTQWVtv@3K8I;Y){ZnA`r6O}Tnx&Px3*+P~5^dqd3T4$w_-WZuKYyA9H0z_t&7 z$sbn_S$5f4-n>`^;oa0gF_K>ZNKR;AczZC6f7LfgDpq{##f=`o z%46xEXhNaK&DL8XW7h6>otc#fN_zg=@D1-yg1#Qxm0!gW_G=2_OKel3LlqEh7r0Wt z%wlz*hzgPD*K~B>kOX*rv}^n(6p@n~y)YA8<>j5k&`~$<2xqxirgdvMylMsR%{9T9 zY&Z}ZpX|QHu)&znu&0Wp?fVzGe33Jppj6>o(SMixTMx?^#$?@+O}p%5`2#z&?E|b< z0PVkbGJic(fifr$wxq`82djT4wtB?4n&j_lo#rk)LyP&NR-Kn^izz#?blj_nlwGLO zC`O(_)jyeTehH$*uJyVts(fFw2>&*eOBVQE{qVD=@VJ%kRNTxl6U+Ih>%Gh8rB0|| z(a2x&35Jv}&`rvly6An@-Yr_#)$(&?=L*}ax>ou&)Xive+h;>am>FwMFu()8b8c$- zh&=4(xWp@l4ty^O^O|U5QDRkz&XQPq_+?t-r%k!-!e&M8nh|NxL&)(g=I(wsS>8!# z(2&H*by~UW7>b_weu{k}e-@b%HS<+ZoZ^|jj? z09-@YstaSjtX^6XRgzyg#MtEN7u*9+JaaA`O+De3dnYxGR!@fFDx-1p!LjBLS z&e8b7!0mhi*~ZuUR*Cu+c+9_xPW<3bKjyy!ciz$ms~|vksq>|OI%=dFY8iCEcg$9S zR4(sy=zeBL4##30^lX!Fla{#RJiWCql+OY^7W(0vl9BoY4C$*0wvd*)V>dhYH9NeWV2?9|(pyIU+A46$7&+tILv- z-m@TFr`(hP^;xA1*}CM(N1R_VeS(ZYR4kp8_6JEbu=M>OfOT?Ciw;OPD4t@K3pO zEn_uu0bQ(bbr`tj6Y{^i(E@kPnGbhjC#i(|+eakUBt4irO`aP0`a7S{E8-rBdnvwT z?^uzt3uv|^bj{G_bd*clZ_ypN2r|UiAz{H&euj&Apw`v(hqL3F*ZkpUQ+q!VN&l;@ zS`hKXKKwg}>k84d0=7r?{P`OKcTOJV40v2F9LsGd z>iZK-FroVDs(e%$hLSnXk&7Q5HY|y@G`jSR#2*)TS%ftki0%qeAY50(z`zu~o+NN7 z6%gHfy*)}wt93YVBJNUl9FL)EZ#X52T?(S-ZnkA`#l{Xe_HgA_-xjZ6t+AXWl>GQH z+H>}-x7FsY<@zY%G?f;lJ`tbADHgI>s1*FK~`0e zs7=wS?cos~yKF=LhO1fi>3XFp=aGqnI{A(fwYFS5<8{B{#d~zufl&u@P^ok>kax&- zZ5bXLkgBKnyRFmbD7wcO&4&YPTw}rwC&LavF2zntk>c-+*s*(^z*R0=Kr6{2Z9^~o zMi*M6we6FJz(ShD-*SH}G2Ds0yfo#K?bt*d*Ig`73#w6v*jO15=tXnG0paTE?a_Ur zhwGlRm+i4*fj33?eo%kQ%jkz5gntpVw+x-SjDsdU?lGp9c~1nVXgoFBP!F2;UTWuq z>I(A<*hlNzon0S~U-`!N&hzyd$bd7a>(M9MAFIS6^aUEG&aNTsTggiQsZDq%(?kjNp%p3sm- zV|Mru^Tr-Nr7B1w7_1uZ@;Rjp^Y`)d-j&@2p}ob|_d+Kt^gx%F$`3z1CH%X4yWB~( zA47;KE+Dd5Daa=pN}S7p2aNf*-}9IC+h-C|#pFK+DJI-lp63`O@Zvt-h^_p4Lz@O| zEr0;3zdIV}K)n~3#?x$2f9>5=4V`^f4i`YejG#A4{xaAYWM77H#Se99cn&wI*%|ll&-t!7G#ZO5tK5}hc zb<4l|zY(KWUht9RSk9C&v}e`Y?$P5dP6gfMa|Toy^J#rYY&5^45d!{Otx9vw^rMex zFq8YxP$BfdIFA!j@p_*sh~_9`2|L)$s$1TA?&TdZEQ9B>F^gOH{#<9|F`lSaR^r2T z=^{YpnZ1kNri0h_yay{czO6coj@5(dv#~LFR}(33|6(q^#FyQV&dl%6R6a!D6D7$}ok&NEb zf5OU7JtBRz&OQcRdrQ{`QuCF!SNQozF8Y630GYV;dH%@+D4;3HpoPPd$u=d);?93~ zi55crB1lek+6al!U%xht>a$x`xM@#%Yg14m{ABle4%Aii#$0TtGW;g{YK1@0NT(@~ zl)K@V`sIGQ;`iK)yKV>R}=3YMS#`6q#;4`AC<;)89qeCJS~Y99MimS zNeIys%fgBqDh_jkhJrGiu(%h#goU%H0FKrv@2`KC|MmkGAni+kvc*0%T=S_W!|GR|R8uU= z1FNc+@ONO(z`6KyX?%&*2FuE_^{g||%7DJ<^>DCNm9qV;tER{9fyCc)ahe=o3jU}z!K!zstHw#dzthe)#hA=Kwn{$4`ooDddu)sCI8jaIA?BUNij6s* zKzTSkEjoVvl{)haBvV@|qkxCFLI5UO!k|Ash+&Ys9U!_ca)63m^`)Y)39RfP)>`}^ z(2lCiMMUKVrosNQAP$sve$(jd%lB@Jyf`SxwL61Xh)xfCKR$x1*i+e9mRM5fC@{g?Q-S8pbKN4`Fi~k(wLG@U+wxy-RRN>}gc~dY+2mhoVAa1~aJuREKZ86J6xBHCT8`+3#NRzsim6tyUW!!m#0D@)G)f79I}aB_aF%46`mjnN`gq~ zu%c?-gw!&6+Z8p%^}p9dTNV`DOpdR=Whc>S?37nkXSEObI~$9o{6dPkpOgqsP?dzi z0i#-~`~hgOZ49$Vx-L9&<9ZTa2cwmNRtfo6w9o@{(;sL$oGE&*!RPV0t?(_Y8bmsC z-4>JHVQo*o`2XYStmB#vyT8AU7HK4;yQM?~i6IEmAks)mgD40}ZU_h{(p^JJC8R+_ z7$u#8w1jl`1}whM?%(bG{Js5ky{_-M&iS18`5b4EgP7%jnJN;C3dsil)}?$>9g47(+~N#bX|ZAa z=aHr0XwHsl9KOM}{(!yoDiiDPwQ%Ax`|~5@Uk}-+iw!Wn0sUiE2qVx*z`A^;h$t*WxJw!~XHQjzLeCPA2$i`jxUD8W3tSVOeyDgn zZiS!mP2mJ|%mfeX&b>Hi^k!LRuS+!!9}WoX-JoifA>9pzwts*7{nhY~d>M~ML_(_h z(VIZKF92FdcEZlD9YlYLtnLz6S$S1NOv;i8k7c<@SZJbhzA)O zQ*Ajm4&|NG4tn+~I@Ke=STT9-%=f-Er+u@IVMB7sJ)G~B#Nt{SX&CHNl~`fbe(S^H zPat&f&iS}6mmSkVa%sm=6@LOV-Rt$2`9gmcg(nFosy8Vj2GVfqIAF>d*S*(pgX;Ej z$N}?j>hGKsXe27gW=Z~Mx;dPbMf|eqT)2_HeZZ^U=fhtAx%EobIQnPU?7sAa7ybeE zgPS)bG1vZemTjbeU}3?S{y5+{HJXLk3OJsWJ z51hxHcS8pN|7z^h?M65Pw>3I_1=AeCn?T8P%^}N!yTD=G#e5Fe=N7hhQ3lT8(?_TZ zpIU7L-MH4Wt6ZVE{5WKj%p50QqYelb=3BLY-gR)g?@f5knrN6<+SUhy`VhJZpYwfs zd{Ec7cn};H?zXU_yknM#^aQBjkc0&)Z_8-b~ zkU~CruWHDZzTvc-$E1N0kE)GRNjZ{Snl6kHws`1)+()^R#~U-D~M4 z$aoO+GL3YG>FZsBPfop?KRD>LHT<1&DNu5bTxvjMQKkA%H_=&*HWp#il6ORIxi!{<`zF)o1UYNG|8}uhbj8@JRw5^sBg`R7by* zA9LMZp0Eo7OG_+2%L=oQx&7gfpZx){Ba}Q>#G@zLZ(^mxrC)GHsbXsEphS;D&Im*KGLw&*sN;)kF@7SpI? z?S#&}!R&sayrnj})ZSKlcfDq2Y&`ON{b$lIifHBhGmGhtysvzqYKA8Z-}NU4i@Ww) zG!*@B#zFTdYwU zeS}7u?RAoWhf8)NI1Bb?+7AblSw&{!&^<6<9T7%m7rI)WW&gr<;}AM~AsJ0X4jfh7 z1SnWjnhg{So&v1x^?eIDR@C=%Z`K;+0n-e5qJKVR&o)}Vm>g=;S*9~)$FdR7sL$rw z=~v)XEluV4e6*P|NYS}=YKC@8EVrdsYQDsGn#6AJ4%hHSouz3a|0w;|)44vZoaAAq z6wgyMjMjkSu1ZLx-UMNtrr^W3sCg^?IRo52-dXt~L zPTQ*`^==Q=pBwT$)bCR~Cnc&_1mO|`_9frP=^x0lmqu(!%5H1Ng&80PS)I-hPSc4&Ul0x zR(axuYV5Jy5ICGd0RwY%R!sYxwjC#qi6(h?aAhVF#sg#@o1ZMUh(+268u6cQm47jJ zfp6IBV9l{b>ZLx6F3&1DXv$v-v%W|ED3XhlsSj193afaw+Oe4ObofpQ_lg=^%O z;W`F5y6v2$Ah(Vtk{6&^fm6hB0W+g%ZFJ73$UQt4?PEOG4=m!HN0c*6*@;5G|J1tJ z^yZ#Ut;5!Yj&zMp<}0%a#g@nUl*DqqrXxkS;>|{j%U7@GyFi+BTS8 z?6k=Mi_%>wx>o`mSj-t1wjtYdRN&h{y3j_P8gi-RiB+D7M$NezCbd%BQUtIYeLWMZ^ zYr~|%`M|ORkKi6y^EvWNcV{yfUC8&R9G|rX5s}G(2>R8xvQkqGC$=|{$8M3NIP^Sv z;Uzcs6vi=QUw7ksN&sp>?$uScEU^E?P`XkjCVAiRYRE;Om48V7x$p+;6XWsG2XuHb z;v|wHE~^#|(}}aYSOex10S3OK)P7aN4_GcJ1@T&lLnWqa2OfIU-w6yB!Bk#@f1lqU zc)qixukuX03BY1YX4cjP#-lim4@F8_eG6W(Tg&{>t$=vTeEamqNTqlb{aCT*UM2C9 zvrX#Hk@(2kI3@H~UFar+w+X&W4w`Xut7|M`D7hh#5;g6_bv3NO$h-IbMi|z=vG^aR-upY z6lYBU+Ex8R|K0hK z^y@$AjBhwKn4&JwSAonEo!KbIY)Z72jC%jUGi4lOyg$GsTNX_sgZ&%sGjtLrGS^9; zDL_F5&DWC8Sf0>;=HCA<`3Q1b(ex|Be0S0bS+QK=OUwxR@C|}@D``Yilt%i1N zW5N|`gs@WT$X_%lv_t7iWe$Hx0>Lhu1?oW0LEm2TXTo)EBgX$l?(@~$U1a0zz<*|x z>35Q0*|_Og?XRClxQPioD?&|AC<#wyiAs2p0NH4_=zPTIysxpGMM(1AA1@US zJ}?=f%h-M_Z6V05;TI<0zAM5tTk=W4wH?FJv=Kc^B8I7{%{nSk^?)6{41xo1`CS_=R>hmBj%fB_Nep2VC#&jq6>4 zaa}TugyF#L;9T=e(28B?S%&XEG852uxK9o?A$(yY1RbP|HZjMJ64KX)^VDw7t03hj zs#|dN;GDA-jrb*qPA7TxTP_ql@8Q@2y8(YbZ^p4)cd5(_Gu$#|UAz}Rj72jNqXzfa zRT>fb<$)Hz^=}kUYMD&sRb+RF&IW5cl-qt&x6uOG*D;R>ccg>P*4XExXVdMy*^x@- zo#jNv2uxg?a(;4)Y}4#Xt6zxLUDf7X#LIA{wH!EBSr(v97dqczo7jfexW# zN-+JL+>;Pxe)w2~wSz7&D^oIuAy@A5Q%F`x8v*QY#N8S{eA?yB+mq2ND|4C9dbzEy ziw~x~U*$nIk1noKRpauXMV^FRN^&)*31JLB`+ELK7kW7`F_Y|&5ZPoGcAZ?Gh5%7X zJKQfIx=()5SBGf-L_l0PKRA;24Q0Cbu)9P`IT)CPAqk7onrFTfb7yp@@6zbhmMx)!t6!P@o616^F(4=fFQI znrA@C(YzuwCzlQV$*S${hJFkvtAgRId`;sTY?;eyF$9`$BR3_RB20|FFic5kucH zig`$(o9`)XHidgD==&71%nH0Gban6yqO(5f;K;$aqENl9r@a#54vT$XH4hFJD@tG< z-?;tk9@AVUy*Aw@S>{W8`9+oXLU{cZ--BdCh6RemKYJ);u}IeH>D3K8O_9ZaN$f(A~VW}a|9$4ZY zOrWplwuB~j#}0SRHu%hg=aKB@PyB(j12q|O_R)Dl2u=2zi9MM8{g_mNo%?v9#va&> z)Z|oyM?Bng1mVO(lZL7lLu6^pGP}Jc;wy3jZ4ROur!%_s@X(BhI%Rez4|tF0LAOg^*wCWERbcPqwB)n z8JQ4zmU8j>qDByd@Wi_g-QN5TyJmmQ-pu>mJ3NIqH?+l_2-05p9U$T$fm-X-=;ZGZ zwX95#Aw>0mM%XnMcgX7!{E>p7b_^WM8vRypsC`OpoCIr&_5(sz|aWNN)v*K zB|+sL70o)-KRUTr9b{IAwZ%3kU-}7rw5qbJT}6G%mlD~om4TT9RpD=2jId($hstv5 z&2}~(0mNT}aJqNDA z1-IO)j7_(`UxOo6ONs3>L84}@GZ%loocmkr_!saJSxQA8!U7p$Ip^$O&C{@P&m?=# z_WerF2Ib*0G{%E^YjE{h!F66*(shY#K3NdXglD}st}p*fR6_Tf@m2Fv=si|owie`x z&7hi*NrMo$Uc}`o^K-q!h{QiruG08>-1dU^3uEN-Lzjk>%N7uj2$zpf8Kh#*Q~@L* z)WgBGx6NbT-s}Wh@sA-?^F*{WG&Yt zsC>ONM1n%3rtv~aK+4{P-|s!74lsZ6PPCUB#8aww8ikc5_kpeQB(kImi0Y3Z zNb$E(NGCIaf{1~tqGr>SCAM#SbCy5B)n+0yjhpv-8x1t`#1b|dD(5&ml+bhk|H<_H zX}e*EKDPlzSpfl!YGKfiV>?a4oHkb#R=Vw?C!-jpYVJO2KQu({Kq|djjz&tjOFxSP zV)$dAk+I|xyc0Bh;Zu9C18)=ol&x<4`_uwzAkOfJ@e8bN=*~ptR)1s>tpneOCf`d= z?PnKnT`)f1dtj6tzljQo@5f!^w&d+TQ2u%wlDCe(c$kIdDnGO9wVX&wP9%u8mOJmk z15ScsB0F0_XtsnBHaWDaai1?9n(5Xl8M?FNHI0v^n0Qz$zgP>NDUNBZJoYga&I^Iv4p+3%jqC~d&#Vk zYokFqssX@9U(t_|nt(31e=!S9M~jtco8Qj9ca;2L@y?hXr7w+24z(BC>Um#FCEGDQ z`@I4imr#K^yP@52_FhiIc?28Ou&`Tx{BHI%Q5I^XWLj(Y;(y%#`Kk>H`DC%mppDRa z^reM<03a@)I0hy{G!OY2^Q~@nDY97{{P4A82v@>p%HS{OZB-6(aUD1W&Vt_!w%roY#4L^@=Sre;2Nnf zW?(KWI?bvM#3 zFw?k#I(k;+1n*Cqd1K}xcj=c3#T$YvC4qg7ShbTb3$=?30ppq=Ea0{)@$y!ONQnfS<-LQ?a*5JcL^EvKu`p~m& zj=`bL2KTBy_;O)wq#Mzx36mkz8-4yiX1OD_ewjR!In-O1_)fnZ83{Mazy*sr1lHfd z-;2Qjad5kfNq}l2F^#jEygRci^#;gcBpgntmaHHJvp^#Xfqpjor{jbpXZ^6jKg(y4 z_f+}om7}`pfKGu|EJ+@>%uDX4Q1u?@clZ@E+GvZ^G%oe59_36&dUlZ`)?&g zs-q`dT`}|iTdSJ37dbyP0c1U!A1HT3_(>6>RPXyJ@j}qPW5md_l zVEuCEemNk@P^}9*f%x(_J?&hdgo{wYa~}2R;5**xUdF9TWPlhmW!};+jCuZ=4xTlh zM0xw?C~`lffG&rjU{5er5BPuI9P9i_`+Rp0?5KX_Q-l8Q%Yl$ zwhPQ$etdT27=O6?CI}q})ygZ6X0{RQpEOGX(CIC6B98L32D+4kG3We^@)3G8Q0>r&J>^%c^+V-_bMSrxM`iAiafU2!P7`r5;(s{S zmE0+G-6+vgu8ajpTkU6=?03<5m{PATpcnBR;`T;Z7X&|AY^tT?|HR!?+&VrS+XX3foWkL&y?Q%B^kZgtX#KV_iqdqW@*h{%F73&_t+unQEw~O_0wa zXj*GiPlEFPhd+W;GD2sneU(8k?|s`-0aSD^DtQVjC2A5>=r^>L7W{lM$)2B*WOn1- zN1b%J#6u7blBgN{)qYSmr?Pf7HgQxIRpj>Xyv<|%dSG*!8K{Za0Rj*y=tcmdI$iN6 zbMQuY2aj_=%yw1`yLU3@X!_MhpREG?NqcDxFflAHnRd zm73mY8Io{ny#`+i$)+&p&2?PAC2;TA_wkn2);;9rw9&RIJfKNEffNPbCg82} z8)WwKF{c-sI|Zz{|MN=u-988e&$l`QKh9v@*iWqfm>=|f@92Q-a%8-Z%7xk27N!X# z+@?r5ighTCc~jokwNDbGm9@8BY`A{)G$k~S@!>`YoybhTnb?(NWZsvXgS(zJI_MNy zPO+%zJL_YjN%F6F?dpsv&z|C;=%*caYeaDiQ0j@GEkhv`(J78da@V_#*fYW4LNhyb^5EOu|4+fqb@K8u&v1A%WN zcWT`UmX|(-rl1SGHlYqPgW4!t(m!pbz3_YSoYUDFGjYme)E?!uM-f(JQJNid&#{4) zfWjH%h7|zQctUCfyr@$pZFN$1xBs88VpeUASxUjr<67y%uaG3hcwdz2S3(m$?Ceh1 z?jejaRmPaIug8llyOx0dEm2ghbQ6RkZe4&#x>K*6kOPb2%&l0QPm&(VoVQOthFOg} zNf5a?KBKvOwttrn#&MagwmVPqf-PXLp zAnd3r-bYF;4#@2Icnc#R#TxE6g>Z++W?aE`u|8Cwi0a4Qt98IDD-0Kye=vJq&+){N zDQjK}ZpjE5$THYs*pwiFn`)z9!v&T1GizDHFhOkQKlC_}KR-2IWT(gb1a^CCt}md< zd|aZDG}~sx%BqZCxk21p+N$ZqCcKkVe&mH5iHkf)^y+%mOisdHC9!>M&4;5o@{CaB zjNXRL^SP=;<{^L^)0d3$^9(M(u2xe(k-2FUjo3+Iz+S>HyDO?+W1v z$XbIA+=|Km10W;fL9n31F#{ZnPZo)Uk}yu48Vco#v0|8}LsbiHprcO*MBB-0iG6JT z=H2H}%MDXCSq)u5pUPVwf7t)AQWxAlG#&uM+0c&N6)p)mQ^XnXVv5?o8%gX9nF`pwitaRCaHDYJ_jKF=>Zepu~}`~Rk< zXNx-NVa#}XaL$6ddTAXCJIoH@+HW+c-8&0+sn7$d<*jy!6A35(5W4;Ae|8#t^|P=F zPJ$PS99}oZ-WcTO!smBaz=$v@RAqwJ>PKa**kL!1)T#Tp^GEXauJ_nYzC3E@@#Gn7vWL*?6* zweA?(!R_x0GGw<}!jUv^ySekYEn9YOLh?AZ-zOPORv|M_Mc0H;GRWJ!De%t1b-8s2 zsa1=^1j&TVfBM;Q%HyTnW-5FUW=Bs!=&GOw#yWkt#?u9DMnC_{7-{ziMyhkOdqChS zlAN9dN)W6V-gsfr?){0f)ZGM~Bb%w#^7%N4bhQkTNt;75g20+zxLvSR9NT5q9xpj4J|$|Gay0~fodp6M2(x-f9G$K9hUYoh)bd%@ zqR(W%)~LgMOi+ZMC}>?LKV$2znXgY8xk^see&|s-nrEiqjecUj$t|1veN8%`)KOOc z39?$Oo;29Fsio)otAyQ;zOCZ*8Ejd&{v5-Zjx92rKr&Nkl_bTH0Ewt|jqcg3+G6Zg zfPmNzGwo2(hn{Dl4tot(lFRuap5=@Lz3@JU#zuh#+*_hNA`Jc~AFb4Q zYA@3;dH9jnw*)(EH_$AD&k#Wj>S><7 zg~cYzINz(s=-KIDr&kPN0&<94)A-bdJ7XupY}c!?T1_j96T1i;@6PU2)p9a76!=ua zF5fSG)AT#aQ~Ab$(B}la7%7fOik7qoX%bo+#t(f;8hBnTNEbC zPJ`?PcsZZt>=>^l!#5`7?;>kWh6^EZW1^WxpDcQ;jSb_CHp^D#qi}pUd_ds={E+@FaQNqYks*Ek2{pGkoEl_z_ zV2YT}OFy(!`AuxD;VrNzNJaKT-A0Z1PQgm~hpV%v0*CK|C&^P1WGr({ARydt(p&y* zs!iSMRT>3C;We9o-aWPadH+zi#)07;r7~<5=>E@v8H2+#}Ed(8LsquIg_NNy< zTQ&g4BFPDI?kO1osvxXV2)%iD>kz`tE!RGWm)j zgBSY`J(leN!MP{Y(@=4M*S!9OO1g;K?c$}8N))+tPC>Ysx-oTe9nph{7rxXb4zA@9YBAQSlI8k%>)g%v>Wg9iXK=H z+u~J^da7s$#3hoQyMWb?{51-%bu5_=AOb!BrC;b_)@URG*VF)*D~(HV#fMNIsP6=U zUQ;`nLiVTK*-|d+FniJs_3bH7IOxrs?bF5@ulKl~d5_ssntA`(olhUf#9#Y-7W{?4 z2*?8Q72khT+<87WD#G)mL)2rM4zzXNNz!mLdB+@FJ$rlt*@4bkPdWOqLJM#*#ON)= z{Qghx-GWI;ww62i_tSpg7T!PI#xHWCh=y~U#ECv{{oZ)^*z?wOc6RB%+GslB|7uaB z9OP^k2D0E^MTDOFPjOJn)e?rALQkuXqnEIUfz7Jd3L~9|+#%5v8ibnkRKi}HFtR*| z7vN3_yX4EjRh2myQ;@$2B4*)GC+TN=KM*Y5FR2Ni3o7ri;0#UYN({BSW-qg?f6|kf z6Qg(L4|`e{YF&+nHdY3H6lKw&fZFJH?0(!pg_RpKF)03QZ zJj4Vde9@)w$1MenWhk}o7L(S%L@`N>K#}89ya7-U53KvAduQ>bEyG8|93CQPp9QV= zsTI{WUIQx__M`ZODqbqIf0Ne=o}0;Jrl0eTsm=Z9Il;a&;{myC&#?L9x_z7K3!Fg_ z=;28XK}6=^W&>Uj(^5!-mw*Ezx9Zp!UrP!)myrW{)#GDCZ||ldL5dcn{&^uOiI#V| z7HCbFgROKUXzKUAsZ@Cal~^Etd!kHfHnne0?c!%)*u2h;c8eh{&_|e&$x^ZEFFB5jXLsP?!Bs;Sf`~}tn@U}shaWwB1qzQ>TbXeWh ziLG^Uo@Rb(+2@J7A=sWQ1}M0dgsKZZ3$5`Et{$(`{g_Vu~xifNw&ieu^0aLlS*J{ zd<+D4!UR05>xk=*xP&MpPV|)ED<7i({MtQO+~(rdt-Bh0i+t%Xa_Fe!czc>uPdvQh z25@8yZZ7bm9aWH#@T6x}v=oTAjOthDOJ1orf2FrRtH|+TtfIy4L9V|k#BQ$MZk^Wr z)yzJf?Q}~-xX5xio60Dl`-NZAF4S+AU=n1IICNbqI+p1^rOZ2&3CNBfbt{zf-4z!t ztzdU8EO9N)!pv~;X5V0TjP+8igF9)Lvzj(dDeH(dR%w;e5T_%8(V`yFicUe$6Tu(N zsxq#qW|Wa{ZNc=`sC$Uk#kT9BSJR>%0{j&7NXQSIzW*n0h{mhQd|qojS&QP_p3F zy)`w5x&;h8_;JvF|F1Dy&iUW~`+3hfUpDIqDCzSAGhS~-nrTx{gPj9I8jA<+Cf|ns z-t0Qz`oHh#b?V*XtEw6;>Go^CRS9c8F;yCX=ZY$L^$+geMV>1@_)b>HJN)RLNxxjr zFq`~~SY_ygbA+&QBZt6vJ0ni35N~1wfj`)|iDAJdl$tlYN^`4QH8$#(LqH#kA&GK5 z3euoduyHJAy-m)~cwFK;4#FU`G;6bWfNdFy5-RSJLcJ{cpQ$Y@Rj&2F-Z&OT}s`87nJ&^UGZHb znQn9JRt!+CPjY=M=W-R}w?^|Dg3tVJPOA8Z<+#`rHrHtGgm!a`noHwNtWxyd$J}Pi zBESU&e)89y9nT+#wKQmOK0OlolN1v6A)TzzoT4C#>Gx`)mKM#gDNLnj18D>5hov<2 zhbf^7RHg5mAY46BdfqS4L&S8~)9Qb2HfIiB0L3fG!}ITEv#MfPp&;8-JsCPYW^Nk! z=G85qH+{}4$XE*546EHGv`<=3WFNb|*7N+LBRl$_^et6}>d!M`OYi+!?=DSzY@exh z#A==0XXy5?ZxPsPlG$SQT!H%A_&m!V{lZR>g{o>&CAJVnYFwTM#~zg51}VE%6A!oq^=6wEgOCxup2ujN(#-kIpQO9`LEP~m!$6_Vzbp;Z$OPEkQU%2vA@nK8W3RX#&#FrMv(|i;lW0dj zQ+cl%d@R}FzE9k#ah-;ymw}vm^v`Qndl3wCp)ePC?Wcu!Cv5wP^x-Q|K%`W!SoGl2 z#|O@D9vSV0J0{Ej#+i1jeh!Q?pi2DwIzF*ToXpwgVf~NnoSncDlxZVOu7 zVaK75GHE&VMx@Jet9&riGsTw{XG4F&)lp&e$b&~*!WA`Oow)U^v@L3J&1?LO| z;}t``T!?DI)W^GfVv}V`rjo6%GAd5 z5vPQCQFop(XF6-N^Yj;t*GBD3*qA|@M)1G#_w~ih0In%`0B?3-n2sKM0Q?otC4@+0 zN+6u04SO!>WSGGnJJKl-h5ELXFZsf=E#)gh&^l5W+eDj5F=0aqJOH4D$HAn-(!W#E zZ(mCFzy>%C(OiDOzS5^cykDzL4J3UII^y`+Bmd#t!>J@qwikN4OwMjjY8CHX(G$I@ z_A%*dcqiFb+a32Ol_;N)68w^(u1*TAu8;ng0^TurVrjyGt+>c`#y8nNd<$0&bofff&tuWG5}wnvS+# zsYD1CjBoGgw#l+|nMR)M$pv#Ya?V(5{`iS+SCL@qkapXj4th><)z-*K@Q$s1|2(SQ z*2xX@-$558diDX_VH)n9=7r>xtE7XN7#8iigaM9*hGF6ALo4W`=iQmPUER`XV3w_F z4YjraD`mt9-6e#Y4$dsk=kJ)z{YsO?${YvF+iEmnOwHB*xq)+uVOwO0q z09sldl6Ous-L1#XX+aB4mQ8PRW4s46LnX(`nr1_bKbTQ2KlZ*IT-BH-p&4$VyEXgr z<1@38_ebO}IH^H@uAO3|;**w!oau7Q7HB4~rJn?uIpXk-03xQqksiQ5g^r;GUv`sAY6E4H{iig0&{1Wq)DkvmenS< zT;S^$eA*mx&TROD9`xb7E>B*a4j4+LDEDX_@3&^Rs>X(!`(}QfEZo4SnwmyfNL$mF z^r*z;Hp820+4SUZ9Q@rhpq8t;Yv7}gOn4&r`kj@<;8!M3mr82ZpLcNPJ9+dQDxC)o zt?+6ASw%;P96`O?KTqHzTD{N#K*-`mL2 z5TaYgc*$kyk}c$_n`0=TNsreZ0HBQcA^}anZx-3je?9eKRF4JYHxHRdDB)KG6?Vcn z%$fvRh&7#6aUd3!fcv^U-UcV%v{zJmv)dGe>xf0+;z%2RS(~15U2*%rWNae?y?ykfjhjH7F@dLr;k3ZEs|jpwe-7^brDqZ5GD% zYF~_MtsrL&jX`6ENmK>AS62v)s|VMQGPi)h34o8H^+G+6ww=x6XyN74gT?6;dStMy!8&U zA_Ir=pl^uBzvp3Y{x+VA&ML)qR&E}v+F?^3-~S*Lz8tgC*KtfN7=8EzOY6W)XmS(* zAO<3nA)FBJYg3eD`gwfmR44J!3+r#iw+}^>-o7ys#*k)%LANbH7Ily}GKtPITt?Sj z*wivUH@lI#Rj*-B8}*=}Mm}9~_P4Unso*+CRic2^4M_jDk>#OM+db}SY{PL2yAc_Fx z73BBda&ygq>)p!_GzONWbJuqS&$}{t%WvDYM7-gYoGM^y(WuL{j`PYxR#iFSLQr?r z#M125=98Y6RTdxq$(Q`&`Qm%#TiY_{*j;&j8V%=9&n$2r~3lzksl?Ea8W6N9-$=j zIAW6Iq|3UT>B)zJlddKvLlo~K1{6Uyv#M#wBT#`8S3dlGVDx@aErK}mvAvry-RnCk z#nFncwegf6F*%yV!9$EP>sfsuBjUv>5SDj6xKU)hXm3 z(%M+gDBxD6^yO#BQLGS*5s^>WPXOyBgfKzG7NC7$m~k~vkKawhG32%$Cv>lTdb(D= z<0!_U6FgvLTbIv^mo^S$i%T_EK(YLayt5G;5B{KT`ir^-RG|))b>Uy{SZwiHDmjo1 z%`^d>8BxrL6V2%Qz%uVfBj|G5-iXQipTX1*6UOf4T@2!QwaK8|% zh-YD9Vs*i^>?T928x-;E`OU{Nku+lCFsbm+xCO-q>X?!6+?{6Kae#R9DfKfUO@YFN zLN9Y(n8#OM<4~F1CfXs-6wcAlROF0oZ^FORLwC=?d{VxaUGENjPVtuKADDoap^z|8 zdzffdxaSUx&~S6#h`sZJuO-4ynWD3j`S7QJ<7>wuZQVD4Z-qz=83?WmNGl2;@UZR( z@rrX#iT$*nkf&L=wg=t>;G9t6$C^GV?`|1;Q!ILu7+TW>QhHzbP`qu*C68vOtlVm$ zj^q>@y0uvo@I>rShe`&U-hO)QQ&vq7oWt?*%z(>i)cBb81!>VeMlzouPkEwUC1yJC z(_k|5{mJyZ-8iYT^=Z%y{+G{s)0THgfiil~lIKdIv{F2Xe5Xz^qC@uodTvMIJv_f* zRQk9jQOu_(z=#6iVKp+oIOM!_oU?Qx@sRYKxKoe81(k`t-W%Ce+EjRi$fjU;AOyD2 zI}u7Oi+iw4`-IAY%wEu_o}?qzeM>O~BMMUvFqkdf0xGqe2xBPTf{X6#o>nT|J3Q2yx^cg?x_jlE3Hx~{|tE%iFa za}`L^FT`VK<0>@fX5v46r^zCGd2@R6`EQF1il!6oj>oIv1nTx7j4mH`b%TwIKJPh(&S z)(nvHkQ{a2+?Si3F;;jUgv{)^*-OkPF#)>!L`Z)x(Y zm~K@`^KMaPMS;T|Z5XF1i9KbI6g&O2XJC+X=-`$QM|2M=e{&FDoqNAUAu?Y`4A)o5 z8Q%j^AtD|8@8}d6Q#u~`5*9c;4ltb~vG3_@9=YTv)_~zm(USNs<{HXZYB5>!oGNK< zry#l*CT#D|VC@UZJ$|X7K?~|k8Ga(?1@}A~?;Eq#SxA5~ zr@!nwsF}_g7Gje{6HCa3uO`RWe{0}AnjP1UcaurS)}9*wQQ2QkydC{>OQ7$gNgsMN zgV7QccX!*CQD9Q@z5p9HNCosG(gNk9eUEH;L$^uoBg+m0i>$PuUgq-gS}&rYP1wnE^WT_ppRjy-!-2cIcUGJlwiD$DU_v-VYNN{0r)vc?cf695rINLu#iQ#(q@({%lQh~Fph?3`VER?*@RYGvG<(MHcOI-D z0o;Jy$}WKSrzMvaegwoW=XL2Z2%cO_ec%FFB=aN>;9uOHYbVZ5zOYlQ6Q@XeK1=83 zay0b+sCo;psJ^#-c!nBELXc2Ox*O>Rk&qA+1f&sA8Ue`}1f@$5P*OrVlv0VITe<~? z5NT;3=Ukp(2RJ~n=s?qXm>@Jg#I`C^16CG*Qcu2b%7 zAYV??8dLpZ2u9qox)>$Ost5f_QSl+vz+t~khE}76zoPPm>wGxH2bOtL>*7yiSvQ6^ zqN&ntPZv>}zfYJ82`&GSaUQ{4fAme)2kp6>9*Ma%vOG<^qq}05Nm-<_skAY<_dA2I zjX-B-WVT4^-S8pp4j01*vg@^Wy_jW{VYOfk_r!~jt|N+ zT=qez^B!)ee)yAnTQX3JhxDm_`X!2`v7~ge^ON4Eou8NG<2Q%=N+IBq!*1*=JEblX zJ1HVADd{opF9#^$HX(}q+bs)@3!!TiyrLQTqbNezpe{6k=`o2zm%HIsAM`*8?^Z4`v!f4sngldC4~RH((MgQ(K@?MNZjiy=3FHF-Gs@Fb8?@MGveGJNG=Jl^!PmWdrVDc@63TKHv(Ie{-?(mWV8_n-8vE9-YeWf)YTJ!D$hCjTb+uvq8o_(DK}ia%KQ+ak zK>M&T9hPedwf4L-OzJzK@Js$Mh36{IEq<9@Lo7+yoC+kgi>X>+aa8y=WGv6O4U3s$ z9nEqXhwbY&DCf_3rP4-m%O^Gp-I+y+waKT<1N#r2vpqAIvYWPsRbGjcUFNyTLMiGu zHlfeFkMG|2Pqx;I;KCw5;Y9e+NMc}W3{F&vDa99}u)b&#SzYpm(XL)FvPj6k%xTvk zZeEU@2->>fTh&P|0D|A=0}-xu5?X7@>bID`y{Ody2ygZOxb-IN4yN4E_fAVi^{0`_ z@J|BqQp{874_#lS>*|oy9Cu!}6Yr_k(hf;=F^)Qja}ptB@YJmIiMT}ER+OuUQ<^zg zAl5`l8|(-MhDO}p9h(S1I7-wIm*EAvP{GTY$ac z$K5b-9*23VD;Lh#{qy~l;Cgt=fQq{+QC}LYjD8Wxy0kd`jZ#+N)u@+a0NPwA`_V~+6o8AG_aUIVQW`@Tq%=5d? zoH9gGiwLm>{>8ryBk9h(mOEsq!i`bd*X-NPIxpKS%kA*{I_zAz<m?(u2lY7(QeCWdHC)SxAKwM@(_>;5_W8vK1Y! z8&PAhe)TqwQ(h)#xxf8Bg*9fV8f2!2o9>?t_~xU|loS+`5?h+4VZKk85!3aQMbu35)xE*aMZ!$7U%8I+kw)3*FO6RRv3qi$ z#^r&YCIvv=)@=ppoHx}0hgx6um_oV~+$n9iBgqmv;5kf7hx^M7O~r5f?N3nW7<1h1 z?8q9!>#$T`^lHeF8p59Qn+#37=y7b0;>>#i?HiCmh$vH!-I4I`mO_HUr~sb^`z-2d zo`cc63+@uM?6}7SWc*9&d2Lb`wVDw#~;q%&E=6 zkt9r0KsJnKO(OToB{)O*RoO0Ukc_nLnFP0VXOkJ93K> zSerCYF_mp~)UYu4!c z0{l#SOgQdY`&Z~`OvG5f>Qb3FW6|#EzLTK0#$ly3-y<|A|ry)Jf zg*+P9zYVIUoT&g7kMqr|h$*{RNo)-$6lY>P4JlIA{s<^ULdd>vw|3Bga(KC5&gl)czrRiY(U)dHwpM#%AGLeCnl zzLIfDWUFQ@FVD4~lh81^TLu&71|6k8TTyn30GvC9Qp7PoJK%RNz;{E*ut&LO_$|q) zg~h%5@=;7wZdp5{W4V8CQu(X) z-Zv^K8{zshYJScHj~=q^2#>||P9tJ5zDCUG3} z-tAY@Bu9Q~>TFc;0RiG!B-xe$E>%NVv-NY<*gNE z(-Htm#=EW~16hK-q{dkwg7yh5m%c@ULzifB=a2-qH-tL$VggBEB{K%Z9-uV9VS(o* z{cC^|Kx$zd$KdTSVKnO1FA3Qii!bGmN+*gt#&H%6{kMNs+@RWHuIOI_nrrFqdPyLYn2g$s68{j6eULcMLTwta*GMa{*XDd`II`)1&0z*h z(KqUCCh^hy!w7q#tkVyrhl<+rX8VCpg)9HzYc!)E7QThEL6-vHcZ~pB>^ZK`l-3_#Pgmj%%}XH}YRP)pS=` z#u$0n5?(s|DJjTJ5)dAs&hz>G}jMWKK&5h9j!%>RJ7mJqQPZ?AhDIn>bIAc7n=bqo(k1rSbl*xqjAGN$k7(E5oQGt;O zhjwn2KVvp?@t$WiDtJWJk(=lG6+CjpeM~*?n4EuiSLeSP4Jj{PH0l9x8k)w%_Egz0 z0!T`@0p5}rjTNLv>lr2HrzwtzASM+qqt}(eWDoLd3OL75!geizloDdV7F^PPrGXeY zo;eiLxuiDyOlg|((VnNbGD@DDLnT{D3wQqn?(5ZEsyPuqMy zUKS+3W%IaZ(HyvF03}1T=+dn|(9h^~ND596hmO2I)4?P{n~#quY>}`BC-650lRzKu zZM$HcR?GowZMZ*0O7&|%;ZpF*^aVkJ$R#@Lbxr|V0m>!6iSc2XZ=_fCnB%ml$WV`a z$5M%Ow65P348o;#=D*JQk=okSF}1i}tkkRU{J zPS*SPwMG)kAMw{#o~d0Q3VP1w^t1n_f@Ky|s0HzmptgP_->Ra0BF09E*i+knG>&M; z9B*KwGo3(CH9**by)OWrmtPXGNf~8dW(Yvpb{Y0tLvI!JIlSQiv74VmAeMtWF<2?M zGI9o$%-DlmA7eV)AbHqA0C@Van}9tGE5q2dIdItYC~q>;EppT+;#Aw9H@kM7cw$eU z2!_`tz#galuIfYG`lLem?Es?tCGGD#ipGM*cl=JaRcBTkmJi+2Nv z6JJtk>s;~~*sfbrKVqE>l}r4;%!R}MaLIM-j8W+0My@ORQeZtq=n8gq*a&A8X~o*z znPfjzeo!aN_-sV!kX3dp$B+!5CU!SCpi=#cr{j}EMf?>O!Ki&gx%v7>R+RkQ0zk9z zgn$d@G)(=qOWO6OswUQHqT|}rjShb2)_kY}!REb^LP+7m``q{t1l6aci+kE1gj9YX z;uG7l#sjG=RKY6LxL6x0R$qvFYeodXo6tZ)k-(%d0+Kh@mdj)YxFa2mhd0ZyWF_c4 z`J$7JN2T0(9ozUTA|zQhbX)!JN%{UkRJe zn)|;?g)r_2g=xn3eDj^?V4O8@IwL3%UT8F>Zyk>=Zg}=Cu+7~5;yV|FmB9Q3onpf@MG=X}xi^@d{Obh%RuBt+Z z^N9+WWdUhh4Eo%6H$qc|mhhDKL4z1I6OA+f7J@>|&F6N^-H!z&n0<)VyjZXJ6x(j; z?W-B_LMcsayh-M8Rq{ZwljG8~Q>E`lg{RQ%H&ycmTe#*rbU+dnceuu7cYx75z=D?j2r)>=EmPb5i0Z2NdgIAp#4P=)Z~b_xEV$82`d^l#$&@IFj)ms`b(~RQ`!FqmhP<7Mp<$cg-(wi>4K! z<1=czU&>#-DP=EM?VA0lFZ?z&=;V&`%Z5;|-VGp2fWa@n-@O%2^FfokS)B$cU)b?hi3PVNZvW_h72c_~dO4Z-d+>3%M0upU(%uAAtx@85^w5m!z1AZgC9xZ)6>R^V zU%e{n7JZ|y=!!!b0wK;$D$kKx-4K~OKoQSX@nh(nmX%POTn)w%`^kS(O|uC2h9FHr zgApq2fED~=6@!j*4@^a{BKLgHHVN4DXiMkySWtc7K7&3lD!K%0R5j{^MshH@(1y=y7tzg-CT}E&oQ_+ohd*WnCJDoHo9D4$k*1 zpW4=9Z{Kc|W;FLsTwO?#jH(a2_j*_)M5KS8dH`4-<~d+`%$k}%;BWPl zV;7xA6@SFYlEcJgur;-EYyg-`E`k?eGac?HsDUsF{S|0nKQQ|ifR}}o-_-GpBgA>3 zy6RbWG1rp+)W!yb5Y+AX$vY{xR%F>e?Vn(u(+dpwCeZdR!#k+3#^>q~FG)_n6(QHF zK%B*vhwif(gqn{$(j}*o*@N0wT$+VKh7sSz|1n*mU7-5X-b#v zOfo3~!2KD7oE`85%E3TfGG@vhDgQ^<{fGbBIu~G_4`_Jm7UELysY)iF6g#*FAhIji z+xkr;yr~g=C#aOsx&pkG+7mdLy=!$Ua3g}+ki@Ul{y3D+e0YHO;~U$SFH%zh{CC>u zTF#Z!G;oFSb-ZivG5vy`&*}Y&CPi?a2LVkV!}4$OMaXo&(2eRyc0oI`dhV>tdu1i* zP6B54JB%k&F_S!SCIkIk@U~fFzmh&L1tnU`BW!olJ{bUQ*#uxWVONfsnUr&erp(pz ze`GYaz$(KxcQ3r}>NYM@?V<_oZX&Syvc8jJjG`qYZP2_$lTT!&g^-AD*H%(y^`OuO z1=WiTA9aC{_AiWq==xKaa*r5GyRn4LmN|4;S<8Ql0OLHL%^=R@j*2eEKnvg?u~rL( z8vRrr|KkG1oqSX7@<)YItkNC)5|!xW8B;chb1F7nE#h6jqG?NL{3X?jL zs&7VX2+c5tn|_WBWbOXECv#%tE=FRGhbUIDw#fR>o7IUwNa1Uh+*8nZLtG}2$3dV& zB*F2G==}CxZ~kd}BQ1NFFDEu@)VNCF&9~7BYRL3Whzj=GOWNXC23-3e$^V^3QBP93 zG%~u3)K~%5}D7rGT&jJ%d<*D2tIeBZBp+}dbXsNo7B4b##UV(X}Yx>oB zbE4GlLkn+0Nc8u{I2=CO%dsStxY8@h7&7@iFuF#sEtFJ;>^667>|rA31wEC@M-6}( z5=mvO#d!h0^)>j?Js&6W^rc=@8BQs{QR%YupZ^oI8SOtElC=L#Hst<_Blj3stJK_h z7bn5I?%&fDlI0B$PWKh(wDZkwzfm8VcsLPjv7xxW_OR4%U-^*3n<|Qx?DM(+0!Lk*y&EjKVD{-F%5@; zql$5$QGxHTm`beHP^^R5FGpd--4~l~_~2AC`s}?a=p{S73eSFWud@VN$fJEf|jlJF+IwAs|(-R{kG3_!gWB~;glS2aoYoSU@Gv& z%FOW*)0P44g}b~-5M348ULg34DnHme@xwJXE~>D!xJS`A`oz~7GcnSf7zQkl2ELUE z0JS;FO7Dj@<}{@^W3Qu~hK4epT*qCGMjjfoV&8*+k8@ku_Jbm~bbCR05LOO?&6GoUg%F+GpcCTw zCI1Tn^Sh2Cs-92klyo@`6 z3_Nsi<@=+*Ympr+{D5~`H)H7M_?ERv04}kH4W*Ooc-kk*@6AbO7760<;`} z-h9p564x)XcwtYMvTg_-a>smPLOl5pelM>5r)(U<-oRbAb*#P7H^6c!We)k^Vq=ww z*yS<%rf-4nExL7$z7zN!)##-%Z%HsbXk zyi&Z7PAR3oHU8P467^Xa3ApNOX~f9BBPbYG3Xrj1X$=`+65|e7FFN4*LS%tYE@v4p zA!%v0E(OC-Q{x=vH`G{Ox1t@n>%mUnUbn$Y#E8Ncgz?a|>rNf-d=)%QX9j2ta2H2@*3kou)_}7lGiIN&)Yc zbZzet3k!w(_eg1s`-QgY7`zuz1Nr2jChoWfp133WGcwQ84MOoBg6FGjF?O7lH1ZgcB_PMz)ZVis^vD(Ja@Ba->1%mDa(p6K&9F>jc|i%98GY z-#r;g!^WbUo}BG_N#gKt9x-N@NZg<8@pX$xyMLOO8)>Y3^gIoJDD;MJu)%2Jp46{e z21m%XlC_&-*@eas{XGR;LfN7P6l0W>15--Vco`MsM0sy1oJ#Jew#R>~MmB8)jzo4| zoS@R63Y=GsoSjIX|Jflq6oQT)vuW1`yc}ZUs3(A(#d$2gl(mX^3$eJ4D`3la6Enzt zV$c6s9fTrzW;@*rbQIwxLEd^h&Pc^>BK|RDDFd>nnRG-$e9$k=sm;KR-35adYqMzF z^EGREK9*k#Mo-jD01G^XidUcwsTdk1w9N#;ithXG|2CCID62+3%XOTpL>8y3MK(M79P+zBntV}*&xdnQBHUn*qar5 zcmAW(P{Raii8XW%qioYH0#@*gBbcMwvZ$dIjcX_EGe;LpK?y$FMfOCyrQsJzLe2;>ve9Z$X;^_9Fj7_{8MfdUhmUh)+y4}Bh6G)VjLfu_ zkT@wFZhs}0SX`iK>J{&HfqkgJ8=1ecu{tJ4ursuI?OE&+WpeNL+!@rDzGHGWmKtz} z>c4-*`Se(k9zY3M+W}Ykdqjq_tca0^l_G2LO&%RIorE1-EHv^9o)Vul)`34X(i3CJ zXOx^?O6)SQBwb1Pqu=j7Tz#j-QaRCk)bRa$L)Y5P4^2|6&7TW_O|- zB0RTU%rL=2<*A(~ctwBhSUML;HugShos&_5=~I{J`9XZ*vNHT+10+QE#bnQ8`5-hw zm`!mI^_CCfqU~*TF(ON2YmQg=Pl?=}Sqpo0Hfbs)T+{2xS99`~zNs_OT1*1dcP4nC zIfbHWS!VGY?WDi(}*dSLhRc^8}W;RgF&g8BJ{)ZO55%sh!*%krgpgVlB70s zCCh^p&@|+_;^_$yPpxR{adcfh@rh~82N}s95z4LI& zw1d&XD0km@6;T$c@8YsEQ}}Ru#ZYn3hjr*xdBX&E^qga5Do04ea$EKP^_PJVVXl}S z6h{EU)VCmoZCzH3ZxdM^ycYY_Qn*0$7O{jThwG<%O-mDs%`5wy4;yK{9AEhmP%Wq3 zn~IDbgTrFp@VFQYwYB->4sN&L1R7dJ3JJANGsa!Dp)~<~lN6 z#}zOP&6z!Vaq-N2BE!QRKNl)!PLntPIV9nEdkrmtElK%^n%hHQFhH&Y{@d>JE^C{T z98?1{vPUd!9Y{$~*w-ApHrNY>Qr7wt;Y7I~a$XSwvcCndq?;HM*zxnYu8KijJt%+b zxxfW5+wnquG)#VO_}R~go^kPB349PmqmxG#lyl*voJEjzZ1Tgc;UCLNjc2Y0j}sy` zm(47>(D2A3d@p@91f6N6%Ut*w(?Cd2eU;UJ!9W?j{{ez0S{Q@Xq6cSSj{%14>iE`3 z9(cj`QQCMPIAbuBqf=@f2WpG%Kk;SJ(y9HX82 zQ>^I*ie3Z+^sB*Q1W_Ek-`^59CvJXs7H}W1wmIzhg8bYZO+QV2=Q$}VwZi`s%F9=!39x?s) z(V1u16!iX&MOvb)>43i`G~BN>&;YKtD{@|$=6{B)f!@ELz=;$jf=nkFZl^bF`h^Pjhc(C-NDdu8dR4kk)k<1&BdsvWrA!`|NOZH+!&uH1> zgXma9bTI?An2!wTwx}39-$H{L-?}@i`UQMayCS(%q(lmmWey*})EOPB8y)%BLtNWq z=o=p3BSPV)CtI|S))}RS+d}1Xc{F{Dy~{bR1zV=vS6RYK-wg`auv0~L3GD`$w@g`h zRL93-E5cVsDf9BCaO&*zm3MaKex4$h_<3Fq??(NTBO=Llfqi5(2udZuu_KZeD`MpF zCS$u!5VPkIN?5&!P2|0T^~7a_@6EN-gfXvtyCoe)GU^Cq#dg3$SxW%|o3UE68&>3D z2t}3F3X8U_D#=r24XchG*RO+lZbPOO7s$m=nmBKOR}tc-pv#Z&rQJlwK2z6DRk4NE z&0n!?Xvpo^xHx)DCvdPW@`z}kvN~GuU-ATL!)0Wa#UEsAViMcFo?kt2jF5jRDm-i7 z0eZhF4WtizST8N(u6(F*0;_OyOf|y^$k9w9)jQyc9)FM@!|OIh0&rUlT8BU5N!G0l$TlQm{(BerDAxbF)vZu}K|mopqU%SxC4YPhBewIWavKng zGk{VB+kB-WnU;{1`Ovnsc78sUs?TNwXOqFB=Glf%;z5#rh(Mt#hEGi?Q0dL^h(J>A z!MgGStdRHDI*fc0`68L z=Uh=%2@rp~t(;`d7cs6KMsu4@;~5T56z{L454S`SA)fQp&nCea(wuu(3#L6KC*P}f zdD8lTn0};*j~{1DOZwv74$m{as8ytmAz)#+RLt@tczteu)->C%J~*cGg*zXl&Fdjb zU(cHDxkNwx>}T}{42+2kAQ@c5;!^ZBd;Y=mnns-s<^or~y)h!}ql6DnP_f&M`I|yK zooQ7Fk*LT6@EM{~J#3uYu~#VC#AYn6AmD1Fg|wU-Xl56Mp`;vDd;eXhRL^FfB;|uolKtp-4%M%G zRp@BSn`Q9*pknqbq5}r^BJGIs3@?4Enq#(~Q>%*Qo0j4Q&J8WjGH6piB!e7ESWUKU z&)n?qb7FHiBR14bq|O%i3ZqubBKpqNCP9C!0Fk!-j&CU09KwGo9T54RTU0)Hmm{~s z_;}*|#XeJU768J}?7=$t$MYR<6>~c2|X+nj?n8*S=kxoO{^0kH@PeJ6fK2}#89m9>7Oh#R(V7Q6_K{Nw)I(|MLPY)Prj zPp9oslceVTHQF~SgR#E6xsJM$HK*lv1X};qRWxt_U#f~O;P)^hHc%cS1TjD&CeMEN z8SwiZvPT)$D^2zKk~#PwvP97*o z<0-kKTTq5Glu>SVkFV-CZA*F<*}nlXO;jCL&P;hv^q@)V;)}V^nxy?Of50t0jl&C- zqPw=u9;16f0xnOe2JI>A!yWLy<~>xG4;@ZG5@h_UTduNHkl|J?qWgVEg4<&KStsAZn~cNRAg>SMt7X4F;(oPq|@ zLlJZ}tc;_hVxVFAq;kNtQ#+zy|Eg2|C7D0f!-Z};KiXrQD!~XrZXBlM%D^Z`TQ^qT zMp^3<^w z41HHs`54)J|MPjcZ1)>ElP4AEU6$u$iX~34*#>mQVu5jqubkoFZ`Vbi^8FFcaa8BI zqSR%oLIW&6u4qm;;djnqDS9USx$dv_s>aKc%?qIu@Air@_z=D`Y~}Nd&@fRv^8`|x z#l~sv@g{>uu-u^R*nf|UR-Dy|bu}2!5SRpr!Hi!MaFvN!*<-b1Z=<&7249cAKE9Qo z2F=M7ztt{5?{n9dBF6^MyF>d`1%Yq*VOHFZy#ud9jo@z77!d5>JRH`&tZb%aF_L@J zhdY}h=@b&Dnv0g&;Ibw0%qAMGAkUj`i_>ACy0_0tfUh!Yr%-6xe>#HpfBc90EPnTW z&s>B;n6PUYtrwa=zlgEBwfVe*naYTLr%9&aF9DFqBdi3j^_ZmxwOU!3T(A?Qmju|l z2i#skhS7Xfw6F+-s-UXoz9a@hyX^CrEGch9&Y}hazS<#Rb#r(Y>L+@U`|o8TtMrC0 zH>U6(X%=qHQn)SJ#$Fq>09o&oWtp^O8+f020`ywTh!`L7t4CI3;i|8KT^k9fB8wh6T{1cPp>UQR@!wQOSMcaK-wgh}fIa@vO1 zxB&jM^nS}i1|bRxF}onu&sn#O?%*Ig^Fy>8XZ@JsAjG4Tba@RqqND}j>#!~JA^iaI z2`Zdny?g;Kk$ZbG0g**iLmO^=v$Ff-(Xp63N*A<=-S_+BgWOPyw}gLQ+nv<==^txB zpFtj7�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%0Aa&Io1@(a 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 literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/logo/logo_256.png b/docs/assets/cgi/logo/logo_256.png new file mode 100755 index 0000000000000000000000000000000000000000..a01735a0d28ad29f2847887819497ca515b7e449 GIT binary patch literal 68818 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

    UVi8leNyWpE`}8y9>N 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

    L6y0K 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 z�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 bdg| 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 ASpWb4 literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/logo/logo_64.png b/docs/assets/cgi/logo/logo_64.png new file mode 100755 index 0000000000000000000000000000000000000000..47e028add9cabb7252469ab8c1b6db7a73a49240 GIT binary patch literal 7359 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 literal 0 HcmV?d00001 From aad0496620561cb9c552b5f33cee52e7da23da3e Mon Sep 17 00:00:00 2001 From: Andrew Savchenko Date: Wed, 28 Apr 2021 13:56:29 +0930 Subject: [PATCH 229/548] Add CGI assets - Closes https://github.com/taskchampion/taskchampion/issues/207 --- docs/assets/cgi/LICENSE.md | 2 ++ .../cgi/icon_rounded/icon_rounded_1024.png | Bin 0 -> 566832 bytes .../cgi/icon_rounded/icon_rounded_128.png | Bin 0 -> 16993 bytes .../cgi/icon_rounded/icon_rounded_16.png | Bin 0 -> 1183 bytes .../cgi/icon_rounded/icon_rounded_256.png | Bin 0 -> 52056 bytes .../cgi/icon_rounded/icon_rounded_32.png | Bin 0 -> 2337 bytes .../cgi/icon_rounded/icon_rounded_512.png | Bin 0 -> 169678 bytes .../cgi/icon_rounded/icon_rounded_64.png | Bin 0 -> 5819 bytes .../cgi/icon_square/icon_square_1024.png | Bin 0 -> 536011 bytes .../assets/cgi/icon_square/icon_square_128.png | Bin 0 -> 14641 bytes docs/assets/cgi/icon_square/icon_square_16.png | Bin 0 -> 1066 bytes .../assets/cgi/icon_square/icon_square_256.png | Bin 0 -> 46171 bytes docs/assets/cgi/icon_square/icon_square_32.png | Bin 0 -> 2011 bytes .../assets/cgi/icon_square/icon_square_512.png | Bin 0 -> 155209 bytes docs/assets/cgi/icon_square/icon_square_64.png | Bin 0 -> 5036 bytes docs/assets/cgi/logo/logo_1024.png | Bin 0 -> 826502 bytes docs/assets/cgi/logo/logo_128.png | Bin 0 -> 21775 bytes docs/assets/cgi/logo/logo_16.png | Bin 0 -> 1373 bytes docs/assets/cgi/logo/logo_256.png | Bin 0 -> 68818 bytes docs/assets/cgi/logo/logo_32.png | Bin 0 -> 2820 bytes docs/assets/cgi/logo/logo_512.png | Bin 0 -> 234149 bytes docs/assets/cgi/logo/logo_64.png | Bin 0 -> 7359 bytes 22 files changed, 2 insertions(+) create mode 100644 docs/assets/cgi/LICENSE.md create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_1024.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_128.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_16.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_256.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_32.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_512.png create mode 100755 docs/assets/cgi/icon_rounded/icon_rounded_64.png create mode 100755 docs/assets/cgi/icon_square/icon_square_1024.png create mode 100755 docs/assets/cgi/icon_square/icon_square_128.png create mode 100755 docs/assets/cgi/icon_square/icon_square_16.png create mode 100755 docs/assets/cgi/icon_square/icon_square_256.png create mode 100755 docs/assets/cgi/icon_square/icon_square_32.png create mode 100755 docs/assets/cgi/icon_square/icon_square_512.png create mode 100755 docs/assets/cgi/icon_square/icon_square_64.png create mode 100755 docs/assets/cgi/logo/logo_1024.png create mode 100755 docs/assets/cgi/logo/logo_128.png create mode 100755 docs/assets/cgi/logo/logo_16.png create mode 100755 docs/assets/cgi/logo/logo_256.png create mode 100755 docs/assets/cgi/logo/logo_32.png create mode 100755 docs/assets/cgi/logo/logo_512.png create mode 100755 docs/assets/cgi/logo/logo_64.png diff --git a/docs/assets/cgi/LICENSE.md b/docs/assets/cgi/LICENSE.md new file mode 100644 index 000000000..1d4dbe059 --- /dev/null +++ b/docs/assets/cgi/LICENSE.md @@ -0,0 +1,2 @@ +Copyright (C) Andrew Savchenko - All Rights Reserved +All files within this folder are proprietary and reserved for the use by TaskChampion project. diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_1024.png b/docs/assets/cgi/icon_rounded/icon_rounded_1024.png new file mode 100755 index 0000000000000000000000000000000000000000..d4a4a9e13a759f242f2fbe470ac198abbc465343 GIT binary patch literal 566832 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�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≷? z{d*6gS>HaKp)+(455NHJQ&96(z2967no!4=TDDaaomxfy6!!P^cXEnuq>K57@Cp-tVJ)g89q+ z(m1*|7tfj3eRf}Nn0~!1@*~lQ2z~QOD*UvrWqT;V1vSm)D|HN6ScB zc?F!BQi8K7r-(X6jFdPC#x2b!dF@KFqQ~eY;&aodtU3xT4lAskvGjcEwGi9L!jXS? zu2AF5Q2Hh1>Pz=mMq}r*DzF-)wq3$nx?8J3()0p$)QHag>6Hgl|^;YO8={OUtLotFkbh7<{`8(3bx~H`HErr<^ zM_zVTjY*JAGSP6&mjS(02SwBC#nL|qY4+P=N8_MHh&*yHMmeXH3gl_LTb3c4-@%u; z2WsZ6W+C%e0C@eB`7{`r6SRLOOUWzK01EpQS`HNvA0#{*&04@0GDdET11E|wF~Hf9-$i`tiJvdMmM^F;Nfl7eX;Ci8@VHa#AZ2p&((f55BPrcOZR~3Go39n-J$Lpz7!7g1)_7e?l%A zxxlaoXKx zr^tjAw(c6jLmShzSYecNuEPLVOP=WJ)>f7y);2+<>&i#M-FHs1psPEZm3WR=HJb{z zWVghp;oClTOWU7{MdHe@e? zam;M|{I<09|J*BC>9)Lx!Gx*pX&W4TkhB4c&&RAZk*sV5WF_ar_s?#qd?ve{vdGvX z%`y(1n#tA6t&Leb2&2L~KV@%(;^3ffyGMGbL-;3l`)ZA5WM_AO+yBSZTgNrozi-@Q zbR#I8qJ*?`4p0;&6$5Fc8v$w92oWU}Bqb$8q@;5LB%}rD*rdA|jKQwwy6@lpeV*sv zz4qU(UFYX>oX2szQ6p)B*nWpyxwK1{wXA$uP33EpkM>mQS=wi={t3L}soBT0%SXiv zU3Z_j(qVrt!>y4B@i#vR+ei;7Wg^tILVgOZ3w=}j$(*|IsV*O;}Io`KbZE>*MF_QCC7KK$%A>>LT>og z{n!-xSn65YX&0u!UsX~iJcUjCq)9j@>yi%|GyF-%C3mWQS5kSCf{3*}0K3697PSuy zXA@LTAB@-yC9b!M`aNg8uJSm-KiIg&1{Y04F+-Sh-xWNyzAkeRob=}P|2_QYypL@8 z#3wIutP?=IfITWa`op0<}ARfm(7hd6DF zvz5Oa?IfX{5c9s)yKy_=?mRV=V)t94*2kdbKct8{Y!aKta!9d5@Xxdfd?%F3h++EF zaj*Z{{io`fA|NhR6e8!=!Cz+CcTlugCclS0V*)G3K%KtxFAZ=;Ig3qz0-xe)0I^MY zwJ~UP3_1tc%!_s`!^59 zN!F}1nY?(VJ6{+?sN-t7K5VTzY;;med?L@#6A_)STKjPEL!tTQ$i-V0tNChd1M1FO zxl>IL2UYdwGQo0P0zMagN=EcQy$#1CmpBKQUpjb+{=GE}e)>0HH}S$`TK>tpmm`+<~)=Dh=KtvsC@@lHE`W{7*Y**T9w<~n<4eQ>nj zvi*pbB`~(TEQ)3c^6_$LV@yKj3Quy>uqiDX57~H^TK{R48;tzg8B(oq`=U{P@_L;V_QC9N3D^ETjx%vuw(rFDkKfE` zJBz@)^;90jceW*H#t5hWdTm=mhx8=7Y*E*CjtLQZCKur>lCb%=eIi*P^Ob)lM4p~n z9Tvxe4<>P;#!ulomoIStaoR&5>pLWbEd-nd2$@BK+3pp23lwm$1Jn4x{%W2Wkj1=C z36V&IRyl0m>tvr&RZjQF^ZV!_fazZQyl=tndA>RjVUe>)IxV*rnOXa;& zWEmG=UBC8(n&d$LPeh&f$$?}N`I}>_zz5dW*ltR>|ElpQr}dRcPWgMrJHKU`V` zUsWv6^ld)Jki>+I%t7>pi1^~OyUE(SOX;5yeH+)tQ=MoYM5MZF7*=XDLHZyevmuKt zYS>b4<54gv@(l}Az7|3J;5hT}fmro{F$Av+tr&5B@}FaG*EJDY7X;cJEcbCySE^>9h->M5NyPUAr zBOa86ntZ2>p`BL z;pFQuh1`mk$XLSqRnEkai+T8GwiZgyXJi+?LYk0_^iKjZSKf<_l)q6xv!nEblsN7| z_t+Et*IEk4VZ?t=8aiWZlsaWkK9zCmGGKzu*^X=nn|o;YeUER;)_iK%`Dsa7G&e-O zNeAgefAi&wVC*mTj01|65&5XNwP7M{Ce26x5O!e)A9y+q%<#hhhudIbcAfHt0(@L+ zQ2^*h3Cy%bKI+g@3dkOI7}JylhSew63PRoYucC72?DT@VpDyS_r+3A(zkQIk66Q<@Cz7$$1z4ri;#kv)kg4&1~eo5h#k7*+O$yblmok zxB_K@qCYIi>7%oeEO)u{JCDXR$@XL7YLMlUjOr3S`DLN0>c?)>R@z*DD66%`1D@jF zx=MB%KhQcq+|B+^omZ3eF~P$hk}&WCE{B&Ds3JT8zR?BiWT^nk@Mg0qE)F#-?90&} zn3ExEIfmy{xC@UE*r{;+go5>Ht9)pr)EBHzQI%XNgoV+tAn+3vVaH-cD- zUpswK5~?_4?!EH$|4Hlv;=U4c>83d`HDMN21~-FS z+--;x*q{uj(cjVxH@0bcdSCXuVgJ_nY)(Zh?8fv*fwEo=aRp%ee(nF+wdM_G#;^`Y zp`oF&R}gX-xY9lv*JXY&fd(qBUEot(OplruzF~Pd+Mcw%;ILvR*zY2VgiOaTCf()= z_Rj4M-wkfFP34FEWpb%sk@B1-3T>uSOM4P|;LH^CjEOaRhPCd;fGmn5Yqk=QmrP$b>ToWNWQ0h1OrK>tX7ho2ZXYF%#NE%Ok$1iZ&=y`aTmBhMeO~^ zsFpAf8U5)p$x&q~tH;i+(Rw>UP8S>`lR3@idgry2c@NuPlz@HHin+Z}IiZVd2igGz zMIxZE^M!C)2D}G?l&t}!UP~+Z@7cVztu9HfW0`gOA1S5=U6j@;V}8E0yJLK3%Ga!6 zli$-FE`GUD5(vUM^{G`h-6k{rRZXLR)K;s4oOD%eyzHo64}RxXfZuZQR4n#|1~@(5 zvHCT>z1HJJuu{f#rBRaCEzg4w7OFbQ9tRw$0rS!W%3|KeGPsK=8d)yPGizm zq(o*MVFKFwZUoV|16VuJGo|~2y!+_cm%~5OZNyBQCl=}$ee`I&dkKMG|2|O*yU0!v zBYv#*a`DB{@M&>Pk1l=7Cr#>%6%B{);jf7>W|bf7s+u1gKbnbTVXw#=5&^fI*WJhw zbvx*t=Lro872xLZu`DnNs#9-y&KcBBK3I|^g4xLR91r|tIAI(#1Mhou`c&8hI=q$Z zkFOgJXd*5)XizEf9SRTlb9pv}S$#<>d4uB*l0!!+pD1JbV|Ilt3W67|6&_72W9Zb+ zCfn6sW^M;S&@y)9Y~{2u!*>o8vX})~RbhQd=bYV=g-F{g}h@ z-DA4%|0`eOmQiv$N0MJiuh%Ez!vKqrqnj7M>j}{)INEE6_i3hyZ6`}V^#bLQUe{gm5-@+@Y4z^c z0PMsLa&Xg&)%{cA`K~)9$?9~Lh9*anI^=SujHm`pe@DP3+Y{|y#B9?yYgmxlsYw}M zVK{TT5$pt4Z1)O_B(=ZtQo+uJ*Mek(x=Tc&ja1{>jjXUX4XTUNU&WzzU~*V!9X)}Z zTr>rChyVh|`h46luGfiKtm(GiYMF2qig!)EzxbVDa#fzl%R*}ELwC>e8avV6$(!MR zaZeizkbkwhEg*WNBnxFnDHCNxtjgXnuTT&qxkAi(ZPgw-d`E%pOj3OP(t6v4K8OY) zE~axeimIs;GWu%Mn;}puLBO8V;{IsHQSi0_WA16H9&u0jHrqb^jxItim}0?i;C@zr z@2qgfhB-6M_U|G8-A1lJ)&IOeAw@yI32XS`5W<&#F&uP{!-nCw>IUzYr7+;z9?(t< zxC!~UsU$Q963L(hNV&H?qyhL8k7E$**hIDo>Ms+xDs12O!HI&k;CVH+?|VZXOfl$| zceB|-Wcw95_QJBAK|xE38oW1?dW|Ds&zHNtR;5?_!or3j7}e-j?~2m?Tn{}{u(ja! zNOwG8g=>YLZ@(vUQtKb^zHP9oD^Dq@qWwkIc+ja%^|`|_^|xkiziTCyJsjvl6#Q}U z>@W6<3U^i9*%yPq#XRS1{}4Ie{vVM;F!h{ZX@3sFsFd?#?XdfABk!~@+=b&~+XXVP zs(*!D*>B`US}UM4h9k-LOPcI&T6v^blWI({soS*0$_6~d`i>|})m){if5;J2vtqq2 zxUJU70^6KjjdEPW>Wdad^(eIO++kagt+05aJhCAQTJ;JT)B$*N<2 zg~kNrRh?I3GPmq0-%C?hv+#{tzO;xwqFxD7ioaj^EzOX?`~Ql9;**4&!?T15NS#6k z1Rb|3Bi|5Fqm#jobLhPzl=QM6IA((S#*Q6i2 z3CHDF@v0d?6^5T|t)rf7ykOTVfv6qHsX}<9wOeUJP7&w2iUx*EkD5P0N%UD?CHyc5 z4`)||Unl7eS-ag}w)B<)Lv9Jr!)v0{G8uycWc4FcrL5myI$q*A=WN5NM^!^2&?P}a&|$?MR5x*$O6y!`$Fczy)oYWnz#;q}{Y03(fP zlb|yz1xf){xDi=9-|3LDRzJ=8q0bM)NfNw)D|n%Tw59)+TWhcqC9r}m#_1h~Fw+T* zTPNuCI_^>$LD=y7!f?;`KcCA4Jg%3g|Q^{$EOr7at+<-cq0 zzCr0(eN{kPfz{AE7UBu>Hn-tv6%DL1XGlb9^TH9KFyhP49@r~7-@+&D^u0H~{Lg{q z0M6>8M@&qpQ*kDc@sI@&63sN;KzcL^4$-`b3|&#W=AJDvV zA~U2-LYLj}eCfzUQiFy4h8wb8>}pwFuejO9QlXn`tjBFhRXdv+YL@l!Q#q5@&z^mI zOFbT(KU1e!}t%!aJ`RXrdDiUMAP zY2y8qmUmmAp;GY*^n!N4E!^GRyujsM#)Ks&moIOh2Bh}jF4@POX`i{kN9NUTi?kD( z@wKxIuFQIb_B|AtZH#IyQ)RxaeZ^2l;r4+OcJuoP;SQ!pYEZepP5u@_^q-1Tbi?RKdiU$#ovq#6VL{xABEegG-b`FU86`tPKP#I2JpH^I`Vv0m}7i zbqLwVthdgH*ohrs^!7^q!H1L{$Xa3>ey17N>SRaTASWqVdJ~*sL`}IFK^;Vgdmp2*$PpnS=emI+Uvnm(%G6l zW4TiVd@TY?8fUAHiX@Q!{p~>BpB;hW4E!$*=NkaHN{4^9viP_ME-OSh&pa$o(F^NW z&yKwRy>st=`tjp9`I?s;_qX)Lqy5>WjYz-RJ#>V!wbtO!O_6y+|6-|URlvRIQ6)B`#-41# z>EN)(H|veCCOjn`U$=VjuNLK#vu**VI|)x!0NM*;dqNJg7AavT70y*J?*4k;GGp~A zUHOB{>|mC9j-|ySU=Rw{(d%WJ8of?T@TU;~uny1OzLU;Q2lm1iJWHKcTHC+BtNUTP zLKMk#rzCjHzX2ulgiKpHCxz|dlAU&?Rd|BNR{5W9Ir|}lCc|))Y`yZ8ge>UqPbiP; zxAg)hNeaJZO1}JgQ?K%Oc4f%>xJ(K}_fBx#dt3)>2Ldno%FReT)8=Uuwe9A zQjsOub}Y|Z6>SBBK(*#Qk@yt})g4DMeY;VAlD;L$&Yt(q4|db4Dgw4GB$v}Eh1Og4 zpH6vaJKWFsxdqX-Bo4T|i4-4B<)9BtLCJi##1V^B@qe7LacXewoDWiC=&Y^fD>gXY ze9K=|cKkv8^=|zrGcAmK?ZM?BZ3?b|B&>I8@IM`}ZPacCP`!eB? z%WVAH7d(`rU%8%zrHHfcSM5u2s%3|1tiGbrBuC*(5*zRz^PPYDn>8@5j!$Jj*C>a` zB00nIJ;wmr6H{SGyx&!M3nZ<^HFd6smt+;ch{zRP>B!H*uPKa(hqBMB;t!JiVkfV-t zIpAHfWM4%dLQU2H-~O7P&QzgZ$+b<%tLO;C0f-3JPuIQqZQR-*QWv4SU%sLGI zkT6K-+w76X+{||GfaAsQ*^unt$}Ac8PKHoFxR*i*XWF`O%(7Y&ToT`GG^o*ZLo(^4j}&hkh=PiQ?Soj`2$)pixjZ(R7=k^v-q4n>{7 zdnH=8Ll?NEl0LBNzT$OX5?zimWFwoL`GP(gBDK&}ZLorLic=(-;}p3Mfxaq`NkCT7Lb+b^f=-K17^!_vK@v7Mb~Y z3^}^d2&qdTdleP&`6)bfdY>r)&dD>)?Si)aClDe4inpd=0JEy`jn{M(s1`K}attQeS;H~^Wt)tbT#$g5)#4QU*_aZ&W zM+U+7?Bbh4YV<{j3clO>+QpYAIa^>3!a>0WUyS}>dX~Kq^-3A)gF)89zyh-;N8DK! zg}4=_Q~O1tr8jB7^pf+NQ4=NhHeLk=XkUqngELba?VPjC=+$)MZJdmV{nx;H&O{K} z>6u$d8C#ll+bY5QTQmRF4b#(r=_(q>mu`&c`xU4+w_dl{Ue$KwsUEpu|2=G|boz8! z)OwIgy}$|3`Q+ky-E9`uK0kTQZ};;Tb)~aZ;-Na}di6$MEQ8w7R0ZVg#7*~eOS-A4 zy&Nbot{$Pd&jasLkG$lE{X4d0ooCI+_#v5otaxMg?q|y3ZM>W-=%rWL4!3K62E3C> zB03_$lCJlecb1sxch*OYAeu6Sg`C|M(h-MFf>_TT3pM8$ktjAxqvlQ}jn~PdqIQ-2 zdtoASIz(eB&F=``jZ}Djvy{gt)5Crk+#lk&Ay=(Zt>I?Z*mDQ35dNwCh9fT;Bx~+< zy2kK$kYs&4)+OP~)ru~$8ed^?UvuA$3IJ^p|L$2&KimU0N+Fl7haMS#J5eRe+1~07 z$JwbR(7JjmLF+&M6>u1=X-Xz0m}HYmV7L#EHL7zv+?b05u|ET>KQ@qMYlc2d{yP0F zBP4T5_ss&i_&l>^pAwzE@Ywv^cNRC7XDfEEPI25MhjaY$vicc}IW1lttwb+dF{AM6 zDemVcpE~zh2G>1U&7I%#_OhJX0Q`UDyJB=w3cHDajK z9N0Bw5SHD~NyCAieNJIO=x&K+_rF9BVZ9@db|3>{<&6e=m=Q*Sa2FxVHGZM-e679Y zNd3({ap<*7egAJ9tLlWjX%7#%1Y_06)0T@!*@zTXE;m7lXR|t#Do>!O&hE~x@WG#; z?RxvRv$=9fW@*vE;RMwWEQ-Wu50_+PAMH3=KbM7g|LU@^bo#)wDT#Q83}Qqt>r3s7 zyZmN#(_u5I*W|t>ukHWXs3VPQ=}2QS;`oObQAsTCJE>P!YLm96=7~Szi38;czc;_L z{deUK(hn*Ef+mCEmNZsya|){%tI0rL_wCbcLaP7KqMqyMMS14x38oDomG~9oPt778 zEP;JaVkaU*PIvLVwi;Ruk*t)^s8&rYcX+2-F&V&GK>RXoqUjS`IQ}5*rdz9mM>`E? zbfY90<%hqL8k#OV9<^(xKve>a!JPT7nei1rRmaOd8%^9xL-k#btbYFlyld!Lo&_Xg zD{NBvZv}C$V>qSXmywiWG>cXo-^lknps`>^{s4SR5H@yS_3&`Lp*d1J5%S>WJ3L)I zJU9~G`8@!1M&T^InJU4APB7pyi3P(RVu5=?>heg#pOmHCMa;9PeSDm^u|dbM`6FfF zy<5Dd4==s#+;hl^-Kv_xb5!->lAKyQTVuogDzj3PQtGA?re=jh9((uWvtE73qQ2ry z)X>+Tr&CCyw$65S7%M+O9g$mSWh}J*IY&6ZAgVzA;`1A}kF$~=mea0v9=jdNi;;9R z?mc_%jyyGbnX@xdRX?f5IRujtgfl3INB=j_Lx0=NzzdejtI$j9v_OZhVI^M$bv(&PCmwTp-pd>;Xl^8%lv_pZgrsWQvru|BedPmmb zN0#u!80upbj9pthAM`+v+LLMi#f6zv;HsPV+n;EOQ+!=nmdl9Ig#3Qjvqa03CS#r^ zXBj4B>=fGUb+`VrRa+^gSY-N{3ZZwY)OuJ1&=i}hmG+c1U&H-*(Ew#)Co+v8HiU2v zhfRihQZtemVrfB0^^d<}2B5{*dKQ^`i3HWcbb*{QqQ%#tAftX(5U)owMVq#m%qv<6 zr9ja8OGms28hSYK#IlksdK_<{0>M3dyBsMEDQ~^tHRKiTWyS=A(%8thH+5Fr)?qPe zV-v8|RqBeq-H6UkujQ!CxHYMkOY>dPU?llSC+Sbgi?slhAkW9KS8FeJeKxM5*?Ng@_bf>q9KlWZ$v=f;)6=rSt6z{N)=e&i(qBLrSci&5LxHYJxb|9r&~kP; z_`Wg)qt77A8xb;OfobzkCRx-_>$=#q<>&sJeB{*1%uK6Maz8?ywWp|xAZ z*^1()AH#mxrLN(~=4}|imhD?FN)Omy(AS%eqA)FnQy7 zP6K7J^9?5vhP?DDt+pcHGj10|{l2T}lT5BwNH3uG=Smx0^wb1|YF|=2;;Wn4v-OO4 z;xtRd8H>iLQDb{Y7~b{45%q^Z6`$=4dLgmU`2R+TYOd-S(CX5b69*&^K-yDJ8&JpU zSG0tN9dY)tnz%Ip-1s7b@j-gC?2XFPm8(3`IAX* zJA^9N=$jToD(WIuqm`1*%s-cV1Ic1GEEsGNJ7W-_nH+R~?ARe3S}iU#(UURq=GxDL zp*r55uMR`s^-#Bbk!sJC`mg~1TP4}{_Knbh+{eUxX}4OaFFB@)D<3Zy`ls&HsQSGj z?S@E_sQD2Z3VBbp5ZL(e_BMyg*$~tC1-vurJ`aIH?I6zpD%o!IAH{?C{S)QnAkYHu z_#3Y~125M&QYD&#Ld;Ni0JhEE{vMp|{=Lw#JO_CmtT@+DV5dUq| z+WkaQvdfh`9bU8ue^CH4;?ZDnJ*Dkt9oTy8(SF_Z_uBjCmQp+Lb|MZiK?j$;ME6A* zsGbaOriz4LXFm4mv|e%pny^6f9>94hu><7e??z5P{+f8;_bV+LK1owR%$xbdA?@+G zV!gExY(;hp(f;+p;%|KCWdRpNedFnsMLL?4`JP|?oF$h#FDjXPQ}k8t1uPU1qTC(Y z+qX>&j3LXqDwuMj;VjI(f>fK2CnJu~#))E@(c+jdg@eO5n*u9%`VXF7$wI2d7?Kma z+tGLa-pLE8`ZMw9*V02RTu&lw^l#6X%jti`LX%_e(d>U&9?zxD_tbzQ+$7OO=`FQW zznnLV(PcF|6IK{tV6Yr%(H7@@d{X6s6P&7ZC4!)LhjnK~&g7E-k~7*r%3AjAi%?_s zI>^3lov+JEhsr4Y)?=pU&hBIp>B<9ES+o3E$Xf%kkE~EdN*vJ5D-nxQM6vz#hiUO; zArq?uh9&!}ynYB8X=!x^j26 zpzGar-Oqm;T~m0%#5*_{%PH?nCofH)*~k1%eNmxuN%X@BH)MJ-Ig!NDoGDcW^}>{} za4nSW7N`LfauqeG$W3R3_gqt_%uRoA^P%upPc3#w=mD|1KJ78o-ibABjm(H3dN6&Y zkzNPKYWmK6RS(_>6|0&T9q8R9T40E*?+U4E6}wYiz|s@K3KfBND*RVXavHHYDnE8% zIe*GSLI90kmTIT}MldQe0Or@N?w8L)O6VVZhIt3yN0W@8UkaW|A4bClOL@e7&d<$MhU%zZ@1N=eVCP<^K-#5-}5!LhVxl{~-*T->5K&gmFKWj~QQHb5|H9Sgfq3_+3(vQ9V z&)1akb@0dEuyE!4p7#jMg&wBN)UQ+#R?wa82o>_yPz3WA4&3rP$T09BLcd%Fu4kI& zJ&B2E%mHG>$FLIZsQlCLj_03H-U`@~FMwLL2%`J=jyaPGYvql>N#=C)k#6Oqx`w*o zh)k%eAMWp!JL-Ws*=#xXeD}IzZq)4GW@M(jTx$uSvHy7z5hWY|Ddx^{LFOJ6OfJ!=H6<5vh<|8w1uH<=s`_Cvsr%OSN3ow zI|E{s{~|DZjOJQ+xJMY_$21am$wPU5m0Yua7~QcKdg3i~T|vDA^yIkp*Iwsn6GDgQ zlY2_<G}skTBL z`MT?;@z-2TJVSlKdbg_DBS`8YemahBb_=cy?m{gVq?GiZdB)n}st*FE`;tb}~hps^gyn^-m(x3Los=pu!1H0m9>R0+S(TWz9o_^8(!L?QqQX5pl>} z3^4tFp^MCEWHV*&|3N*xSP-IhGt!rqHR~aHI%>+9i%{+wl{cY#8KbgZgKG0U>Qck^ zeYjBy^@5mbLa;p>=2XuRapSJ`S!(V6!{JMA53N1+z;6oHnARpUgu2_DQGC!TlRlRj zq-f@#W|vKGP*l?`t?f@`E4Kt0?&d0s`Y?1oizsyK>=g+}16Yjs$a3{u+XkpX*SE0c zt|i=F-WxU!{P|IU$u$iUuBRW!^|R9K`qY0ug9W-)u$C(b8qN=<6pG43Oey-~VZp2N z2b)=w&VS&-L(!4gZ6W$?k4!jSslFa@`WzqyJf8#V8#(gXWW&I&&YXni=u|V zk+R0YsJQja!vy(ZR>D#} zVJ5<0Mt*A1z|{-&mOJ2T&c(h2G?5nqUM^n$|1uYy|79-nPcu4L?je;YioCTGA+}+= z^l-{3QPZaSB=J<3NapNIs%P-TVu_%;{-f=o-3cl26BGxHMU#BF7oo`ED(e^=o9|r~ z{mSd1jzbn~K6@Chl|FKm&Kikv{{m{|sO8?H^kQ9EO&-_a@103bGr(g3Oa?3!2idOH-3v)PAaPnm@g5W|$j_%>F~A;-B+sq=k@afUNiKXbt2q zh_6UxK{s0?tE6)-N{A}CMPn#6X%|?k3kn0$rh?J zvrvt1%^~YCelWiy=7%niZ}4?nGXp5*aoKSsyvE_rIdAPsXzf2WqOf3}Yh%ER>{3Od zV-y?_22lKy$;csy#s}_(0*~;PvL)z5kEOXe@`q0ilbQF2uLB-~lNk|ZM7(3ZCdQYc zme#ajwW?nMzsYm6rV)*{OGS|nA$zURrHoQ5-o00Qc3vh=-oCZwK(X;G#@)pXl&W9H za*<`k&1&@-xXF#-hGxfaCs#98$Szg**OQcY_q-qEFA=XrUgx@qKpNcrO9k_GifiJ{ zbH`6!9>hTN!zV2i-l~I6IxOw8F`$}^kaN+Suw>%^vv@<%90t%`xzLHidk?~y$wa}y zO-Qsin{#)8?VC*&#@@_3kTvnv(7~NPA&b(~mClxrv@k*IAp{Uu-(Fe$75Kg4byU#cyO-Q# zjei?y2c(LQQhSI?h0U3jbT_G;1bER%i9*-oEBovUuf2Qi)>6}8FS!5}#NEyCkq4g` ze;NA){*77S2$T;yv7=mv;u#TJr;AVBcueG93R z+tpL#C*mjK`(mw>HrLPV-~uF$vzIR<UVop*fS;A41KgsCfJX7jFdsc*;)x0zTe z@>@xn&cpLsh@6qMtL{&-T@}9~o`1HqBHL1i5?NbHDt|1PJMs$Hevd*ar4CEH**jWH zI~pOEeF3ahk=t|ihaA>(BrVr9!)YdU@K3PLG9O3O(AZ%eA5; z0c{FC63J_nWT(DQEL)B)y!J52j#RJ_e;4O=G4!19TK1BqosDY9(9^SQi-?1YaIcN> zsi{cj-s{_!6(;jdQ8RV$x|Hsf7oX{^y+f4`rXbP~_i3@5BZW~MgMlE?pZ==2TTX<* zn)!a!Z|Eg;INcweZ+C6@Ut`RZ&u1Ds_+O2u+c1ZxzTDx(>eaeOI69TUT^rMDFTFm0 zrY^oYl+n#n#mabN|M_%=M~IU1K=I}yROtrNiBR~4jKK`P%lxw)mQK(iJt=fO+YnTH z_qM-te$NtA4|mixAekHBJqlU+mQbDd!P5*J?KeU3AG0)k<`y+6z$!)T3CrnvTkQ}@ z9`PaB`bNits7%&KZzkl6A zW4|?g$1gLIfc#!wDoOU)Ly8fU8(+>41Jy1Tqifd@OUo^pK=mGM_!a!379O!xZ&@;X znT|uc;RVTEAMZD{yyscxA}`jumGgtr4eyWvTxU4)m~0y&_CyAcEmPm}r!@&)@*n&; zUzg<^(5bFXTJ8MCluLawQHOl*`!)F^_-~oj8!CpTN11!g(r#Lu%O*TDugEcG?1F)R znB$ba_QFa8`y(Rd`52u=24C>;B#20yS zuQzbXbCjZ)LEe%s1F~K`;R`b{T;iY#{eJyu}~t3Vt!o+oQ`gf2e8>xMiPO)ToaWR23D^< zGkHEt3=pZ)U7Q|rNMH=IFPTmo)gr|v!3)|v7};!-OQAZXJ*#F=rx}nhLRn8-DOY5P zbNMDIF~SO=TXe8`#xX_S#EbBBaps1Nc+l+E1m}2bl#L{9LZ8dWFNTzDWxG<_(-*ghn zK498EamDes^C2>i?43{gug1l59gnB#=AI^f)C-olZTPS_cDv zzD6IL3tN~SBUHQ&`i<9mF=h^%iwtCQks=-WoWQcz5 zz7ZLC2Y)N5z)5ty5|$kUX;D_c+)-<;XO6v*nA8||tzm!)V?hGdVFee!33C4iKvdoK zdCbX-kI;V&M5vHQ#}$7s{8=2PqEK*C>L#o!j&67Tw>KW^Hh6(_Rn0XuUozFB)uJ+d zLw28mp^i{fH}$N8WLqrjEi~7ghn5Y@_%8BX_Y_#Yr5F}C_Ib@}&`-xfGx7^oJ$%BK zh3x5|ms$AcBx%N01R)*!W=&9yfYqU4M&V2t$yE{rU*)m|3!-$wn1Hb)494D-g zS-`k+pQqyucF4~!dLQ}!Ha@nPN=SDy1W#Y#h{-#AH%6h zgdS^{lTK0Sz8_IqAjaK9Ii+^<%6WR|99KTg9tp8|Smw@8ZJat!@twk(SYh-lM`w3W z!OKN1vbk`Gl%A#B%u&Z&8A)E4>HV}7$m#EH-M`Hg6sK9;9ix3a<{e^y|BharPhE)zEcpbSf3mcF~ zazGjh{Lrw4Z34PS=(n1?8-c&`Pd~CkNo}(#bJ$pSGA4sw=Evf0=#t(E>~>R|G*%23 zM`?Au{o`xN7$4jERZP%~yya``p6_S?elSFpn(cA+_bvxon2nj6O}opR7pZGEQQ17Y z4-*AnCr8_^c5!y`Xm>i6jxE|jk_|u!5$Cm7`&BClSl90D4t2QyqZM1 z^jAvO62@)7`_M^Yqrj5tCy)9D?s8rf>ZyonWATJ7H1}nd)-5~xG&1lSU8Nczf$rU9 ztxSv4iV5|1GMLR7#N%+|cOlm+EEL8qLel@vV`GN78NRo(kE9@7~h!ZC0bBT-q3DH?~BhqPK&(q4z zzq+QFrH`0xiOiUv^DZ7FJ#bkwaL(2L;9!xsf3qIC(X}T=+5Lp&E)n^W#)N$JGoe@C zMPNl=NW*`IhvuUpib(A>kU!OL(Reato~x;E@W;VUij4~;U?F%ZPJ?i-_c z65a+i5&ury0@F1ZP&|ocK~%lWJuvO02A=opTL=6PJ)Y0PajpGvmK7$u-?g&z zB{qXFc;}8VQ3ij94#Z6gAT655wp+hHdpI7g&E&hw~%hF$8)(-f@(eG9on={R>HS9gx)eae%&d<%{tPf4+y(#tXDc+-l-;2l)GgSKiD26|skh*z_jx@}4 z^*@{8KkK>=kVAf-0$@)!!~d8hRSnOpfm%9=CLS4~$6ToWRx(k z{+nih4S)KQPm&l&r6f+_1#eS4ZvPiJxj$l=Ahij@Yaq{8bO(RO94{bOOV}+~SLHl&;>paIV3^zq<`))gVMDog+18vbgjhv`W;xFO zi3r}2tvMkIV;3vuPUi5Ng-&2E=~Ky=Mfw^whf{;iQyNr)JN5oKajAW9Li&M&-4&_Z zAPIrxwkPl1rJxqh!h=X(YeYKIB-EiYu>L(HlS-~^?J>;23?P&_vl)Vi2p7<4t7s+S zAY|7$3yLhk#T+{MM3NF zOg-*}Hbh0c2#BDTSDBvxq@*Mh6+MGoo{CeVx3aaf2uh8?&A6Vf_^n-Qag(Cm@ z-o^_G&!=H1VQUm4iKxl(7cCEih8KH#8y5zd!0gM3^aMB_JWmWA=*OK&Tnpyv13Nk7 zr}u*c=;}}M&mU7~;ABnyz#I0!ZCkExXM)^)Cm8!HGYD}B^sLVJjymjTOJ8hGoDOkv zoo{Ku8zo|wRr7(*^Roq9>(tQHoi-e%#m!8sJyt7$62VvDayr-|BmIVEQyQ7p^W)P} zUB@8wWt}WO!@81r!%O&rU^_ngna8h5TU4%(>?J`6{M*W4cL0?R>}+Bqb$i6QUP zAn)f49?l0Ie&Gpagk6pdLx?Us6kLVRa>xluIw63>zp|-Do|V*&@%yux<6mTM7ly3& zbmeq9lZm^I=BBgp7kynvs>*^b8|)X6cr(L9(ze)*B)w@_8CC^w8IQR>Aa9XjN-tC-W_QJgPHIcji_ctY#IFmt7w=UZm zL>!{pQbzusAT`YVpL0%tCQfh1&mV0pMj2BrVZC zB>aD;p;ipZ&aw}rk`sGk0bRy}_(UQ#{Bqc`8V&C|N0qOgf+7}v`9ncXQO`+*m=i$W zY-?$oC$ZJp(jt9%7VJG&(`}Opuc1f$yDCc!;W_b4=9*xt6$L4I${y&KNRBM6pQtM| z))FOQug5?4jao8~j3;aEI=XP*)4&LS#lArLnZ|$H44!2r1!svtOWVQ3S$*udUs(Y1~|sa2ast8W$=I>DvNLkuqrc%zXwOlg_%gT_+qh9J6+>WPOd!cugy_te;E5C-i zgTlbSd+IRjzClrW%z#VA!UT^3qEo{m(67h0=Eg)Cy_9*)p!#+@5~uh+gxe|p2}B`_ zm5Jiv1__g5N-MZKYWUilo;H+Y+AD2#d07a24=aeTDvPx#zXx3tb+lVkcj(I}lEEM7 zD{;qpOU(@ocP~`HBR1zCL__k6f>yxH*FAna;0M<64+ET`c%c7w;KAj5amx_(LdEtI z9Y#{!P8oT&CaU(O?Zvf>{ZHUClH~k`Ty0~aRPd7XWTKlTL{fi9d1BP7K_JAm>*d~pXO$YmiaIalVfz5IQkt%vM1o^~J>_0&9dCZU$ zW~SN7^tMoXn;ZcmQfn?FW4#9o9kmqHw}KAS;+tKzf;LRB-P#7Nnsdh2%hhSeqv6R2 z-#m{MF=n4fjG@d*s2oB=;(RAy)QO78ta0_)MLq7Z#y?+X&ASqAhQr(dFhRN==lF;c zN8)x(TsQ@Bp#0xQrwQJU0<|N(Eh8Ng#fY3J-1jGqOR$i4pXF|D0;X3T#X< zec(_VH_KTmbw;Zw)rhFYtMxH`g{mS z%J1AUt%VYiG4gxx*7E%sM`Q`*=+Tqz5^>NKtA>|93|9CJB&n&X*N_~Ai=k`EsEsN0 z-n|!`ep%^u2sLOhKQ*o0=Ed@FFF{AVfs<{Iwbc3<#e*{9v)<{{U)7OZ>XOyf)lQBC zJ(;3Q{G3|TK;;jUfXn+Ne#_^0sjOA`4x zL7}5>@0<-iSVqa{Y=UF_jZuGZ^WCgYzk#1rEKeE<*iFx9{ef`sF=zcH=RerLQk-RE z$uSc$Z>f_hWH{S$ZH9Lkm3;Q81;13n#jbwA-L$rV>iVdsa@oV$t8+wv`94Pq?4J>*aNoNtEWZ-V8kwRf$pTn%&F3+5C6kv}0XAwjf#jzx`K zs`cvvu{wa0Ua$6JwCKsCd~p6xl=8~O*A8PcbBAv%L+IRDd3Ij>Z7Id4PO)Qex4-Kx zok@EYkB)?~=|tH&MBi9!%gd5S0!xLA$#Jbo6?}xq7qSmT z9zo`ZX*kwz=TR^GVg#+=^rqpz!WbRDoH1xm1Rtx~q*3P?lA*jTj@!L`{}@$VI)OT? z)^JpDH6NhR$1Ur-4;a`sKA8^frDDd*I$oZDkh5jeK>6FLFW_sDTESXPG_jVCcSF&g z3MZ=TPGMt*@%@E?~czE0YO z`0m_~S+?iHDw0&Ab2~t}3q6rM@seosuJfZ^Nqi*l%Wrr0Qmj(Gew3WgE1j={aP)gV zZSz#2q-*Pb6rB7$HvVt-=GWnsBY_>ygHr}(r?tj)1ri;^eRd*Oxf8B|(jiqHCT-$2aVwv(r-%f~u<WtU#6~#iG!8D|GYWd*rIfhsewJ5Z@vOqO8`}}odabL z1BL$}G;kr)SnSfn^Bc}6Cofs|*cnq8D0!gVFE;QpmZOk|N|J8g zSDcCnq!mazb9HYap;E4uTBjeSf6~&K75So`%wWX1Vb{~O-qJ=C)!EHMIOtRSg*$P! z8qjFeZ-;<5%`%D;nf$_IrlH?I8wBlpjaQPd}?Nk1U zPQ^G(e+;tvMEK%A9T3c#1a?n88-M`Ux=w%!G<;d9j-;&@|3 z{A|NNPpOI6^EW3AJCU8MD0IbGGFeXr$r~1)RwlXMRLvWzM`W3qkzt5;b`b->kSBzpneV z{RXIoISfRLzq~Pen|j`H@EY4ClS|&IeW5Wrjh<%F zf>A*~bh9`qNO^wl Bn%m2L)PUv&L9Wc8=wbTro#(k~UsL9LLJX8Q^$K=;sL>t^S z2jg#qMCBPu)7Y`+DqLz(x4n|Fb}zP=DUv|q8LvtgXKW^I=l^v) zV|wj>ZQnkdFC^7of9BqZL)3*?r71Gs?iqpaua_P(P%6NbbZs3te-$2} zw7J@^FlHS7o&~w_q53OlW$gY-0ZAG%+727V&_pXo&K-Mq6y;eWBk6O*X%&1ZxxAd` zsXu36q0H(Rzk6Uwm;(yjh-xjz9X^4L)eWmAtPq|W8J4>XUuN4P{Tt;UkUS81e+q%; z&MnN8Cmn)bE~uC4kK>Xhe$-enb5w5-GX|g1eHX|VO+By7x9Eiof*K2c(hy~XLDf1s z>E`u;a9&v&e!Cmh&0eE3MP^B79N@&hp4ohx^ndC0&Ne$U2O4SYe?D%=gKY%!8l?~F zj4hcwXK>js-BhvckMBW|$pAEU+>Yg?_3$O#-`z&`N&S@5M?aCkO7SI;&wclRQH>C5 zi7ct+U5UXH3DdHGG!33o37Xzo=D{!#$u`HAZ4PqaeWSu6T8X^s`@*?Csyf8Ww#fk2ssm7o#6?3D>pM7eAS`BM>{9?5{m6+0E`?X?@ z4PRjkP;_X`!@|_@yu{Wi6>whI_ z_}SWc5AmAMe$+zddUk59+X6$&VZhjXjBMGP_8%`7yc~wc0LVZ?y#qPmU;-a40j!9# zULp$Mke@KV+eY%^VPg|P(mvq=^Vx@#rFjERu(BaPf7^`q?)6UN(ETWJDH=b2=V_Tu z8tvkV5)Rw8n`2LXYVu;V$|bEbKIfd6UQPSxC$*RJM~p=*;?=snEsuohx(TlwMSn-q zYiQl=k3w!2=7k+3>^wQ)KIzp7lt5juQ5S8Zc2zv0(SQIsAN(jWMdYyzK_XQP53AZB z6{Ek3Qvg!D%(G?K=e0|!$0VXps0J7Ph@Omf!<;!};~eHtB17M!C^@0r{#*V|`Mzpm z>=s|c>wzP2_cTn=>zz8S4>I>tuQLusEK5@)F>?D|G1jCbR8h)`f3^clwsa{@K_g}e zmxWn+O$m{;Hr}KJlHt~iGr28M0P(7HKy0!jr&6Rr+U2NIbI@GZ(%}=|z z&m2zVM?jC@)(Ng>Lv^f}s(tD4LvZd3lftVjoySv$!s>9q;sX|khNH-FL60ULU%s5# ze?H^)tCC>U=+6J`(v@OR!h2qpk*iGe6CJyq${9KIvEg`P^Ih>!I0dl)P0T=$K3UCu zwdjz4BmbXKiGnD0gu?QS)C(_uyE;LUHd6V|8}v1};;ihj_tw?oaDPhYAnJv@+GM~i47$-4^cSs1krC6`QFSs7 zU;cE@%j2}R!Dzz*^UPAudXGJteXfc;a&!xD+nzF&FpwE0v4b!BnGHZBDx4OdA1xpJ z;y;^`Ch2aif!4T?l2&PndN|^QS|1shg!{rS=z~TU1Bfp0O8qNh1$gK8tGfJH5xVw#NL9?OTiJ|zbJ27wg z=X%7H<(Sex>{UOfJHTN)g6HRb7JgNLvcTBkKo(uoXZvFgf5<~}%H3XXUg}kdR{ssi z22l_L)5#%b{fe)2s7SK_LM~PH*?JfLJqc(tlJw7iJVXt*C37)=A;5(XAV`UrItz`_ z#M5~BzOlRA>OjXDKnk*M{e^C%a;coazIx4tdKHBPm|}+dX8dLcH59SO-7{5ENjpq0 zm+7gJd(I&u`un|c3hjG3&`ue4h$BIlD?~p(5BmDfeT+aXF8WiyMuA`p@|N{ItF2@% zpOiUK6?w$qTka26s(5RB*k0K)L$XZ5UWzcw;L0$O^W+vmr`|Gz+Ae@wlL)&nM2UY* zjqjWxUb}aI`bADmE%CUVfAildY?;k>Gt|Aq|88|3_uFF*C0E+x+jEtHA*~F&j{OG# zUxpaX$Lm!qDeQWcm~S~e8K{dcftpdNAM@2&{!ByWCa!aEzKpqGQT%=_MaR#{bXg^P zIt28#tME4q7Yj7k;-}neX(Q`7MpLT1ebBB_(rpKTIgM$qQ;U*x- z5MVxWRzZNX|N97MToQtK==)*oYG=+;;jRi5*-^Lyh63yhCFoM=<2ir_)SuZ{?!K}{ ze%^NpWbMeKfl5~WJSWVTfD`_^f6o(+W&_^3uF4)8O9L{L0V8-3lBct1=~xfoA~WFb zSpbUD7WoZuKvW=B52y1SWe5(IJ(N^K z?+g`)E&En}UZb%QJbJO%%XR|F9zmAhWa)`U)y(Zpz%91N8)`S8nzX|bxcA4hnpVkW?NN!KO5vxJ zL#brWnZkbc)akiXPVkQKsN|bDFjl1}J(bD~k z5)a{`^V@Wvjj+0VX1SN-O5U0ImfMl~Gem1#L1HNI3lTgB7~*_*o}bqobxI8fUeup1 z0rm;gcGXlw64{H_W0Zy`MvIB8oDre(Z4V$p>-wm(SQN}fqd#r7A^HZkE#_8GzM9sE zQ#)JD<}gXCOfZp_)U8vJ#7HOof>2z~a7K!~{jmcJBz_7*OeO}##eu{1Y-XFKHU%jE7mLCK{gtWGA=D^87~WZYdc*6K9mW2Hv? zJ{!NJjmymUnvqf*#2;^O*s+7oe#9W)?_h`^$mGv^?482?2Q79j40(P=Hk6?N+&ir5 z?Tm<_-DKQt@}1atua_~$EZF`8@^MY!k?D!fHf&z2bPkqa*!m2t^6G=2`(eY<`0nxB zpy64)(kmp?8h7cn(#4S&!bnXG8#PwT4%jWD8Q-4fY$sk%FZiwFlW+zf3e_|5pc(KI z`ujkBs!WD=ru9QcJVGjwuY9PtSvkIQAw81L#Y*Fz>&#A?$E@uh^@NJsz`<6onJavQ zE3tj^wk^`H@-ttuairWRe$J4Qu!(~DWQjh<;q z+(Drp2-^?2wE2O*F0Wo>^V+Kxr?DnCE4Eh1BZR|vmKnk%e}neO@tI03LFEHcMfCMf zCvROhFCsCq%d*`F_!%@rPx5m|)6b>C+)G#utQq}R${2dW$D(TsA!A3lf)}h%wqS&# z`1D<4%EFCN;jF=(tL+s1$=``1#^NOTi>kx>x2{3Tp2Ff+@IKpny7qMPn9L1b}w^<+Up0H|8sho8gFB=DPwa@#kq&v6qn`MXensr zE1N#*(RuCt{wcD0`cqISr2z5CJ#D!*Eu&{LCmC1IpMo#I;<+|rarWw6;|$3Q+8yEL z_BD*BD%$()<)Uuv8^>Nf>|!7?*CIY?ko7?U@$kv0U}kQfp8eT7%IIMKt0U*Jl!o4= zLQBQ9=Zb2Sj3M{Ba#6jYh-T!!Y;#JJa5V?h@7NI58M)tk0$;Vyj}D;^_F3>KPXW0Q z_@ErV0TuC!UJ50S1Nzey4he>+kK1K%Zns%~a-RXWrT$B?Syeal>g+N@FthflYAp}%GY)!tzQ2n*9z66h%R}v2*9BLY2B)n}MBh7s6wZAF@@c@u z{oQ|@>&gS1S^1~W#>7hk*}yx^dcpFUyA^V1CJ1R5HUBLiv-oBr5JvNu?#~!VPc0K`Elpsi2Ny z;^ymskV&;X+2UHx2cCz47#GHMv^Jjonu7IWb3=g2{2}0=ov-x{@8vD4H`=7&A!}0b z(6qnbK8b!IhP8_0%t^S$etivkt*U~i{s9O1iQ~WPg?-ml>}jxUM^SxE3rX@ktT#ae zzGc;3TmF5Q(gJD51qFdqtJlB(vr#bq?7}0vdMXz-z4LeaQ<>Av@;wcZC#4_RQ}DaT zThZDY9MX1_5+8o>Z@E0=yqw?BWEwnQk^Yy8sHswEO|g~`Kvr80-WFNB`TFS(iMJ^z z@8~|afBqxI&G)m%?J&*g`cISzcEzi_P=D$ZXfo>@CAJd%Qf9$Y!>*+B+}O1`15&U4 z=4mPFOe}C6o9ID^-d~8XwfGZVYtfeK<0hNgKAaRBjUw1U6NszYlwQYd{bJ9!S1 zd-#+rJY4MqN&inx%Cvb=cXlwa^vMbKthS{hcbD>2a@HObqbP_9kMnEv&U`rH8jVgCQ#|2Q3EP%3bWD_ENorrKx$w2zSsVcZxryTt`WUi+Lt8dRh|ipeJC zEqj6JZjy2}@H17^6waSmBAn7yJ(ZO%+>UMLOT{mKt(OqUR5$n}L#$qV2P9V`RQs9= z(xpo8fx+ocrEku5AsTzo*hsrw-?H*`8vzZ<{1S!!*PsvPG*7yoIVkp>WX{1-(daPB zyovSwHj9|&cjue>m8Y6ho>C@^y{{UBYlX+V?DcB|?4?yK8-Pgfpc-R9IQ`hL+GG96H` z+Z=!R8=VTa?YVi2r5th*_z_x9n=(M@qQnU43>Z!(Z!g|^WEoWYhUZ_>cbp3MM4AQT zTBl?l--{OxX+hj*afhd>X*8C53>xCC{5yI`OzrZ-M_&lS#X;kNSN`Xu{v+ulR{o3#YFWF{mHs?Xv`RU3fEgmz2 zTK?xbXh}&h9Km7&w^;)Te^Kb#GWeAN%!QI`2FfCt6vmUv2ZEo|h(6b^;tNv~rTFWC zAH`HwAMEX>)&<}MTwAcB2w3NN4YXACh@(^$zwCR_o{4_JK)9*B-1Oz$d%K4Z^uLHw zO_sABxyfEf@R~7ZMgJROThabeHQ}*)X|O4L52Vvs^#K&g)*RIExm<35RC8H34xsw< zIq?%{cxG79Sr(ksEm87Jn;pwt3iQu>?1Oy$hq9VUAxG3Jf-Qpc$O@Cf9`W;{soas} ziA}0q!TA1LT)wMb7DJ-cugxX-(}tzVX~0pry+*QX-9BIk^6}uBdrwAOHO>Ml5iC=f zS?Vrbbsd(laBou_llr-7M<)+!!=s6BmquqH4_hAtnXRf51s29!_WlyZmkK&p1>Kh~ z8^7AokpcmTbiDfIi$AsCOnN*SZ%gm=ne-C~we%#rn`QaKMK|UyJ>P4lPSe?Z)J%2! zR{VK>*KMk<@Y7Go-+qI3TRj{PR&le}aZ}Eh4_y=UHs)~WgRwX0dMSVGacawjJJapi z*qr<1*Q)U0YC;~53GtgYC|FHsQk5i2_LTJMo` z`rN?Bn(Oo#uo*v6=<>r#e)<;_>NZLi8(#9>N>y%(wR>D6-%{i&r)~Vpmq3DN3nxMc zz!^CxfD7PADw6qiaI;eB-udNDSR%k{8N3K|_*f9AOELbxg}8-1J>0*>aEVg&K?nP9 z`%$F)%#ysX9%R79`N_sMDyoX_i_8qWkH_|!NL|n|r7r<%c+kB_%K*cU_^#Xpu4M#n ztm{wnrkekH#@`uogMIRGCB@fBanPiAQA`dEgnsgK19@n+3FL#kKHn~7!a227_dU>l zCS3J@-62lX>&g~@_|@C&5&UMCl;({4wN8Y#oJ~)tY@|2>11STF9c2aorv*5DMPG+D zR|#mEx}Z%hAi~e)@lzEmiVV1l{-82@3_Aiurq6QaYA%m$MD`QYn&Pj;Uv;P8aXi;0 zV2V78Yzg!%=hv!C@sC3bxzaOwQQMm~X@coM)Uc7$l#* z$!I8RM1mud@5&uxGxDCg6g;&HPBu37ngcqTKvvhu!yfO8PQB1drFRe^Y+tGjt12ax z%^dwWUZ6e7OI&=-6zbi+@on1nNRKK#CdC)8O<}Y9k-_)nSP;3S5 z)8z_J2c1inUr>2QqZoM)JtBi@x98vOSE_rP|6XwU%b%wdDA*jhJ4B54y-}9#tXp|Tl#H$#WO7(kx_@`BDO1rHwt0eH;>7O2&z;z}rX;3#ub;ks9tKU2` zh--b4qtG2c@ULmtiC^ITIqvK=_JOvl46)bs1P>YEA~X;A=1nB`SUjO}eVh{Ys(*?6 z4w(OYs=&QM*pqI5Yuh=#QU%8t-@|&m^?F0%@ypJrEmJn zt0(Icr7gdl6vUU369Kx6pye3%!|}_STh-v;SkM+u{ys}uH!GB$(5P*7FU`Q&xxh*@@= zw>A`T&NnwdFO;!Mmo&{1f6{lmvMR|W=n52`9gu&z;Au<>)3{QeFBgOpx-Xtzau!hV zcwCtrc(@O0Jvc=dsE&nq;>gN^pZ>8j|q6i;eScCjGC|5tjyU+4!Hf?G8YFQ#d+>Ac?5>$=kQMa5{ zl^&>;XZ@A@THkDcL|g7f;Du#> z2IIvWnzF!2?~aebJADgquf_68%cj_aHKu$T3d?qumGqLA4<|_m+%DdYsPm4cV1^|H zhk=C>+fyDzesP)`p%&(<>(=W^r5(s<3?N!V7BmVF7iw zSmf!9*3+8wIjPq>yB@rTJmRw=R@+h|gJxiop8an!KR$WN40`w(q4Kn~7UeUE<*;D6 z0@`_L4f)`E{iW74_06IQ;!+&_m!m0ds%i{@8gY9iW@2&+HvO##RZNA&UF><^@!i|u zTNohuL@gTdJS?9>8Z4?!GP;=#dY;opxFYj*claYO7m-nDG-{7RQfHg|8OYO=6{lIN z9`SsRF#>s9veXf8=yBX_Y^HW`UI)a+EdQp2^Mt?CyLLU@d|_&neniDunDt`+eYozF zwV}7y?y{`HZN%$u`D6$`Xwm^D9=tpXds#y~zAPd$8cAs(R59T9mDjfmM5vNlI;sKZ zOG?Cm+PQ@L<;8Ql27#$v|27I{@qeauvYDflxmtwG2Z2`*ejpBWI#N6~!{aeX7kwWS z?{Uo+g98UsU`iUyR%4VemEfwcL$-15SLf+WAUGz7B5?QS~D8P>mZQZaQ{o7y}G8 z-=BWoRPX*T#0FDa@VengK zy>l{-y!hIZ%>dY=P8m>^u7J~tt95KyOjay6s+0SRd&@$9W{*aLJOb9Y=w*x|2q#!T za(0(ID<-|C^wW9?)2lj->3{RV?rdvx!-dES|@Zm)q4EXqF|bvHs&>Zy8K7cKVK zEz>~fHbS*AU#?_xj|mD~4Z3C?6AR$twJn!x;Ok{AQJA~989~8GbrL7y&5_(hO*mHO z`0^|;$C~F9PI|*L7Ik9~V2WpUR1A1L=a>Ec z3LpH$O-V1E6hPcz4!H4MFl?$Ez)U(RaNq0{ZA=yDZ{ha?`@8(Bj;az@qo8d&~1>t{X=YiK9)?s|crY~Nldhu9$UQk}vJ|rv1 z8u^#kno~1^&vOOfNBquud;hw*{;!34i*u^8n@F~ez0_AcP~(1)1DA^?x`*|%>J}0(a^z)um*;PK+xrv5pEbF7Bd| z1{*JJmC|w)=g9ptY&2AN406#GWqgufm#D^2mX<>zX?QWP+;uS3V^i@5;(V7Dewy^( zIIv^@aiCETzSImHW!|0dE*n3w&{oN53=_UhF~j+_wzIp-_~po=nrKJzQ>6No68-Ha zO}u(tNkd@&fN2Z)K|u}Ko3;qveMbpy_c!mB;y=tJ_$+r=i zb_7ROVov1wa3a|X*6T7*{9HLTL1N1l4QEqF1ubA=aJ-|BQICws+}{UYgCCt*wy|5_ zuJO3Isi(Zj%5r6p)tGjBL6K#_oUC*aF6;D?`6Iq|JTF@@!*!vKXK`z>@2$``frSLY zg;(HwH3dhyJdbkd$%FDa$c)Xx1=*yloP)yO^%x=0!tSrfpkoukQmQ1|d(T{WX+P&| zv%3cpCOinpf`cv_;4bg9%I9?9QC5(Q_i>b8%I^`*#)@B`LmWHug~SB}UkZ$Za$Ud( z7OFKQC0g+GQ?oF5oK$9-b;~9L+~zZ76jlr((7SU4V88a+T-~EBPZwd5Q+*@dV)r3x@l+>2vDodt2 z`dynN1IMtHJ&GN>hvBs)N4~8sqM5C>unX(`oIH1=G2q+0MD%_pW!6a<^8n-c5odCME~>VlVV^|!u5 zgl-Nl>p9DZUkruAsn?oGLZ|Kr;{e+!>Pe94961_OFkd77&pf@%QFlM(mXMWT!PRR= z>q=_2?nG0e6P}kw`0)4Cp!$hkkh@3teI*qE|<;t=3$%FE(Pz1 zqn-6ji#nNtY8cK&^*O6saOD1cch!*^6su|9%szU>Uiw79<-jN|PL84Z_P23?uGgY% zh?V7Mli50SW*5CA@dAXuoRB5* z^r2(|L7G{Ui`dpTP4)F3nP0L8gT%5}Qxw4Z0<90pUJoU3ayLrov1Z#syEumj<=`*U zk#kqw18M3!?t3(qL>jt?kNSd0M6#cKks79%wuW%8>=xZRF9KZ}>QKlm=)%T#X%Q7B zv&x%gocuWXhbv+KGgx{4iAI>V>_gQyhe3O?~REbunRyyKI@`@?Su`^&*MF2QF((^;t7XzC6+|-IO=yPWM&A zwAuKbaf$H|9oSM(`xKE?XQA%CSZ&%DTGnxRx2oh0Y3UGru*mmEbv2`9598gOEH6cs zB`SYvE(ksY>~#|kNOyR%XodUKFcRuo6VVAZ$@m#uQCf-S@pBG-pEJXn-P$hLuzMrD= z6YcyEQi5a8?6saGDSbO`l&9JZ78il(&}wx@TwnikLl|5aG%L@&F}hZ6d&9hQL9;?$ z^of4n@y?LC%@KRtQL!ObVXcw-02V?#jW8!RiWD_>sX3>g3L_N6)xs@-?t!IKCyAVh z1$E&)CRxuDDVqFOS!aRCI|nOJ7}yG$&MG{Og#ScMe+L!Gyf5kw5k~5IMJCG2^(r~& zfS)ojIhdQrgPcr8=JJCU;P3m3-HIVb9VW)YU__k|-vRdkoQXX~dYKkioA!^Eri%A3S7t?xUdV@s7Ta*xlrzk_y@UuaZ} zxlmKZeR`z6-3`|NKs4eF5OL?rqQlqT2I-GF5JOse)f@ft6UM%if1ANvdZWgJsnv7fG@RF~K@=2~pHO04=bznWtiEMEO%>5b^?zjWK&r|rI+EC7YXbeelqK0}4qgVY7yPKCLf`bO3Hjw*`pnBAL|1 z#|R`=fSUQBHXumk*IK^YV~~^7faej*-8cHG1Ff733%m<1JOMZQ|2B8cEJ)x86*SRF3UM6;n$Cc>Q_qXO zMEG2Dk#A2mo$+(v<-k~n?CnA!?K?j+p2x+{&UXJrG$Dgbt3L`%Jq zzPY?QK-G4JOqt1l|HNws`Hi2M<%;J(f$oM(y^ycripSup;uU??ho6-us*G=e3#rAM z{3&SN9xhlygyF;V17ES+KMGIfdg%`& zmenZnPdxQnKhZcmKB=tL!*MtqN%k{{noUxY&37#W5bR^Lm0!oT4wtJ$UIba&LbAEB2aBEq77y>|Y&a zTz@El&2I5NJ3llX04(eL@!r*pT`TuCDgKr~T~E(JFu5(*19z{62+vh&SK0U<5hd^C zXkbcRwHSfAlz?oOMpwVTVXPM2i+t`l1B9rBeDE_{su%Z>72v%9s=D*VE5~egeg+Jqr_mIv^>D);%c%^JBqhdAEQL zdf*yo>EBX!bM{D)&a(5jl5cN!T=2+0fuXnaK@um(nLAmxK`GLL>F;PbCcQJR;#{NG zzi`&&9WTnz*o7P}JP)5WMf6pU2UDq*mXK3rF*3BcHiA~_s&6rDm2Z*BpD|l zZJ2?3xWCd^Pi7_baJ;{Bv9}zMeReE+z=s5+Co#_%=k~rFy0x63mXnVGux6g=WojNK z`+4J7<6khNkjor4%8f_~N+%RSTol;7%kX;aHH78#=MrSsg(4~6zBWUB zS!L-ie^HV^D>R0R*Vi^QhZ`LhshsBQ!zeWQ^L1O)HR>9d^ga5|*X*1n4P&ST^ZnXR zBL|k^Q|%8on1S28X}qVm!SrXxn9)7fDld@OfZSk9mr8QFLcIB6kW;`ukarGe4E^lU3 zoJ(BaIBW}CQQ8P39Y3uXg;p&;>%5n912WNOz&2)-L9xC8~apU5gyWWsro*)pt@S0~@3RIWYj7vD_@ zm=NS`r3SF>qX7c47*E*258RzJ9=*Ev)*aP3oHF{XU?rP`=e1{X#M;<;inBOKvlpjZ z^O@sp;7;ekC;|5B2iok8w3|&AI$M4NWb%Z3kW8_bcg|b|q+DZM{l88wg+)mvChKuK zLHPw(uwpnKWs!rLnY$8vSP!R|kw4`(0$hHMeSnRXS%37#ZYXCbt)kpk?PAG4ACFNu z6WGP?)RmWqGkmPR3}Q^!zAaQg%&PY4jvllZ@!$-uk(E%D{> zbjy+^2i^VjTpB8-z5G;q%h7+&^vT};@)mYLjC6LT@mz_Mr$)!l>XZ3Dp3XLrP~c|>!gdyDZA7Y-=TdM^fSc3(P}QQ-urFU^u*bO zn(wWkl5$>$3Z`|ZZ`p#9jZF8_E(;I&>{uv>$28D&)5C%Vl#v18g4f2DUqFHfU0ME)m6Zwv3(R^e@Qq1eNNyiU2XmoS7tqbKIemskD zO(R3MUO#AMaB~m5e~2|TbzbKhbnAChpNVxYkEP=$O_bITr}q*M{=~teaotd_N1*R2 zV2<}+Vd`M{q&6S>#|Y&^3ai0PcuD^MwrOKk155CRU07&vPd)4algI70G+~K#o_Sa@ zJlD?VHMij!d&P3F5)}37Y@u)ti#o|JLNniT@nYN-t)DYg@pjqmn6q7An!qn)y6m3?tKDHn zlqCdir9^W&mWD;jJH}0`(ymeNN?$F^bWvGy)(vvgf>@<&d&kDLjp-{v-O^EEAt7|W zy8yU^AaNlQLp*&$!qRB~`7N4Ohb6!n7|6SN1G8?~UrB1}x0&I~o9gdcq6(r|+&Q6_ z3CQvzxr9&Y8M$kDST-YPvTv8u3x254^2~Mac)GTap+t%PlFXasZl{c{RJ^A@MePzn zt5O+*-BT|Su`Uz`?BBV$xQ2yED?V$~92Yom`Rd>r#U6Cdzq?um|z%Zgle#Q zRkZ3G6CVs_LfYqE?3(C^27Wy6J&tH$W~bdXmHE-PH>0{bRMu&r&I~WD!m-_*nO{i8 zk?|3_z^-P`nuuqQUO*y30b_yQ%Gcs&9YqT0e?taoa6;qzA^87k0Y-c;exhQu6!fe! z))fm3L`t4L2RXz~3M5XO0Q^2XbmjlMxH9%Su$g81hHV`pSMl7)tp{5#EH63JcE0=; z`iq>%H#VokXZ{tbliRo2&JMKy{?M}V-B{C4EW1d=dzN{6<*NmuE#WG+8fq$=2B*`_ zH9X53i$%Mx68-KV7Ve@Krutc1Z@5xJyc8(CG-AjVD31pZic!WIhQx{i!7@Ek19NhH zr1a$`bSvtg?NyXnZYM_;(N`3F_F%&xSM86-ChPlUQ0^gZ|HKjXV8ZgJGN88p2DKSH zh{lnWjVwm|V|}*}kLqg;|9^OT3%{ru?`wM)x)G!$1VoW;q(SNK7+?sI?#`hU1QDbg zl5y)ahB?pN-_QGd|A%w-*=Oywu5}C}U=V-cAwd8UqXMm4Qdr2u#i~aJ zHmv=Y%7+hXC%;+LQVQ5Vb2d0;R}qMhNLYXQ^*6%Krb`Mm@jc-SzQWv;NB$NL!A4NJ zXfyY9_?aou2dSRoCGLX0v~E{K$1{cRO%dN~>H3H29KTVZBb4Sqj2@qdR1HD6guke z$*oQ4*rjRKL!LE5TG)O~X_tbzhsc!x`HovK`yL+?gRBW|7SrRlBoAO|2VCM_2yPZ? zeSNu$(K<8+HQQCD|E2a((oTY)w8Yqr!ifdx}Pj z>FQ3F3I#Z}Ujjn^-AZ_^s=j6JRYu0yqL9}OL~NP`sDDPt>P6Q(=Fk5dMx4_}mB9Uf zS#tDA^QOSyqssBk3zwj-lM3zEj}nf`DCY&q6LDcEYWdMxjGa}ddx-pqiFIfiC4j3$ z15(RY|0jUaTwhDl2MS5azTs1@9{80Sl2my~&rSy(ZJHivST=S(a!0JDcg-n)lgmWq zJO<3i+b2S)Ks6*&@(Hh!_iHXfmjq}sgX?G6rLUi!n&KIM^P}!xS|o}wU(TRz;{4mC z3^Z<29LWIU>Ji`zUe~P!-l8d=>(U1xkNw9`1cTpAvH8$83V8Rw0#)dRlyYBhhv9L6*`(ukU}_)A`V7&OlcC9D zLD~(O_UkB^+h1~&Rb^vDKxKeWNua`+piiTBnuLZM9={-eJp}M7EtJ10AS?kJCQons z5Gir16G?|EnoE*s`XiS<$(k!2-9^#K8}&jE!%Y|NT(zo*&lwe7cLgJkbTsG{Bds}B zRTf}65@$M)cnyerIJ9?75ReL#RQ1ePl_Zke2gj9Rfd1;bgh-bCr&yoNHHndyT&GEVEuSPN~UnnOmdZFt;doEDLRMFUajZVN2ixW0QX|3*|R07dK z27qxF3w+=%U*Ijy#kVN4Hg&h*l__NKSW~s~ zk?Jf8=$Z-PI7>eP?H)Wq z_1$xd=iu-JS+8~6ZZEHRzQb^P2smD%dm_qfTuBOSITjwx8P}*q5>jPtSt);IHcV*B zlQjAMr=OoJ@9uWsZGYUISeZP5^Ua<+>0tn`wAZgh$=TxtnZ(uh)J6ji+gHEg75j85 z`Bg^YSD*7FY$9QkT>_D?`U4>Nl)J_ANbjL%2M{xnY%>%@rWw!dsaYX2&2K56bntXW zs9Y#wA$%iUL3I%!yhe_SdjDA^L^_#9u{pWHBf18n>$+xMJ@yMw5Gt z9$yLYp01vB-LPe~2Uqo**sT~i4GZRXHoFqKj41GwyII7*N<(#vuV|U@snq035(<8B zS+ztavEgBCa%Y8n*nULyHfOz~9{bd&kY3ib`7Bp)!{Z;REbZN;e6K3oL2Ve3F@x0KjM`}dUj{+mz5;uL}Fgj^H4(c1>a+KLGQ~Wen z?h#rBs#tGEN0wj0)J|>%KyY4LcLC$cTVqeW~EYM-K}%)0J7uFLVW*$dwGem&e~kFr@I11JQcW2EW1vZcI6J!4RacK`P8oYMP7E%p{%KM zBhg&Dt^^;KA~gkmudCq8Z{C?IiHf_&`BNS~WzQG}l0c0o&^h>y z)svS;@T1a`LQO67n!Vc4PlHWSd|E7yPi$pty}yq%2h<5sKev1NJ8h;(@~L@7NL-+A zxQYw=N}IcE?OV|_O=9!H5Y-mHTL)iLjnmLbK;4uG1QPr5(uLdl2^lDa8(qXn3GqKg z+L8tx5W{}Jx>Ha##F92}>5Lr@zDR{kNS&GLmu)p-Yv2Fq+IUdPu?w8u^PNPXh>H zH5B6&zrVCuR5)w|0ojS;3AF^02#JQC6KSRb=aVzt^vDdUTaaNiS2t&Vsk@F~3S6?h$I2hgjwG>=GJQvo#G z1UW=ge+{lyNHOGaHQl0cH$IUetaae_ew~4cftu;-F2X#)I-|?1=a+XqqTamDg1(;z z@`6;cK)<1`WiNTw1>!BOw^)+A{`Jt9GZ^np$Ka~w&Y8MGlpR&B`nLV+4&Vk>kE4nA zNxudWoY&8d4dKZGuhaGm#R~*&PjXixL@tvV;tV zDmFf(;zrhWe|Pjpz_$l{{Mw=@w`26YWwM(KF4B`_v9kXPjcOZRJu z6|$crN?1vRHvK)=qE)PJd{|z2qPk%?HMP@joMdOG-&Ho?7j_6B;KBlSPjMKazhC3g z`w@tE_(mD5idH#kViw@q^^2ns1qQEloD*hEb4uR)T)imFbquJGQfk3`l~qqD7s~nn zKT7wU%dwYIP_gndTh`#LaCQ{3)b+PhIe>`B|FFa}m3jQY&houldb|KXuIqNADl% z->>8xn%c=+`P+=}5tAac14fyMOZjjEu;x8hv|>{NAfk*WUGM%3pBswBU?T0e>hky* z^DS<}fA}d+j0n(6BjDGBRfi>ukCSb%1U%;sBf*J86v4@E5!v|WX04NWli5ZRi{(`6 z+ae&KhJuzfL+<0c$sMkSG-m$=E3&M9IDGC&w`&-^OL^i>5fb#HG2!?GLQk^r90!8+ z={}23EjdZ}^CbO4+~S|}`ubT*4uK|=M>{s=TH`(e?3|A|PstJkm)xm^!vnJDSB5jLa@R^!BXL8G{ zG4*2-w;GL$1{ugvyhU9tQKcn&R*K`E{8R9%P{itosTnpxpZDMDdqpqH_u0TGZz2en11_4sbs-XtY1;utp1_P!8$fM|8bF z``;N~_U2rH1QsZW^2RO#Zl`*mq*hyQUOb%LN_jUQpbKbJ?B#K{%XZh_c?b2_mXqvW z=fSi(C)l_WenV5lg^RjHk^icpSwSOQ$pRWs4J>>*dt zD5r#*hcwBHW3ua%FmZL?k|`0nG@eXFiwO4cn<@5QmUvP^|H6Xv-QfvG89sY-&5G0^ z&RX7T2S%H4D7lSU0H#x`qQhd~kCwnK9Kjxqy-*lr0$%EehCF4axY5XPfHfoUDMIR= z&jE{=NXU82qS3tpeSOij{*)ps-?0Vib(8;e&39^J+^LCY_+y94lRImUF5t2)vb|NS z9UPAhjwLRy6-Q`jM(G}-bUTBrPw3F zJ*@}q(WPUqdZ|(#!!3Hef80yBsQbD9?dZg{KzLw4oC9p*EPwWO5|N2xxJ`kA9x>;f z;tIX53d#4m9U3IoZ1>*na$onVU%b1oV*T*@F(b&J7?Sy83rhFkLo&Dq{QZlCJmE%K znMidlnW#G??e$uw0O-#QF`#U|T4p`{w^Z8pM$bl9Lje@hY?%)eV)KfkgleGc0K0Dv zO~^wr&4**nn(ax9k99ZlZmE)?fUJ)(?L4CVNQ>;aKx(nlEL*f~ffaXDS&s5vTkdU5 zFG_WUB7ahM1YS`s`Vk4J?_lxZg&6UWuk9{4h!n%4i6xQKf;u|j0*%iOKF>+4&n4rM zK|#*{p6qCR_-{YzOusU^zx7Fq)B`c^J1YO_!cd`=Kq22y`WE~q85Ru|Fg8K2rW0Un zv^yw)f7#)s-6g}@(D$!M*6vG2=SVl?AoVGv^!!*#>ZEgbEO%$@xezVbXZ))t$yA{Y zi|Hs`y$O%5uO3voxidA4vz7~^JTQJMR>GxEAHo4tnm&&^BS)0LzPvDh6wc{(k7Vl4 zK*Kd1AQs&H5>yEj`*8c^{)bWAa2VgOjKpY#hg{vjH!FqpF9kml;}PLu=L$$mt$)Ur z`Vv_bxp1Bk)&oJREkDAOw$V`FRmgP4F$eC|nC~d|mD7cO2*HO^@yHbP)1!Zo(hl!K z&rf^Rbc=OP`hRJ7iq2)JbtN}P*7+{F=l^#3NyXpAFOZ!-?}YJhBAZzCUf`N~UwY*9D4^mYroVZW)f<9UwTO%Ut>7;4CpnT|9Rz=pd z8OwEg)oq%G*qh-T?>`>hrc!G0;wdWloJ&EP9!Q@w;4fDRz`m|GfAVu62~(}<;aUx7 z+hQOZz&n&1y&%d(ZU9ji!e$qWK;#rB5a}+R0)*`h3Ad(>iJdm)tTcR=CmDkwbJOTn7xyFXFKH8Qw(j4gW@`0c zW9Y7XM86-mgJ(=L?6>OeY2)q=1<+7Z(sQ!_rv1l?B94Z`@|RJSjm75B`-E9 znfZYfH<@8W_pS%Ut1o~wU5HqHNRi|pU#j`Jv6l(|(wbjmFtm^KKjb%tF0kW`9QPMK zWz?cvgJb2(aa#!$hWIzi9hCD8!$W~(z5dCwCd^zEQ%jo2_#qb;8$0s96&&3*aM=_X z#+vSf0G9|@GO&Yyox&!+Zl3n}G#dkR|GE^@ujD3uKF3B~y`BjF$mjH~N1#+P^>e9s z{v#Uu0&_svKTLNT%zxHQcMNwjTWvj-1Q|X2-yhjdWLa6KxR)qEK{4|(Q`U6&stW_o zWKhy??7v)!_!0f2pzo9~YtcGSjCSR%%Q|+BvuJYssaW-su6sN4d?)^8{eI*7ZEUwi zT{B%^FWpEecn&K)tYs?>vU4>b^IJ_w zQHb=T^WX#@j03_QzI%UaMw1|bip`B2Yak}~#)66IXnDIGguylxS+o%iB9{HQe|0j_ z6@*T|OMDev07K_YSLuXhd1qq{C0pCe7WO%nFASSGs%*(p2L90Pv76${TUad9tWz%J zhW|Tvq6D?ud@KI*dpcLgoXe@baMqSBj5x?PTzKrr^{52$Pc1~p_PUCV_StGj$lU53 zK~C@n!v@GE#IHF`Ciwk|LZT(!Vs#o`9yT^c$_l9J@YJHi1I0g|Lq#l3`jaXF@4y{R zI5ZSECVueQ+{Fvt>eu)VnvZJ8HZVT@ZRtEmhI|JtvkaSeW_MZ!W-0*LT78=AWQyJK zS!8{A`@gZ@O;oO2*;wc}Yhoe5J4g`Rf8*Y$=AZH(Qn|%LzZ?#QmZt}rZ=A;mqtVG2 z|17VDG~#j^xs&NiyTtLw)wok8u&m{Pot$9A!kHI~ew^bgawFP{tWE^CIcH4tVsMuZ zl>qWkmkd(G_80Fht;vB%M&E+SKDK1~ZMbCfS!Q&QX+p{crW zGho|_ys@x@xW}K{6g?6Zakb%ObDs=sFQsq6a8VUnH690Eu^^IA&dT+>Adaf5}@&oRm4Z1NBGLWV8!r*_nLr?pc%OFjHNe z(GK&}b2aIB%!3r9rQvpK7C>CV(O?RG%53 z?)@wHY!JZ3XKe1e9PO~09Zl3+fOSP?gIns_qouWTvZCsPUn8eMIs2twR&V2S6T7w? zqt1DYee_&Lv5F{inV0jg+!xlTI9uOVB$1Az_z3hG`{L3+_9p@@*_oEzmLw(6Y0y$7 zy*|s$g7;^}HyvaS#Ozp(e<@Y)TU{~yM+;`3vIZ%Ecqww}j5o#442!QW&BI8^S&qzW z6=Kg`lqa}k3e)(#F9X}lyl*v~8Y~p(vVUkT;Uq(I?raTz-7`iOxdp;5bh-BdI*He< zt{*_=Yf%3+DS(|U6quev&S^bk17AfWP@_6P@=ddOpaP313#td19_{Ivt-F8~o8-<0 z*3Fk++9~K4{jTHcb(CnEeOWl;oX50M`N6hrJ96{9gXAO!&_v_wway9gO{+>XUKdW8 zB`lssukRqk_$Gl@gsTH#G@JE~Rl9;%{n^{8EWN>(f&WZs;|lLTQO7*Fz;*$(kLBQt zm+XC9Bn_+4&qYXSaG8~dvBsC!YkJpU=U+o68-;u_dK6>`9-N16gc68?ng3!xE{|dL zrvPK-P830#OAKp1U1z0DW7YO|gbxftJ~|MWyBD{j&NMeJGDS=KP@D&iApA0>!?m`V zbz;-Ur0u%2cxtFK0?swxWY!e-LxZyXHf#>jUiLKy6 zL);q7O5-)7h|?h2`k!uS_Q~6XH3hT(Q!{^iiXFg;Cakza)Z?x(kpWv%9yixJND|D3 z)iycgOO^dpA4-#uCoxyVdytmf1wh@5;|@MKgtCyu(Xn`Aq6L+!(>}4rmcwX`?!uC| zQ2z|CiW&`ES?C?cj~Ft;dA-*uc-KB}gjk#}`L5vGV_piH-1-J}pcoj9897gzl9n0z zHb(oY>NAH+ca9yr-&dJ|Suj2J?#W;QoW-gztjV;XX%~o9*wa6~Dr9ynFt_i1$%&)GhLxQ6t}sY!15ictNF<2QVRSanPm5);(v-}LWWs&QA30O$%t^*ghr65 znAG1Jtr`&UcJS37*fCXN&GbYzgCQ2Kst7MTE67vzvo(Yg%T{T;NT{B&lXfHRgcoL> zm44^o6_D*Y8e?lkMGg`bp%0yj;BK+%emnL$hb@1iOphd3{GnR<;|Ou0P!{^y|#9?ekRx+~vYxE?cyn z(u2Jr1I=R)yDgqg;CXwp8KQdl7*jYn3a%487Hn8^#7+Be6qnc!mIeQ-Uu?@gmo4_l zd`PDFG!65Whbi7#kNJUG?MW$anEdW&v2+0zUW1$ed1=_*D7s0D_Y^MYlyE4kBfV+w z?L}@knZ~2QF*~u`i;x$wk$3{aF=|v@wWN%}OfeSa693bq+S!ikuP#nrjZ@um@6`v{ z-6K%=AWHZ-e@F=gaG1Gl+sZilj?@c6TfACMLyt2;4$mM*%J54OtF{wh?dQ0OQ=O$E zn5D1I4_(Kstm0fFcCpOSE5~H5hiv>(=s^KFwUUj2y@CyWr|GE}8eE%N+N;JmpyA_> zb`Ky=eAtt^tHGJnLc2IPLO=V8p2BKzVf_(*s^&711&=ndRm$efcjBpoIk$yX0G>3i(N ztC!cCU#F1RDL(cpKYso>St^ZxKUcu!p8@hUP4n)%q8U!r^&r$CAwq>Z-YJi+s zcj2;<*Kf~L=rWRc==1L#c z1Wvh2<7-C5@4XBL;UbC<8wfFAR&oIr8B*C(;7I#8}~zxZ{jl+UGfT&w(Sm`tp4ZG(n7n=Kr>0cH5NLYvmX z8Y|sJAXk?fYP|vdp>E=4*?##?*yY*8(5~ZFA`WSh_kKY*o{N8tD&l_9(J$wBZ(nzv zD$`1Ext_W>;Tyj`ILLdPjl`VSSQNYA*B9Dp_UWxwIOTkWrmoU>+MT7bBQco$C)0Nr z-dbz8@&WH=Y8W22!-X_@|L?`|8RUHrrXTvwc4mmKw3HHra$XwvU=JsHI3gx#9<5IEhh4R|BbW=Tr2(|uQ8@jX&SyQ@R1qMphEfNw*RJ5+rIlmvLEo&Mb)7k*6I8>vG^>FL^pX#ujgmOTiAmKPj)EBVb z%bgDXu)%F93ytoFRHc;ZNQap&`|rEc z>RkwsPNFzjbU#(X6CWyhLPqdMqAT3>CPBg0C0}kUm6~Yi56#oRFYx~=xDWC0)Hc8E zTbV2i!V#%D<`?L6avH>OT@GSPH8(3>}M9f-RHr&N#hPFI&gu76|b`J13EVAYJa zvyPJ&7BZpKs!;rDhTn*;1aVE{H8?INga2OzS4LV?8RrWW*-m^PXux^!F(wG9wE+Ql z4u*dH87tgMpRs;ekuwY5f639_xRg#w{z=CDhm|8~`4O$fa;=F+v6f&z^(Krq3|-_4 z)u_eGomw5W>lPxlxv8M`znj`6W1q+z9$C8wYmL?pKVgHL8O@rb&`79gItAb_ld$*N z^b*XF@n=OF6t^Xu%r9iT<8Z3T1xR9kC3I74@E_77Pz%F3a#;kPKNB{32$2g6nc<4v zLA&1KrTv0mEH>G;%yk6#+HaDHJ9wmXsy~VYk*u!Z{TJ3n{;jt=sf$-g*& z=}-vL<)?Lr2-N${VA$Q6KR^gUTJ+dF6-CDv4jnU+d?@7m7QXd-ou4G3+XPe9w=i|) zm0Z+TNLI6k=GTwpQF#tz5HTA-NQD{&Qb4``}qLDM+Ez5&%j^hHG>qqJ4-Tc=cL9uYz@P7;E7gVm0 zEbaoK7ijJEY+l6C^qTrsk~-$n2hQAwwG$b76HO0f-7_g5P*{HRhusRnJQw5OIy)83 zSm z12phDdIcH1!_JYT7w`~-Mj!Z2vxr~he)dUT(*W$?(JdYda*T7`dT^1>eQ5VRTzjj8 zTcaxJi7xs+0R8kU=w2HOIomsVX?FeJweMQqa!pFVsVB}^Bk`K;J?2hTN7O3pXjO0P zbu{~X;yxw-h2t3?anZxf|Mx;)?)^HN`D{$tmn3P38ZUmLocWq=*3(-22OeK~{9Fzy zw_tdbGbcmEF9H?tojG@Ivv*U|<>ig1%X2zb+|-NDa(f1Rj06RGc2;k*pUw+<6>{e- z2d#Eu&@gC5oHcD>bsvO&WRubLRfHd#es}or=(93&qjIh2utRalV5)Vp(huqHS83iq zCp&FQ#O9(@)jmB_7`cR^22+vhylTf0H0}Y0GQ#Q~02#Bo)9yK$hj{mNpAkpmh7Ct= zh0pk>_ukLYVR>Os=iCe%@*Zw{U_;-Y^|tWKt}T4wj6KCmJQ))Cs=5DH4cCC}`@hz7 zRhtg93{?TAL}`SENHufi(l}~cZd4GJ%AfiVlN036naaqC!(_-mz9Tc=KX0DE_Ysjp z+mu-L#2XmYY}E4TWw)ZY4PP*x1aMVfj&-ChzHyA1po%+QwMOmeF$Wpvbs=vgtzyU^ znN%oA^hWafQe)%rx64f%5Sv7V%)B>485nznYaH_G+mMOBQF7gA5&|A?$gYKJj{b@z z(F@C^cM@|d-o*83^zPx5QEvU!ipMihd;djUpZ_P>A}NQX86K!~SUzr|etO1btclvl zWv-ydkFYGwk8HK=o7{Vg4fv2!t_i%5mb@w=s6t6JtDjNU6$V(UpL6vmLF0m=`-G@Zl_Svh6Kg%c949+mmlmFN2HT) zC5vTBtYiP8Q+y$bqddNrhuhqogNa`p*YUEoWFm6ef3L^_26{Vi6iV#9xqgKrH!MQjy5KVDQ&w+qI#4JlM96g zwni(65dF87i2aIheG)roAU|_3Qzows*^W}7oGEYOZcz*wgff8A*o`}slljNv}_ zKuRd!y9{63o_z7JKAh`SJ~_brpzds!QLy%GY-6Cqp-!7zKv)}d6#X2$>wx@o)PSZd zhy_jLe{?(})+Q`etvPIeLP}`-=Kiln*P7Q0rahxH--58EBfSb}NHuz)!hL@{)O#GdrBs>rF+onq&pTj+%4kxSx2nQlhSVnD-X>RFOmG>!Q0qj zqzD7Pl1aL|3(jZzdE|R*fO9e^unXn9{O7EA$xda2Jb6cm3g))S&Px8|&U$p}aJ~Il z6))G^L6f(zPQ`uX?CDQX|B#Pu!l@b=JMLwC&`^??no{SAqQlT@B5MYqPeT2-ua&yn zNVc)U%4@oRWM^K3hF?kWSQIDHhcn`fm#Vu=RoHnPW^}JOuTtn+K7~kl%#JHtTdbpU3z0@V#L_F&oKmRyyGTgL%)q?$Q zIKNdY_Ny3?96j|D;)eHfoBnOjE@zu?niXe+ojyqQo*jQ!v@ENBT>V)jf5S=kb&JC6k7+qL z9Hb+I{}6eMWMD!v&o|&z-(LGUtbl}~7+-|ElZj85(DIEV<;T(FR3vp_lj!aFj+<&a zDQ|6`i!DoW9AWwe%l^3&XAB10`BrmcxjREv%ad$-@9PDsJOO_ON8HT021 z#Eq`L-%zQ8HU#^+Ui4dpBuGYyWXPP2TYTcVKh%mC3|v)sves&RWoCyN9-KU`b(^pj z-@nN$GEt_atu!!r(7)rZe6w9B+?WR|TyAkarobz}yhh!NdXJ~LF&P4HHc^Nr!!@b6 zhm)LeJuUMpKw=v>N(H?q4te-5_AQl&RWaBS+o}KSc&~`-1CMa~aONASzUZnFB`3!-xQ+YLwTf-3NI4Mu6IoqFX3AOfS{!^uRPzn&< zv(jXUHLeZu#+pw)$I!Hkmj$04jgh_{KJ;0ynHhPIo{d@ojW@TIR*fjPe98nJka5 z9F=PCKTzAWhBEBbVN9OCr3etaa#%PJF#d4e>bf%8PhNrkoVz2jzm<(hV9G=vC&Vte zc&*V%g2ct3CwlH=uCK+k{+OScQE3td<=z+1cez#=|Lcq6*X!6P_um!_v_Xb5fL+ECq~8RvT7#Eh zfNDhGi4EtWf}JM`lha>x44u`lzvF~JNjcZtdx$`L^fi4h>&U1NFT@9}KROd0r>FW} zk&Ih&6Ey0k(qXepT}~S@S%|K8i0h-9^}f$>iyYnwuSIb%5me9Irh~!>WR{H)O$3g& z;BncLq$Vx5@lY-1N$7*DPLFtE=JTH&0!@=cg`La_ASqvhg1}kxfLfE(#@cqr1~&%+ zG1xO^#;7FXnd0elwvG4x#o{2(@#?v~4J*mTm!95pFj^KLEWI@!_?1VtW&k)-|qBA zN+1{u)zYr6aclQdo>;5m0be*j@|!vj{+NHdcK1;Vjb5xf@%m9Dgu0!CDt+I*CB7i8+ zj>gUfh(?0^UlagtMnW{Dt*lMa7C#qEq!j8!eN(@%FP@-}*2;F`NOPtMavEGV&7qH1 zUyPy}5RtiJ|K_}d2w9Qus?rT6RUL6aoOtjNN6wnr5lHrhXgLiNm-4eFNiSEzFSru$ zf)1qjb~s})nlr&w1fWUZ*x$eB!2{T9s=q#1JkHTrD&x}Rfsl*eAsR-rl&h7(kHaX~oZVJ~rz)G4(3NQ6nMa5Bcl3k~o0D`@?)C44GN5^>fu&JZ zU_HEPA|`K$75)D#eUiH;7%_rYt~6kNoJ(!e@iZ&5;OC0pt?YYGSO9jhc*NPO(i>dU`KUA<{i~qqPLTKU&(XOibUGjIO zg>m+!B)jVVjRP%ZqQ{*#Os;@yT4!y)wKDuJ_UW@96HPGMr|G9ZgW@(nhZn!DObyfK zIEUdO2iKr{OAHWXJ)e2uiGwlOOSiy{mY18IIrUP%ZLz`wuU?IRQXTY;@W`62JA0)| zI!vr2_UpYQX}a`CZuP}^_zPkBIbqQzZ+}>nf+9`KmlN=C%~_^Z>Y@J{+Cw1ox*IMC z9%AAUoPVe{jINT*6|I;(WQ-SM)}l$}YStx>HsFB)Oz4;Aa(*89S@}hum1UNCNywH1 zW7ykW%Im8Y!YX1(zu>GcV8C3*6_>cC#ib4gDHT$GY%!1k-vzEzr(eR&4#349%XA+W zDT5^|_$zxpK(1G*t_?2ra#iSAY7i2jqR$98_Z|I?h?A370y zu$4DpWjFiV=tD>2eosDNbr{3Nj##EzQ zw9SMKhGN~c5y67snYFWJWEQGP^7|1h3;eh@C zeQ%}o3UP@Z41`cj!&YQ!(-kzO9QovH>Yn#3GI_gtFDR4LJG94|Z@^Ec%9z29=C-J2A z_kaN+c|(>Z1;@<+uBVo-8slg1m`+>Q#CdN0gG%XaPt7Bl5?^#uO8d!&SDY2rHAu74$BNUc1cUOk^js(?B=5{Cf@Y&ss4rVdqeOHlb5N@h| z+_~!VM8=)oT^iJWn=*YgnLsTAE*pGRV-PTKdh{rS$>pi z3{6f10k=~tuNF#3t@Q&|m#0p|Flyd$9A&-^GjcdC1u@*8J~tB$2+O{|a?soMQw6oS zpKWjLoVMkxq4Evhx?op{7-mnCO}e2)4CU&GssFx$(06TQ2Y~jkq5Zjbbim6S<*jQbv4U^-aTBO;1cT zRD&wb1XShuE;I$&La6(Sn>LT~$!$$zfTm0F^yRA)JabS^A{Pb?yXunVm=&7sDjLEs z={GJscQJR1t9Zq%suSNzet&5HyKDxvmX%3&-rLPeedl)Fj1HrT4!L(3gZcyyJ`*Xq zs8G8xfG>G^3Pl~r>2KO#b`Fol4M(|7w+bkmj`+XaP90o0%8HQT&sQ$V9Zu9HH-^vj zeB(Xedx10e^|ik9hRKGfR!$6TTRoK5*bTul?4|49CD;hb(ZVG^5!$Rbzi{+TY}NEP@#o~(hL6e@c{820SQr~!_sU;_(UJ|Aob&l83%4kY$0Jt#E1 zX^Fy^dFR9abvMB7B36&%;}%`T*J)n$8`^dp*JjC zD{b?YKw8G4S6o3bxyxox;08xY%MSUEmlP9~K;^^F9zLz`_7&`4&TJy=wPi`86~cb3 zLG;WV)=<1;{z1qX?Nj`a6Aqt$pFP@9kmd}K-X@M>pmS{z_v~g4dVI}+a*ep5Usn$N zyu1b@57%8jsPfyJ`CJhv7mv@jH$4a4CwB1T9*_FF<^YRUkl-RG@FAUY?eE5yW%Zzg zUv1>478iIZlR*H{Nk0meI=A1F1`YuOsC9MqiW?g#w)`m7HSuUyc;Vm=u*x@I&U6QM zka4}AD#c5pP*5)=xJvlY;Um@^fQFzXXPUC>jV~2j`lR{;T^*Wmd@Sd@8U0bzY~T5URBR}`ZkKbJ8Jx>N7^k^IIlNZ#@lR*Z;!kcr z(Co*pj2Y1SfzZveiiB+qTRJ^nk zYy~YWYX&V#jawt7BXStm#xFjH-&p(n{(fIZ$SlDwjeQ~xcgzmhJHlh&#vPEE*z#{! z9sf1ZE&us`@}#jV;Bn(}$A%-yl0hW^z48q!$yfnt!fUD&+?(AFR)5SM36F&AfTx!y2hF!Zeq&hHoj0EqY@L#l_0jws*lgbvI8JGo1UMz4VtAR z@hS2p3*4Nfz;BLrNEe;0dAgcxC?4}xdoY=^2?iXhvHIpHJS5h{~`u39T29*TdRm04Kci6{YEmCWM%yns>HQk?j&ihTC(K3$yW_%u9acC?l&^l*gaV9aFB#qs;$QakQM@ zLGs9o7FL55W|Ekk2!bAdFj_;nXy|HAW{Mt`sO0tVX0GqP5j(k#4Tf3Z953NV2a;_pnGMQCT^MMy`;lX)~^A@nfv zKiu{%Y}Vgs^Az$eKaC(dZge+Zv+|;s z=pC~8Klbq0v}8wGGN$96@Wzw#W)BMub&+(u^L7c%t@|kSdLQs~;e@%;+)*9LUSmM1 zTKtSu+2Ded@ffI4GJ0q9le04pZF0TLpMwTos{W4YdG$r_WWKExGX6Czt>;T5+x)>! z@v!fTYn1ZVUj=aIr2YTT3!wTvUkP-|b@&7n75S+x(&25?L8OnGKCV@~I9qG(ciix+ zdp8vPlOnW@@v|mG!}R{#ser&s9uWm)yvH-#G+Yyx?D`}n1q^b5ZWdtJ02$Xorl7WX z#z%&!=JeM=j;4nM+zP#Z93e@nC>;`ca?YXVbXg&dT@*aD^|(- zZKYse9?C2K=dSz_7v995O+Ln1k$u8%ga`1m^v6KSC^4Kqj3_IE7L)Tp40jV)*L14$ zmII&1MBH3L$exW4ip)CcVeEd%r`>To?y0tnbkYGI)?_z}{Bf$aet|%xC6<*Qr|G;e zh}!co(onSGzl0&4pZZ-SC65*|W<87dJP&}~1w~vQqCPz1dm#DZKhTq;4tZ^S6Q%z%V4aGMPwRTdUC;f(tn5i0Xern*7 zl^3-Jayo)@OO?(7rK&d;C^;JSgz+^4rQCXK)?q)9B`75xcG9%;`!)j=(^Bi6l5E*I zKa?aGoHLdj2%bPQQ|YHJ@_2z}@q8d&2De6IfWEO=V!Y~xz-?Yy;*hL4mzO!#l(QNA zjzCN_fMq32W_yOacOa%q=k@oC{IBoDh4)|C2oDrJE}df%;_Pk>F^-pf?S5y;IcPa7 zH8wmV)x2o-E||kfYdy&G1Lgmt>8;FSX8v$wQ?yjMv1ZHTI66r=M zrMnyH7Nr|u2uMqX0TW zr$jSCX!(rwOZ6=34U!qYM<=B6Qe# z$(6aVSy~i=D;7J=G77iL0^9m*Hj+!N^)Yk#%pbs4TuW~t#AsN|Cg-+**$WzgJF))b z+eLdWTSmxz?j-20>(nB*f5*_p6mB5Add9kPB+Gz*M)IHICCXBBDPWaY7scI8#t0<~ zhrtxt__7H9rnLsmm=x%?lKCII(6##%P=@kduzh~pd#iW7YpA^Ec7~I|?b8w}hx8@b zxAS>YWEB}mwa(qNSLk;R66$w}T98PT;qShXN!QAS=cu;wJqasqC!puc$myT@bme2( zf92?jvZSYnRvlR!OG&Pqp@j(NNE{<)HgaXn{F$LPRtxQCHkcTZ4*t_#L_;>l4P^6a zw2Ufz1GyRC*%GeU6RbDJj5&WNz0#FFHZ{+t#g-_2jNHZ(&q9|go^uxJn49SQU&e=F z{#^%u-{h<>ZeZxl4?T55LjyxcLqjs1M{yX#Br2lntEK@F!?blc#f#s8Pg;q8%9OAY zE3H=Qfrn$OAdK zRNN0PB3aNP(D&ibk_E7QKWbO(2T#`(P4vwDo8Gjr>EF{RpT5wK7io?wigrhTWJ)FZ zagupUyvu&!OH~n9RvNV7GMQPa!^sORzHU(@t?Bq8^X2lvh{CsErimgUn&MK)eNwPl zE(EG#?k1lEq!ibwDoslK^*tAG|8s=gypy#p^lU5f-E5oaX*YoUZnK{n6bgI};Jx!s z8u@z6kD{rKXaU3*(i)PODFPtd84wLwG=ejV^M-r9`|THI1IT~UuTg4rfmPP@ z01?z9{p2KM*wZl*vu5){5?D1D~x zfB-!6yq_QcY&4!@OA|J1)DtTju|TF9!G4~HN;gR*KaDet%RXSUFCScq*^g;w** zsa}u@wu<_KHAA)eBn-TVzi+#rkfC(1X!)+Y6cug4UaFOp-wrrmuoTaQd0^FA%x^gl zbiXcolRLPR@x65JS0)9=x=}J`JjqgT~r>8*wK$(!&TOQ_n^gE zr0)ngng%%)4IccNvDHheB?KeU0`lQ7PCb$_yo=uJ$;R5&V2`*F!QC~pe0!oeWPcEp}RI=|2Vk8?VVJtWsok z%jvD-!SRcr=LTRoV+>@&^J-Xjg!9SYYUe-*4(AQMh_FT! z`sVLpsDxF5?)Fsx{bt0)=*ulTdd|SpF~!+xs(#u;V|x&76yq7n?~@NI?>l^S#~H)%*#UJ20au-s(fLn{FGn zbpsQDb&bA5$4|*u3PD*S??y#Y(Ksu*7O1RdMI@J=H9}oqwWgt@w?g<#{8htJZAM5m z#m#(ZLr^{o%g4btlTrQ-l2Q4XIO#K_uh?}2)-2P16TCjped!K|h^$dmtz1;qv>8B0 za4L413!gx<)qRP_1UYZyyyua=%kAe@C0i=8my0UTM=KA3J_4TDuK{}~6r$4VT}UE* za`eQa^_*>9@uI|C&fAaQQ{H6^|NW+k8=*mGam%<|wU?PT?ZAWhd~m-N^*MDMc2`kk z&NBw?aD`qZBIyK}M%$;q5aT6P3hy;;ew3OZ6;EdDt7#PhKU>GFF`yd0qeP7v%B1D2 zQ3Z&(g;cqNV&4l0b2-P#Z3gwTcq9wbeG03BEGkqoaWW?GvtO(?&O7_25-=LGviot9 znn?m8by1??r^f!qHxElClL)~%=f5T7|2w1daad?-;9CEihU~cUkOLWPQY8vzGB6l3b;LA2FGu~P7#WOexK*Vtyv{o|m}ZYt zXiwM+3QL?pwadXO(7N2eDq>3TaLSD#H{<^u{G#~cmQu`RgwAT_I`e7tQ$+&@1#d|NW5fQ7#ufnd#E>=ZDL*wLmeVj@HPY*v}nEK zhVsKywGnI7w>V&P?Bx~-os87@DL83!L+(Y5T6e`y}`+iQY=?tFQ z_%)O7`3_|?@&`}}Ij@SOh_IMd1s~UgbB)Wm=N}3n4^|XnXkkj3Yj5Tq5{%3=HrUeM zeDn!mhz`T+le+CG+q}5Y3CR6d1r`*46WaRrBiJ9MVo;3wtyi>;KDUhRSbQ!oa zopwDRb+lJu;4-JBwC!OyT}`Kp%# zrRR1b^8ynTy6gg4hl?g3I~kAT6ZUh)nt8x~40JzNi)o}9BU{z4VdV&NwLgw&OFr!fHqfC;jnw2I)n#Xp^D}Vn-&2r-dtTV{g?@vh#3Oqpi)(apTX=Bu z47(^uK8CP7j4iwEdR%z9SA!%(Vy^iaVThKXe!(B%bCTVT5% z^I>9?rPep@=4ziG;yHbGq8selRSaJAd|R;C{*h~3zC-7C2tXAi#2Xa^&DJEsV2t|A z+MQ9s!TZA6bV+__aysN5!}dEfV&xt1xWqiBUocYKn7EB_`2zD}{t+tg;6Nnlg4Q^y zzGhM~WaDCSWVbZ6??_OfphS@~dX$E>gVKr}TcNfo8BHUH9+81h4(j%kfX{mbWpw~AK3 z0>9*CT@lY+3b|ZL?7C07a&AVw2NdgWH}vtDWdD?Ugf%rVhMlAmn3S7i6c(K&FA5e9 zsJ>085tziO`c`LG^1ikHcKwPMOHF(*x0T0Lm#JbZt>X2<^y}E5w%A-y81}I`lBvML z^1Bh*(O0NT{_>`oIq*oHSj@KLrCgm$=f7&wLT)yeJ`Quz7XPOQ(Xu(hfOIPB)eEcA zk>lW>vO#$|HhrH)|1YhoGLG0QJ6n zF0sr~aYW^aHkrx!FG1Oe^6_~C7ae9X#8s~@t$}|lc$E#Q-H9ys!l73>AeQ}iO})&j zI~>xC6~dI}z?%_U(M8gTp%;jw^~q%8lRC6c-fa&P>>8A6R>zTwFC_!9d0`_Tb=a-+ zWnvt6^|yUS_7%E_DN!}*ey3h2GRj`fD z`4}yTb1}|$dU|7i4+maq1nwnn*6gHZU(Q*jplro`=y+5(fud`kL)Te3O|}m`tXC;# zr7XG7uI7i+Vi+@NxbfjkxwZ58fg&cP~!yF3nU$-Jd(0Du4w6> zdRH(YhhF}$yG}47J`jF-ynblPI08OdyOCb&Z{CfRa43CpbP zwc6_a#X9mA$sGVc01^-9mq1+c8%HCr*6r?L^I&2*1HSC@-p{+TlK9qAN(OwB`N$ih z;Px%>3S2f?ukE$78MPdB)vvt@j(l~$JYzNX5Ybg9Gd8Q6bX5k@<%yE(p5K3)!@=P_ zSD-oys~%kuGvc^?r~^wa%e$h3IyC)@6(&f{kqQY$e%-bIcoqK6I&J8S@+78@ZRi!G zAMSvJ1MWl2ea_8ZQ|RxxI2$EVnmK_@NUYqG@P+m!uYEBf##z#QWkg%#19AUY{`6)@ zUHWf+nH(nb<%FnDxlIa{Y)pUxK2E3^fOn6`>86r6B9_uv|%pn9K#bWa8JT^>q7-Yf77$WXJ1GO~eU~VK|3w@oul=9cV|i z=Yn%8$jYS?xqh)(BmV9wa#s{sl#5Vr1UP?!lwRi}8~k(3U383wO&iBo4j}WQhZANS z-CM$bC)Xavxp9t|vcNKc7EI21fwk$&F}92R@t}Kobev;P@N9MEK*JIxDvQ|EnpM-K znrV(g(Y4em-)t7~zRqGG3ETLc)HC{FGLfS zv?<#^=oWW5?@@&`itJgHTq7pMc5c0Cb|dz3l*^BGb(oZvFahKXEI;1H&rK3+EfP|I z{zZJ|mSNSYwlKbW?M`b79X)rljJp@c_K2`FQq8+G%PT}3JNdh>Zso?Uy>8+u`2j}j z(&u(2K&dz0dAGGw z)yt;Dy4ajO@|c&srsOhk8Ws~hl7Kho4O^Aem6i&4l|_U z6;^*X(w1bnrUxflyCkOWcW(c13fE2_InQrZ#U>Q;Oz~Q4M`(wWgYoI5pC*gYRDagQ za_y>s37*aN7*q;j}*`LoEfns3VI+6y-*NG^@+VVw)1K7_wYhfs-wRh|Pz(yH7 zFj2iD(Q}Wkauw0`>s8ZF)-D^!DFM#F2ZEVqwc@UDf8qJ6B0pNR+9tzi2bKwORKZAz z;Th-pq16t4V^xP9>-Y=__?Iy00#)CroP!SRzb(5==R;DQA#mzlw&fCkmZQ-Sb^wWl zz~=**RC(q26GIbtSsE7X%Rm6K{qwg4k6&qkb(odm)@g1!ljSFD+h$Fugxh*u1mmC`bIZX$v&CVBU!6cd;t-bCt|s0~XV6}12@wwzL{HL|IU zssPOH)fcYTSKqbL#NISXpzGfpmoi4M6=KNf(G9k4gvgypD{}&xpiExSN06cw40Jy5 zkc`0sp<*#&$Te9Um-zC@ZyWh89RLf{MadiBGH-s8O`a-1F5?u&pcj=?>n)4LT>J6TCh`dd^N zd+%Ly16b<_eV32OM#m8^6lz2i`iy{o1x7_rJ|o@DBn@P(4@~seLh2#%8OR6FOH?0R zYOyt(q)fwe&F$SHIC8=fQ=jY+q7$ZF*+(#*Ox}8_f@v=D*P*+Cv0w$6IB5c@mc7jD zIg?FD6U9=!6yYj#uR+j6MFT}SSl`@^xX4xo&{lMtD@?=G^y5{fO8ZxMr70>gUe_@w zf|h{&wqSzx-=QhhnNaU*u5i9Lf$9noY46jry_bsojaMO~`J_kcq!_a3ja&(WZi40M zS^n-=`=Qsf;UDUIor=S9ms7Z#&-hCPqpR1>)*gwMnnQ<4?mfD7y%cMM=8tQL*H(M; zpP*M|fJgi8N z1HZigH$pd9xyVB(L~nvUOZXmIy@_tQv>QDVCSZb+a1+E1sPm;sPy_Tk^Ym#RD}WKz z!ue?cSq&Sv)+#m{e zq-ut~U@dr9Al;|T!v5Q^RMB_9JU_c7qSRfrd^v@Kg0&XWckF)!&sYt5rJ!Z4`>NmR zD#W*G6ekW4|I=z;C2teQMQCgMyRF4$spS)<_nW!uMc$RUo(jlHy6Ynd!S;zOaQjeu zdwFkpn3sg4=MwZo5~=>o{Z7Bpphf;MgDN;)QTyBJL0#Yc zDoB}$Slw>(H#CWg(#TFa=Q+m}OT5yU#5L?yHBYeJuu8q#Q%HZBM_=!GI*J#FmiUH1 zcyZYk3a?h)zD4_vvz0FnOna;F?F0xwyy2aTuREhG0uuk`Ck77AzA#9w?aBDJOYo4+&p6U{>-`R-99yZVeZp)p{%i*FaZ z4(f8`i*5+A76ajd7L6L4GSj z_&OTZG6Ys$zhgX8x9>eg@M4oa{?3=kWk@#9x9>mh0lN#rq$v~9)eq8u|fCno0f^>3kE7% z8?VnfWXQT{?Qv^~5j8+@@@u73w(EG8W@tUkOyl>@hQ6my+-O4JO2@meli9aDCqIp{ zngl(#ds_5X+e1HUKrsT?`5aI@+U!q-FGM2^25_pXQ`0Zqiue3Ot*u0>PL)MS-*>4# zBdV_AKripA2(V;?kEOp1KDmikPu}j8HWClgVX{?*bcl5rGeRz($Un0wec#a-{8*?y z{2!bTOk=PpxS;Us5g9YNg$ht?4G4f>WqNr9POsTm3w|p_R0VpefRzTBxFMVd>El2` z5bo&SI~wwZ>Yn02o$Xrcky91UCixGYg7`0Fi`piZa8il#!ayvpT7dTn1RhtPoGiUk z35Bo*3QGA~v-z|9e3TU+Q#*;vPN^@b?B|tz9~Oq0_7sC#gxplh*otp`_)KCZuyCUD z#Qu=(;7aI5u~D9M{e9eUYU6ssvdshXgM>Qu*f-j)xcyRPo;Z8#Hy0v>i%)J-=VcK$ zRI^o@B}kMMg{2+PMlM_GLYC}m*QIqse(7%2ucxdDo77+b4wA9ge!rzBlBeIzTcEJa zne~+JJ9JL47+%A`iyj{8xNi$5OsUF#g4jb%`cLwk=88m=b7HZ|PN&~5jHii($)t9+ z3}-3UH;zE);r0e7G=3*`zw>HH^!KT7M)A+qgPPP5{-*`F2N%(=@0PM5e5;I8V=|ol zY0e~5{hRL31z7qFrh)%;jH8Tah}A0tWOXio@%fDH?0S5_MJ4z)-k4!(_wgAN-#jhE zbKPG)LVNzkH9f74U09i@txkE2)KkUs->dRdeUXZP?JbaQ`8z6v)N;e6(1gGJeEO5` zgd&;T@=Lz6M?r#l7gOFDm$(!{9>ELQ{!iA`*W6-gf8W1qN}*tm=}$vRB4+(Z)xe?Z zlCw~+6~M&p5Q4T>*W6gdLFXDui=oIWT#+cLN-g=OuFCJi-Y(D6`~&1!l#=0pYL)ZQ ztB)iue9lCQ$>-u50he+uW$*Sj3{Iy3!)`aDkaK^*YZvs~sQ2V)NB@;D9^8B)JeoU= zQ?-zCR|~TUbbp)Rg$LbzZ6}|vxbTSSnYlTA^fHw}R9dI|lpVJBW=;6ny0mDOrH#AW zTH+klW`YzRi%HsStpnZ?!~r{kf(@u9mGl8g&YmVu!X2RLk#52JR(6!~ftkjI#SAZ2suHnJ>`j#C(91}cep&0qr#?dVN-W!B zy8e8zF4Z0Ch_Yz zE!)>$&nsgqgP!dR#_v`&whX@P{T?y@DXLGThO`fb zezOdXJxY=oMnB98=rZ^Z((5e=z&C@T_a4F!c=kD{0O>JFSg*6CUJILP3uucMGzo$J z$*alcdrw=to%CRx5;nbA3p4w|OU8hO_@=~xWZ4}Dy2*U*!H zNc_@&=fCZ=cYTxLVp~!Q0kBrC_zwuH(Thz7C!|}vcG>^2UWRc3EB0EN2i`4N(>}Va z7pw#VR@7+FCMJ5%7`t1afjhM_7jw*X?H%nkF*QGZqRo8SnTf7fS!}?;n{yOP|{Z8pB33v1@;h1C5%U z(<#~E++#XkS-6!!ApqdzzMFONS?uo+H!xUllDyAB4bTvK4t;(xVE5IS-{5&XyhLi2vJ{i?U)$?AM0mRh)1f3NbnE3-8OI2$^xIESR1Ml}!J zJ@o%<%B^XXC6m4Cc{7sL(?siU)Zp~ZRY;BReH_(zxwbeDn%R{{BU3tclAyD;AiJlT ziTq)oCTZJ4uXoG?g+5?>v~XbNs?x0Qo!YQf<)D$P?T{@h`^Ia2iW23RVzLUkLlp(G zH?0(N!wWQ!+*UekHe8dUyzMnjWTiM#``Ntez%Zgz9Ddx1T)qakT=lY?2mBjj>Mk63 z#`j#RTOLAhF? z@0tpYgJ{gs=wYdiun74W4OHd9VEc)>iJd00j+?Gu+>QJh9Wfu~)IIyuC9E=sEP2z6Zz|0J=&+lCTV|C>r25Wh|Y5oSgIH zk+C2RqN_Kx_%nE^`SPwFtAw9L8)1j zFq)Mk4Rf|8!ze3FRTW?!AFi^aA;5s_c2`R0+9!nWUa1Ptea@A;==)3t+oU9aEaYgR z=T(FQQ0DH3VAo^5G`{N>g<{HoG<8#%A6hN@h1YlYoDlvxD1dYuT0&179hARrC?4r? z+a+lC+qh<(aG0dJAURw`9U_6{0kuleL>iDjyXh9jez)aNCq_KeO1-}uk?i-xa4LL{ z?Fzq4G%z`W^U9XYtWOo(j9s&B44Jd}2I`l9{0b!drmay9lde9~xDuSg5)u|i+ELN> z2%Z?JsqBT@q#!s7=kAX0zI)^=x1OcZ>0DyVU2KGDR#x>7KqydF#MBdh4F32|66fMx zJc7`;kUwZmYHY??cT(brXjpJ!KR-RjB;i<+&pGdHJMVQH(Uy6<9=TY%3|Te*A&QpW z-Ybq21Ora*wHw-cyRZH~67`-T@)pdZ^kT*Wp_qoN_pvKwvVpbH%su+3f$nOAxzWLd zpAR_+0%402_k)yTl5p#14p>>;Gxce{(0TQ_g|X0G0{i3yPV zg^qigo0}oHSgKw4BMfrze;oqp~{w zWKXK4$+w%*3!LkDU#~5|-fr=>o9bqLALV^wQg=nj9;}lp?4+~_Z}Cpv{*bj$rQ;*{ zEL`NTS|~ukC%}}Kn}MYwbB805>#A`I$sX&X^+#Z;#i=qh^MCzy3X2JDgvt%-FQ80L*KC@I$W44hl~(L zwU;QbX1N_1ydR92PP*CCwmMvY?S>$ez+lj{s}GvHnNeb`CA*P;diY5xA&1=1d!v4p?VqjgDV%qCBjpVLDzO zo*d&Pwi4$%LK`qGYkWU%2S&_ElU;X*eGRylLg_puka_%c>xN9agM1*s|MOsfIHK)8 zA@1KW7=i9IK`Dd0o@u%1`8?n2c&j~)gdG$;-+;Lj`d_}M%0VS}|EV9^nE9;KFQ?_2 zTukfbChg15J}+Wz)I-qQ!VPk~-c1GRMOJ1N?whCI22H=lrj)yOP$1 znf0Q?1`10A+@2)mGrd@4nV1f zqfUa4X6LOzFewn;nM~m7g{K-|af9>d0bI1JESVG{4ifWb9pykB{U?jhJYfW;M-gA! zj0nT{GOrqP8&rbjWy?8ZU?Wg2Xy^cNts0?pHPq#Rx&J6Ua|&$j@K^T!xcm?8wSeVC zW|~5^=bV38K_4M0(s6fCy{M-aSCu##jYozQD06k_o5zigX;Ht(#B&{oE4^jg-52)! zt?w3L(%e8WTdjtuTf=ZGI2W1`v5|xrME0QXlp=VId}K9Izp5vrc3(qA(k7^{g^LAf!VMkCAQI*9PY+?T28|QUrXL zGo38Xuaa~1#;St=unOm;g0@NL4(XO~q)_-*LVCP>ac-AiZp`gj&XoL&5;i57{;ekg zQO)gUdqtX6Mfz?Fbb%wr#KEDRRiJR_vP^U6ZogwCtA&n>st1jXF<})sutInb5eDG0K(VMY5Ue=#8Q8(71UU`1A z$|he^-(r=#Z)|=uvi{*vEIH22`!5Hvxsg_;f!gN#HjS5)GY^Yo^Z0)|2|RE2j4^sp zKRDTzmd|Breb4{MOYJp~P^^hh`CTV?U#c1}rhlUQ6$Mk*TS`4j#bt@H*YhJ3Uk`<+ zQt+*ux?*b@hxffFcg2%sKRp}H-PZ9XvoffTc;rsr1o!sDQcfn!^)~);OW%Lg2siNH zZ4&tLhNKX2jh~$VhNTZxdgs|cvHrB)iohUik%?|Ch!(U4OXQh-#^KQ%C`sg+*mZYZ zMw~$9Hvl}t;QyVSt>`svqi$cRRm1n*U_TgnI>6)$Y8bo{iF-=pDcX4aS|>l=qP?YP z6kpWqMe9N&w22d$p*w|q^58yiHz~IId3$jFI zyUyMHc}e=XaT#o(`>rAgm7yQXH`#-FYAp2oo$dz`=D*Yyo(j!#2L^V_*Z+(#L<&X9 zXslUqt{4X6a<|B*Q2H-6kFZ1MoVloYLKm$d=qI-YE#B8#F$xCtZRl!xfV9Saux)>8 zvUDQ-2N*Bvfhg*-CwK{->H3H~NJ9QM(b-nS)-Xz25SC9nV{e0H*h^&W8M{wzE*%9s z@}CA_@$4s>U`*Y-Xzz3DxMI+u?7DC0@lqT4}UnpDiQZOakiOf(Q7IFmkeTv|A z06=XZ_gx=fNcbbHiB+odyRdw%9hH1up47#rTq6b?z73HT+VKy!tXxjm{6Rha2U-*? zVc4#D*48HVcMS};-v?&i@`>FG5>k1tDM|`j;SqsT`H@xTC0&S$r40|4ge;$qpL{sa z*&fSulQb3SMB@z=3)nOZT9vO_hjzYmrygK&rVmPb<58E7k*c|65>@n7`MlRn+~H*K z``nj=Yg`%PsUI8u=7~3F_VS1J>G`9Z%GxTuq*vt^S%~Qj{wK_U+U*xGDw$i@CeRPF zst*{O7$pBHkHK8UP#Wd@1<)B2^_vAhLF~LYgg)srsnqJHuMc-Tu*O9&v$x9ma3?qh z`P95AFTV;`VcZ7Cmgap`?FTRQ9yYb4^*qfOA)h0seAzW3CFhPLS9km{KW{0POfR}F z$mH2+hd{vm!Q~%4?RjT1vZo$S4pKvlo5f}oeac)lm-Md%rdoEbR-pXlxhe9+;dC!| zi4VM9b*$@e-eWn6l&oVw!s6u`UtwkB99-vgRQ~xwNKljuYn?NBWN&s*1sIWhfl_8S40uOpuyJpxWNnGCET zLN5+Yi9d&a%RO&9hqy;AsKz!Xzga&{GpFr@9$M>;ALH$L-`h`~9qUDS>&SUg_A#hG zc8I#<3grQuBf69yOy-iutlkVe`k-h4`A~UKQzsGaahDl@9!|A7@3c=dVgW zp6sb5uShW!R^TMC8DyYalP8I9Qpw@pn9b7IoX?B4ez&Pf%qO45LQU>ntEN=psI*vj z3e60ViZ+Z|A@~*0gmyw9*SS9~XTZ3PwO(()IdtBsu##Pt^FUl^em+dyw~jetSxD-> zmwtq`x3lrW+^jb$Q}*$$ZqUSltf>50wS4nUz2Ji-FfIsfiy8$o$1C@*O~nAkxIpi| zALkjLUj%6QHqU`WkBEJEU`8WgBr%}c30aDx@ATKZdo<#|Eapw4^8B@Vu*J z2JxW>eWako_%Y?vUC}UTS77dU^`|u#SI!yQ$NFSi%`>Av&qEURi6m6al|$U#M}k`V zL7y3BIXvEKZH*#PW$d_5sfH@0gBpJ&^>&2>#DP^k1Wki)+%i%{tPX@sl?ZFlHSaNV zk&E7!r_G9j@XY-{pZ*~6GZh`3bC@3Q_QN{v4VJthJU|;c^@RMBgp{D|(xCMEmj0hi zCd8|L&{>gL8=j5!yN=1zg!@G;Hy7yTN8G(8zVy{5F!i@ahE!|OEyvtmJlVw*Qj#(l zAI+Ztwa@2$HpQ>vbXJQ`p5S#(|ei&h*onN(774YiTK}~+XGJkEWFX!)~0rG z)!~J$UM%u-#;i+C3O=-~FoFT;i%qW1NiJYlo4ri`%?EME3VIxt642HUCddZ&jG8ye z*^u)ioGzxviqtOLd#mTenJ`t9$2-r?C>|&_$Uq54NJTm~bkdf4N943>Y8>e|=uI zJY-OOY(ome&KJ5qzm%2Tms(BesELIlnMg{1hbo7y%sQ|g;-t{WB6ih<;mdi~U{lUQ zTASihWgxOJHBrh05W;Z&tv~cUnj#&EtXPlQ_{ogkL_`Wazewf{e7vB=H};+-B@cdn z;%Mqe|fAG8mY<2j31ri1&mYyBW_;TV(FA zF8XH#520I1cVPlN?t0M5cbR6>gykP^xMK*ps)!KE8B{zdZq-}k)f zL~^rOZ+nJkz@lX-ruk^RlKAfSm4#Dmj*fMn0?HWmQY7BJTpEMTO>~Z0D-<}ke_buV zTEGibUY@=37wS*LO=_Ih@n_LgPQN|#7$wbGeqQXq4(Y~R>jod#5e@|)0(8@LI`{Aj z!TTTs)6)TDgoQ6`aSEAx4kE*dD@TOCTOttCy7>Rg6pz@)vMGyrOupHLA1uQ2I~s!R zb{I<&{U%nT-%Lj}`{XQcz@Yva)woKGBFmHx**8zmS6M8Q^OcxlR=aI)|O zr&=r`j!z7jn6Or8;0uPgr1JOcI4{G3vlta@Npd64Sx>IDKD_)A_}M`(Of!fA-!u6G zsIZCKm=YqH-23*gb>IaCKY*+}j*447tLYfQ$q|OZjAgW0{@%T%c&Kl!x9!h}eJ)qn zH1+Sbm_oj=QslsFzGP{)VE-8GrLSF=Fa6|#UGadh&b-X1_d|BJQpC{ASFK6opdHM{ z-zKz;-nqrt!`rGTP<00fnF38umeAc`j{q)CPxq0TE^`&VA33?*@9&CHh=`6N6?WHz zQOXUlb)rD9O0~#LSwo$gGq2+f##b(-7o4Vo(rwXhqCIh$miHe5@wDX@?X*D|ILuwZ zgIJpr1GV|>8#fdn-U6U~hHmKP)_*${8?mv=aBv&4VKdHSd-ErwoR@c7GH(c+zJ0eh zKWq7hClG-Qh^>`y`R<53Uq|MDK13?um=V7Mn%1(+KD#MKdwTpl79pHozcMWnSriPN zb`zOP&`{?N<7+RM0Q#9qz2(JD`tkBTy3+4rH;IojPHi^Vp8JpCdLG_kOpseVoD0E+ zZugVAmCkg~VMMb&rjW6B^Qvx>MeQp9hdz6xpMXmlGX6FnTqJ=ole4SKe{vcD(}Qo> zym_F{{Q2oaA+jVF5m1OUq6dzW0SIhV0{;Wu$s~T4X#>N4xYRYjQrq9Oj#=KWYOF2i zn1}b&P`sE3;Y!~MqQs%7r_H<(t70{9RQ$0j1tDzmEQ2ydI8eAi~U*rBS z#^rPH50{!o{tJAT9){g`%$JL)n8X3*Nrk0%ATzAsCO%FD(O&oHrTbz!(|>DzXGt5_ z>Cya*axEBG2JAH1?uHd>+boV3ul!z)F)pgSkI8>9%+NM=#Md+A8D#axUob44Y1epi z&_)b;NBt(FUukSFDCm~{_uJPEi@h}Ew>E}dWMTkdmxfUt@SI4G;5zvtnCR{J#l7e^ z<2xtlYe@VdO4q$^|CM&ZHlT-gD=z-IeLJT6Abo!Vh>c%uLoAScfkFu?Oaty0nedMp zjRE@ckl;qhI{q_zKyB~6 zE6=*rehh#QlaQW=xoz#It1HX2aBdXQC#PEEe*Dl7TM9d@_JB|0z{QAeAz7ms#X|kM z#SCMp&6=YxVJhm@D~EFZxCfVNoxzdd(-wGyQlVv1{f+;Slb+wo$Hcq^f7q01UmiS# z_DAK-%5;uUk$XEq+%~&oO!f0PuPT(;kfk-)?BshXHiAlH^t&=66bw0=jz=LsV_z_M zSK>E=v`s?#vpe(?J85sR72LZNCGzS4{^cz9<|Ccvk4M~n&r2^u28h!3f+D32#U*=z_Gjz@R*Wox*)y=el}g}nJD8fw{gl0wu@8d#o6 z{uHBORV(zWAI9Tz!j#vThp#-P z1l$icn=~$6M!<$Tf%zV`Z#kW~zdOOuJ#Ae-Y%Ksb4r@BCn>6Cx`Ijb;_>SY}gH3TkMH_Iufj-nHNMA`H^IS8P!*v;eB) zgtppiSaH8F@vWbU_;CX~JC*dcsUvEXSA!I7v0^A@bA0tj3-$;jOz6X2BZxpr-=JJe z%p`ISG!mh83;d$6hq1C*o#UlkF)jZ=-*mg~)3}$Yb;| zGBKK$&9ZJ?1{`bH0*t9qv`jbfT#q3fS8}QaFDOv>@w6NXo3a^wF`+=F zb~{HpWc#b3CB?O`sbfTa$buS|c1Cf1>xCPDndkAhv{L=LCb8k2JM*Ja5S@@cj zJ&n^(^NMVCwS+=F;-Bk35}xpg|CB?{UMAvydpWf-3|qB20me_AXFS3?crM=CpAd5b%d;J;j_vm z%jQF?Rrx0O={7!a>9+a=#@TRAfF5?pDVuDt7`#`=&A-{N0_a_ zz?qXsi~GBJG10S4U5q^E?dK6oEU9W73S@Zo$`g^^SUY#|V^6G?N_vq$`%#wKfKYn+ za>rdTP+gwS9Yg!bTittLkfHc>diJqKh}WjO=OK>6B&^|Ds7YTbBmWJv>D7)fBy-G< zlFyXS4ke<}t7o{#vETl?^)I3p(iS@*S=`UMxzT@$90A4Z!o@zGS75i}HU9kA{ z{y+=J(|nTL&dh%g??7hz%Y=Du<|RRJsw7?n!TspvtA+PN@&r03Shd#-!V;2jOimy( z3&o&0_Uy{X=RqB&^iSc(qkQDV6!O^kSV2n^iQE+pBex2IYUeD1ePOMY17PAZ{iOTG z!Tc6W17h&)trgkITq(z8u7+~7Ma?zxAW@72FWO!5a%r2#u%CQ%Pa~&jSdB@ z(>^S6@pdd{*AO=&qjtA+YbcwtTj#NwBWnRi=J#t4m5pp1(MX|^x0=MR&;~bC!R_Rc zf!|})yVD?m&V5@I{ExT8^TUPI7Mr3U5&;v&dC*o1sS?iSB4`M-n{q+$N)l0&*=eYZ3YE}WNHIC8 z=9fdQ4}4Ie9Q$K-LEc1}WnTv=c6_XXnpr724duj`b6-wDdfCs-gMw%u-#gNrw-$XK z-4FNNnR#y{zxlf2~|QtLAsF+0qJIFkX9NIP^25A zYba?@=?0N*NdXx;grU2;yJMI+^ZP&NId9H&&HH(?_WrJYulxQi54uxT9ikKh@El61 zB$}efvB^!rXYt8bD1i0dBZ|}engmD?>@&N5e(*yE!@$<17;G6LgbbAON}wRsRHT`p z2MG?Sd~+`y@t?xPu1Bs(oi*E@LK$u8Tgy18#P*9qVC+ts%#Zx1i|$1HXH~wmN%SK5 zA7_=T0FuooS5ijfRGXE%)b5X^YqK$mCC00Q9nBQJ(K=o7Aa0IIvr`h23U2~R!%@D2 z?}Y(@pIM5wN5xovMW7Fpe~d$w1MerwPH57prl%X@Gjfvr&WP#Sq=$#pinRUw6w2&e z<2}hn@UF&9#X?o8-$uFr2Na{5P)F>rKmfGu^F4UZan7U9@8naW1hnj;O7Isxj%b?5 zZtO)Fv3FP*OozQ&Y^py%ned<9*WNk14C)`4%06IWvfaEnncJpI{Ng7mbB>@^%M@(m z>d2(e#e#thQQ|~w+5ANb6j#5T-n>&#Y`nM#{TY*z4B*m~+7)MpzK|uWm=>(y3N6V+ zmqAw4$q6n8CrUcp)n?WAC{Nz}UOeFXcq;6Lh2Uo@^Kv=>{iUj`0Z>op3$@FCrF|Gru|UaN-H zgO(odKxWG(wc_+CA)8lg`?#d1oxCsj;}jF6FPQa*gkl^T{N8=f_o0AiB&s&NK1ca8 zqA^T;a@Xi%;r_1HGjSOD{!J}6+SI%oJl}WQ`)sV=HvXKj!}f)YdsZd>%qNR5QM(>! zf0+4GiRUZ1kY}F0jTVtul6raZy9$Kb!baW7*1ftmoD2|Jwgve?i+JM&^ZxUI*rDq( z*A}`!_Sj)~@(nBAwJ7ZJyi1LmbT(CJJqxf5Rxt2cRc!sTIKl#73sT?kZBY9uGds2P zV~;V>&R>IxW23IhS4fRhNZlvA@ET>d)7yaiV4T2=%Lm^={But$?|RAemI~nOiV~M} zrTJ_D$wu$@kJ7aNj;w0G9j5}O&rKPS8xvJjn7qFx*oDOng+*+z`%spJ`9}LEJDQGn z;&}$3k*dN(dT>=PfTO@%V;05wZzsQHmR&s~)WsdP;zn?=QQDMIE;ELy)Ob3KW~Q|_ z;y`Y)ueRka&8@z=VP&|2DP;|C-XiXWeI$1CM7VT$=%^+ZW$$0@xOe(~{+SEHRbdE$aq z41v?Z#)Em-FyvI_j$N@U80NwqggoK?&toDw7VGkCefbCxszG=naoFpGgf$P; zT=_Y{x@e>lPcEy7=%7ce-w*RxV-^D1M@>~VfyB!BNg@0TixHSi1?l{-J2CE0{XxzaJ~Iip#O5GqbpF&oV5w-S`O3hxK2?ox zpzz~C%oKc-w9@t($wh#Z=>@bu&xVO$6iL7xx)H`@b8sGx`?@{2HQiqp7@xW~%*Fs< zHFdG(17KYLvDxH>&~7;Yua*c~xjxI~AMrkMv??_dA+*+BMj5PeQl1?gNDR@WCd<)l z##zs^X#IXaf4$L?%EygjL@{hv6{KZ(Q)b`1-Q?~ORJaz?WzSS2ICWp;{l(NhV*(Rg z)ZAuKrO?Ry675L#q?mTq$84fLKUs3{P#bq~*XY#!w@Z$rbZ2?hp(TUSB*=|&(d2Y3+d8GKE!HMCXOSp`SioXpENnzcHhP4ej`)l2fg62uuI2&cooH#oVi;$O{0)-SOX*{H8=SOv| z;=v;f$pBd$>fX_k4w2p2B#zm4DF8Zxt}Sgq-ow0j$IUK!@<_ZMaS8x`UQ!g!x35Cz zvr(6ar3NSpSh;SC=vFP)s-3+nLMwF|831UhfzLktmw=)4&*%znz>ktde1O?0Z5Vc3 zUWx5@;5_epR~tq;=%buNZ^qp6=nxkf!B=oX$zdK2k=co4Ku0WJi18<@3PG}{hK0W? z@9E=y_0Y|-Qn&q1(N{5l6=l189T)U71Xng^CaQhsP$k@C)o3{60dg4Rq=rh0Ge;m4qH60IH)er7)|rIUw1J)m#RLw-$51X@i^u`mWgWL z&m`UKM%&~D(DUU50xdT3ybvoW`LB#bXb~@;NAVFMBvq2gF3Bu>)KEXfpu+l3#4FqP zo9*?ZYsOy%CR40_GJbIxH%M*!y(3mKl)BRHmhFAPSKlB))p>Aj@2=u93;;Q4{Bd*| z_y`Rqu+ccLkK2M|ZZ=uq6aJ`1xmGd=pcp|<*Kti&l^-yhl^dxEBZ9_?zf^Vap^R1= z!zSRJ6IQyWtprX2X7EsnFg|p8ghI2*>N@Nqx6c+Z!5g(RlwPo-GHoA|7!3#|R{E^f9qfiD%SE^Hl`eeXm3$s6qgoZSB_P9#ZzHLNqs3=$}cQk-iV=B&F>HGK-TPm zcWY1t`Z#9H(%DI!LRYoWUXuH4vW|^WCdt1s&=f7AKoWj+1xHeZ93S*E8rSG0AxOG9`B*XWoI2<0#a59Fs;wzXi%`^}>o-X48aSi# zDIcEvnYPm%XsvK!$V?ko(|P|psy@NB++J*zzEWf2N)RT0VW6c4sMV3B{vl_{JU#IT z=!~_cxzuQCI-p2&F2OGXE+-6?W`2!z622~1TGD#IaET$s8lpU*DeiMVD}laC+&-td zC)UKKN(;_=dikO*51khv1k8pMbWRm|`7MobYb+Y*(Uu4p} zTLXgZ&gykip=)|B*3(z#HOB8z8gPxSgbTVkIrs1@X=XM zT{a-!1Vdy^nH{mFELQirqdLFM=O8C*_ujh8A3M9)Bnd9UaShW3l&2{eXQR}p0oq;y zTKI*${|m z3q~{d(JEXmDEHoSu&rD{} zT$E!1Kr3KlF0(R-r>E8A|EmxpQ-OSlMQ7(W&W4XW^PUO152B3m0E&h{_O~~s3 zWSsEcZ#qy{hC7~qeo4?buM;uIrk*<)LeKWxoEbb%bQj^R;Ca2OzCz?+FQ@k2Zq*P zn2(dHd8BjqmMrXKofh!w--HbwMHCd~_)fnG8ny|KI|wZnb2-{pt8{#6@qDymK~D2| zl{)6UeKHYu8*Lwbk)Oy`=-(V0t(@@qSiRu7N1hb&vTExLVf9mjFf7S0Y@9;*wpvdw zCX<3uJ7X+$fTeTj`Xx;`7B+jEj^EKt&mo7ZvS*n{oxq=pzV90J|Iw+^#NkglNGM@T z5CYnIrEP{7>qRO~%QNp=I?SeFlU^>+jXb^j{}SV$i>i|v>Fq?~WXu{SOo)HK_z^PU zx@b*s^g{Z0O~p^-xP?aA`Nr}#`+#$eKDomeGJo;vWXHw>W^BByMHV+|&KbONFgJ_g ze=20Vn}uFZ{2@jbwd*x@8pD!x^dBb~f|8?%TM=b)e5DF27vkSQ9gAR^7BTz3p1>lB zJV!(L$uFGi#1r}+ZJCET09N-xu>0fQ99+00c;;fw^j`&rh{j*H=;q{$6%V5BQA7ipMfclM<`k5T|_-~A^<$oou z^X`D`+&Rkb5a5b^=cR*VO%G86Bh}FLXn;?@gNbDlIyk(6z}vZ$|3b|MA-+{9@Fo1W z;lKus^jHHYuPcqzb~tx>#AlyM>4gJEzf4D-^|9sDetpiIG28>NPeM=m5Gvygjo0_U zhL>L)_n#JI@4qRSgM|ypz~OGF9Jyk_39GUiuFp#wQX0iRqF+=D7sM6mUh&|@;5$mbPHd;fxn{wPa`ztsLdDOEbJvz z|BZ$dz2 z84W`;^jg^COMdTw0f?TRV#NE#mk+(ehr!G>?Pv{T=Yy;;uguXL;mBsuhP zS*`rI(I}?WI^NX=wv8-@23w>3yo+lYK0 z9pWT&3)J@LqJBPh%SqXB#aAN!KfypEYU=q@tOj$Zpo5XMaAd_+Nc_raD*KRzu}Bzr zyS5+GE^&AkNOZ4U=VGG%Slui0!coq;$TN`)-yk>8dz^j8?a3(~Zx;KNp^+)v`fNe2 zYUA7Dk=1tr|Gn6-qJq0PF~I4>cZfPh)G#qxNG`g)$c2SdLdjPB`1Q(>u+O*6%8MkC z+MST%fcma1==GAsp%YeE7{z<-xK+70K4pCYE4(qG823Df!-_kaCLe|tqq>x%%k%k% z`p*v&>4|7xRE){A^$tBxyA+GBgit$a$3{iv)8FP|CmmRA4`nH2+IBzSeI2p2_7SM` z&9#fWEssBcyLhhoV!56l>dQyvokG>ul9XqG*XEkcYs9hr=>9>^TDp)ci%c}9y5^Xdq%z#^6oBLhw+nE=#D9QG_XK{@M&Bv~Y zm6~-;>Ghow#L*!M0FWRYLGKRw0<6XjMZ1!Yd@?*#1eNaV99uA^+JM+N?#<8D=FiDwU)s3 z(v6Ll%5CT@22hW)svdTq*RmDgw#9#aFtLpz*;+~Duno5nYt8TQ(u>ihQ_Q>q$lq5CC_iEy z^;YJ}Yl==n1tOUKrJ(9iDsa#JTVhG9D*fRT_ZVNzfW`Kpz^xl{FgGlyoV9_R=BwhU zp;Mz3NF1EcXATTDU=7ghY;~m!ON03-}I zYmb!bwr)y(565Djmya_Kn|S*(J|nq0{niISgzcdd!WCz?l$cTu4h16P2Tuoruh7LT z`pgWEZ@|dOaO@3`;s~AKh(7WLyye&Vj70i5GzXWY7I7!|^=|ldZnO-*kw4dv=Zl{* z4E`O3^mU*5${n5~nu3L4lBe93KL4*zvmNsP@u_65VJoWcoIuz=P}Wm?#cy%6=|Arnd^+?c>>cVRXS9$}b2TF$&0brt z&u?s3v!*&^`J!eea?C~de@tK%wUaYQR?SIU{;+>B|*Y{ z%SVUrDkaz6{`e|9jBQ#-*CzP3hJc(@f4@d5NLQy(SsiICFB3c_?~*e#-H*YQZh%Ph z&IalO_lZ?#FV=4>T5eB4k@h6}b7YYzDS)TQcR;6v*`6Jd+8miFh)#M=Zkl z+2o0*<^#4`iZ!te5yqJ!b00XYB#IQBUz}iHPt7V7bK8}@>xKD3$7~LAgoeVmtrnrf zDkjY-8HEPq+%iD8BS|mqlkqdu(fqh2$z-958p71L#l&qW#2>|_W{3PISMrTY*-Wdg zpPwHIFSg#Ewbz|>gJN*u`uCuo&`^q8I9@K7wxP-Ua9V`Y-hTkl1>3x6`2TT0F^n?; zp1g*GR;6mCuCd&4-^XR;L*0{ORbpDV)-J`&bINfq9iFU%mT8sWC!r1U<~wTE`87PZ zdP;UlHDn#)I~X`Cb*LTr4|M%hntDmE)&@q-S^8-5U1(U&;f`q^Z(5kl`nMI>*~G3ot(93g)Jjh>zY9BX_lffgBnkG6VRjFg%b2kI zRjLh#_n&tsEBom|A4XBGhaBZ0t-tfnK6UEO`baiYhMSk*&?(ET4YmZ$ z|4F7o#uJ^xO`G;e?kg>e#a>s6UsYE3{&GI8rh=Ig2qx&QOt&&Z%Z3l#_Kfjpn%_j# zQQY%K8$Xt#g(busuT!+}CD&v4fwrsx2!Mnhh7Zsll$WB-W_ zY>wE7`n!4N^y}$VeA(6K#Jywqj3`{Y-R*cL>|t0i#>xzQP5`R`ZlLvTIN z6`4Gk#zl(=Ec_$f1tRx4u; z@fLktJZ=~rfxNu_`yL7q%x|o`DBjf*{7~R*}g= zkbpqm)m_C()+XhhjQPm8j5*9rpKbxqwo|Z zb;xMQ$)WP=HuwF~=)r*JOJO!Fgc+k$QvPPvQGza8f@*)n*}bSepRA`NTYJ*y zlV3Jwc(SR0lk|U!9mwd*%@7?uh+7{UO!jnRl!!`u*J4CCUxP{8fOAE7+7az=1~Z}9 ze}nzn1l806a_VN3C{{fXsRN9Q0fgJ&K^`7{x9GM=jG!YTDeg7*8OGp99;^51TCYpT zBe(uN#ck_q&0B7(uC%)vk;k{tCkiC@>=)WMc_8_i%1i2XlCW+I{fMTgoEC;+nzZq@ zpbsB3YIvUW8oGwZLSJb3>3#*ZiBAIo1I8JFxA8it82>lcRJO zHUr$j+jHODgHsrvXY9f4$#z+Dp7}iY)<{wQ|J13C9z2#VlW!azJ3BpgPX=xpd^{!x zX+FjVyy<`1ziY0($)lsO_``IWy8pD6x!4u(&g>VE!E3eH&aYuChy#ra}X#-vJNIo zdb{PqvSn)G#{y>T3%qEwy|8Td<{^a_?x?G*MW|i#dWzLiwOaV7Z!M_^sjQ8gVupqt z16EIuDe@jJ{`J~!ef;7S1GdLM*YM@XvADK5=FfK~)}bmf(_Ge9+l(Xn^|cT_RggR% zYDRAjpL^Vk`+6<)y0rCs?ts|l_~4GK!QF2VYLEcNRfh2!lSOci@p!I^fT;Tevk5=9A_Xkzb)@CI-R;WIt+Rs@B|)qbQxt(b5Fe4bzGpimQvo?E)Kmj! z2m>Z*zOKPW+}M>mDe0~5Km+DgX#3qDi(N!?D%W|1`7@@EYfGe3`BbAliVk{B>l0*s z@gmDdJFz2d2_0b%H&?yqTMV$|fDOip!xWyNSDN5cjRf0pbM@q^r>JjE;)IQK@bwDT zqPO{1*vvl)jUlW*;F_F;;r+iv1n5}Uhz^v)KjM2!5g9q9tDO0FnO4p<%5MEb*X8%sK4$o4-(n-wA|8^EF$PlJ21AiqG2p4C> zM&M>V^9^ryL)Odd5$U}~SF4!y9{*W98FVZ@p$D^}UUGsCwg}$G)Oc^#*MhxP#G9fJy z`1k_9e)+vX{mO8Big@=s8|>FOJy}2SQu=H}Chv6g7Vi5|W9WFXLSN^R;q?Ii+2MUr zniit6@-T7eJjQ2(f8|phnE_fW2TBa@H~k<#Is}bcGP|E{fU;5 z8+*5|BGNZUd@ifdU*b3FPHcF-=IttAySFAs867cMophD?)8tOR>?qb_*`hg8{Yxqi z0aqVD*;j{hfBf#s8q(y_i!NDzOwRpG1(1PXplgFV*7Lauj)*DzdXbT4c%sBE9OCx{Q?rhZM5L=tSO#D}SY}CtaW^c`y$D z|6x^m(L6tbZee4aV-=zZO2p|Rq^cW))atWLVM%GqZxPuMp^9(yY%y zAe%VgRHU*|q@{TKDD(5BDaMYa#E*S@9S#9#v0=s7KU&g!jrypjQDu>-Ky$;_o84q_ zbe=qUUqV2D|AxWTDn>`|{{38$kOh}vTZXo6HZrS(ubIqrxQ;0drd;(7LZ8HyS%qRZ)lGq6x z8DS3q0jq+C9xLkfPb19V@mg-o2Xv(B2BS;oCX#3*f=ce%Vc};P-c9w0hFf2m@+Auo z-5~e=(a&5i^h@4`UXsiJ)n?X z=?Q~gi{k+;^!(A~Y_PYku|MY|x6>mru%S!xEwFTx(^%fC8K~2FJEVc#=~+uKy zGdB85i1jmi2w`DwCrZ?vj=Ba#xuN1P3bp@e!){&E1j*w*3(bS#8SI}H zf4gg%aeAFA=&pJ7ia{pyd=QAK4BUh)+=C%7^G$Sk%lgDJGN^(L&lek3AJEc!TcvMM zZ*kD(#0N1wKPGAOlI$dYvc}d)0Z_~z737}Gq23ZYZy>8H3iofKek0CZF`DfFfHz^A zWZmeBZYCL^qDUs&fDxqzeiio#316p0P7|q-klFg5tR)*}gMnSoR(jPqh?J;waUn*X z4;RBVFjP{#fnfc66IWiZ)Yi;*h!O>Fm}vI1=mtY+F@i~GHQulJ;ZuxoCq+Z6ATO%s zf$r+AMdoz|IR)C|u@ow`^lJxj!}r(Y(~2#?l_p5Qb9vQ7Mk0~oRFDI6V6({5gI|M` zmI}>OcMMvwn{V@n?AUGPTOgDw{{ue9ZKdK9SyzcHR{wcp+M@D_!?L|Ak=b|zq9|Ur z?UsY851Kcge+a?j<1F`B{RHb`JIWrG*y9>43hZO2VcA{W z#B~I0p8eC_08cSO;8|k4+~zE1=K9swJ3s%P*Aye-yxu=&GI%)S_@|ZusBZ*!ZEu1K(D2$!4MNcS3AzZFiUeZh9b;a0wBP{yjVp*3qVXs9J zy&D4zm$0a!d_S{kd_rRX^~HuqtyJS^S`&AfBu8Ix3tk1_Q!LOzM5EROMIN1vIoX8Ni(o zq_jT5*V*I(Mhf>6J^CkWzX5}1h6^ZPt`J-V7v1OnH9^E{9alWbNT5*H=yTpj_L<{H zp!db%MMt8q9r&J2rt8}6buXW!l+ksMyU=-y&s??Vs-rq%;`O_=dh0P@ZD664<7J69uT(o$G z<;0PpLB)9;f80atynRm9+?~M2g6eh&2pvQmFZ~$@y(#VCDMS)3>9UL1L%wbo+!G+M z>9J{3rj={RaqX*s8*^`%VNK}>GO)#f6819Rs^7yA@;@xc9Y>3l{L#WaPp2@~1yso5nKampyC-KRx^AGgaJY@vox8AKHCS)Iz6)FxZWmf~0$KwMdgS`}DsP!pf<28T71j_~z;83AAC`L_aRSNxniMBSaW zb{gG4&OG1PpF4+RTscP;S@0&?yI)@yl~?;W>J$nKy1l|rTt{LV*k}`6=e2KR5u_?U zEZRm}m=Oc~1jbBd(CIj3OJi!|Uy1|+XtJS_*h1j`gj4wGU-oC66l2Ipxd+7g#V^1m z`T31>i58FXd*n_SYx0|{ym9s6vmY-rO>>OF1e-pnnn@fDN=#Da)ND@GpyVFZa&D(4 z$x!Tse1qSqcV89EJ~WXfidY=HixV|tibnT2rUQK#TIq*n+1K;p($7nowuVOW3ZfY4 zeN6bF85@p>*trsKc;NzO2fD}45;l}VbBY27+10wY_{;tEFykTYDfcu{Qz$TN4+bn7 zWrNhb8X;rBiZ>5*14jOsPCwkfmw*=L+a;H4u?GE;9rY0Oo48+GU} zJtoaQ29ut(1b6@Sc3k1N;K!UtI^R)J>Aa979&{&R<^ylO=)bgQN1IOq0qzKEk4kl8e?{jLNlQ!=Ob+21_1(CC= z@-3t9<=sw)a5g}CpK%o>8OvGL5tUJ z)$PvgQ7gda=;^pW6SBbFEpy@m4#Y}wAr!pk1CCePhmFDSoDdZfh_=&5PRDZ2f1gVb z@HtxjQRgSVY82Pwb&D3j7H!F7^rL)T!_a_ zIpzIq4K0_Rs{diDUtAbrk%Gp3q7qV2+#30%?$>pO=yW$jd6}5UoNamJQ^ehyY7hB2 z?~%eppWd>8oEJf%XxmJgu2!zy-u^c4V_+*1yeOx5LGid6l0}xE6fts$Sj6<29GCz- zG=O{Q(ikF`KqFk2C+xSHhDY#+&N!+#xI?&d)zQ-eP;xgqH~dqt<>kM1$TDEP?g^op zU;e~bE(S(sKP)EkMdX73scKAoDG%n0(NX+nTF0;5EYXZ|JutYCn;>F7qs$Ce=Wd;3 zNDgb~j0Y+3;eTDz%DvaK4o|;q+wKiBw+^CtJO3DVgx^Q_)s?JRZhvt|fw{_DWw;BARtYN~^Z0Jgbtgi5qj86o0(hJjNCKoC4I{E(2nR&sMgWcwN-f&*V7Ec1j{Tgi zxjw+F%#UX>aoRLY_c`nc8`G7)S|f&Sw=TI&=~mqy!_61v^t9~SDGPb%8`r_|N^YUM z*Dq|NaH1CZnhYvG;+edcYVcJri5ZRa=`_3^NVxmz!<_Qc#LZfb>yKl84;_k2iBQg1 zl4{*%-iKle+h2o%$In%i{CcRq?Z?AU|Tj34uHBQ2rkatK{5( zKu&sflV~kN)T3QweDPM1`szM}N7`d0WUXRJZx(L$QZ8#(_L1zL!}=e=2hQ0F_4tYh zLBt@$js`zaXtUUaosy_3o>x^T%@Sb&kgj24S<<2WtaITPI4tAu4%xWbucCxw(r@Vvo4vmC0$OC-DVZ*L_*9vc! zp1=3Pz>-qj;`+@4cdh-fM;&q3^qJ*W%F#`9>MINklvfU`a zkIm2VB7`)GwP+6qk?H&*3Jk>^Hc6&P5efDBk}2IPP%c>|;Od(Y*AMAnV9w-;wZr^k zf~J@FOF;hFH2v#w;0kI>`hD{eso<%YfkCMm2BTYca&Y@!jH~HQH3@99adn0ngrgyG z?i~KBDS6Jl=9^To$=!!DqUGQ_V>i6gn;2Q?byM#k$~O%EddvPb!-01ty2Tvg;viTG{rPrS zdwneT0kU$0n59MNFCjMXUHU0*`I2dws=KA5EpjVPAVMyuAsOXI-EK=Pf2n20w=hf` z9=6{fx7m~M?n;ZDErmTE=UkvY5e}|CK4)#1JNZ&u;LFrRx{+=jJ@Vjw&#L|7qT-!| zBO9pj)X^CE_#M5z%Lb(?1+f>=^6kEecwwDe_%~(_ zPH;X5n=dha=cSxzO9`5mmZg1W8wqffX0T-Hoe zx-q=$?Zl+yrbO+9G<2v-bH$6R+75}sF8kMrY+#TcByvH z{xPHvCm%9WN1lCP`(AEd_*xY~9|{X#9F;!z?1J+rSnxSEbkN$wbkaV0K78w_zDEB2 z`4^n2vXAek4g+p(7q3bS-(^dVq4qby4Jc|N7pqT`0~9~M`;%ce*85q-)EbS+iAAsw zpLuZO!%{PAbIyS6Ye-ct(aUECnUrCTurysNFo82y9d!D~&}_prCg_0>k~$UpPa==zY-t8eSOcn!M&Q=%e{bTKs(n+k`OC;2%C(EU{ca9Nw9sT zo~Z*tcy$FNs7!cKz3#x*vaYE_llT~wpH;K_a6Hy&?!kb;ce%?tS&lOKLUN|1vhrc3 z-l^+fx)!y1CtWwUhaBoZ^Hgb8%4IjQV~NSeb{_d(i4M(1z+8M|r~%m~FT_o|E$^Q3 zNJ~c`A_LZ~7H|7LOrfSjaew3Da9!0fY9EVGykB@w>>w&%)KUQp85$VijAbOVETuo1 zN=~{-<@Rvym=){Z+~#AwzVOq@7mrk{JugcxZdce${f>|EJhW5Om~kmlFf^j@Z!RZ0 zUIbu<(B-ZdG|!j$(aJ$*Y7$$3Jwug`D0nRoWdV1P2MNAO_h~z>OLwhMJ=>Rti2i(fU zjI9Oh^*|e3s`wK55r2sQwm6Sg*6_aOAWZY%n+oVb=E%;d=Yj>ZV6k18boEBoljz|p zbDuAb_;Xg#Yq&?>m=`p$X5f3POjXzm&YbbYyBZ=Jemisevf8j&lSq|x_;(a%OWMqH zqrGcSIiow6lSISy>TYR(PIvoN+?C4{cnKCmfApHkUr z7s)50w>fzBVMuupMXud3(v!MJjdbX2JX*LgCzFNtJ#2d|ZYngVIUs)J!m_3y(Clmk z>p<}TSXy`#Ik$OesK_D<4lIyVZ6p&nN*(Yi?;LPl)p%O8^cpoAZgzO4M88_7$Re~2 zjXczmP<=Pa<*_<`sN`pJk5$}wd$))e4XhJjE$&+9PZmG4vG*o8AqV%OiFJ+8aJqyW zSg;+dr0CLxxT;#=?tDyyeGB^S$whB_PRPo03uTKAq-n6!%vwEQmy=4-Bw?IVSoJRK zDriNO?nABFcgET$haTb__3*<-SMP<)G0aV>C-RcG;CK6y!Nf@;+h{tmkshyhG6kjA zI}Sz$dZrPCxOTu&wyzb_TO~dAy<(d~2~A=Eef`Fu>{3sulKli7tgrOrmr_V|y@UF^QAMTzY?)Y7y^QTgEq48d$ zgtLgODvd2uMAjLzYp;(w@P{*s?Q`eR%)cpvuf(pO{LQGQ+?f@#xV;q6NLwyPJT*Se zX=%-C*!)7N?P4LJZ@p5O|A&FZ#3v&+AWj^C<}cRx832}00NiP%NyP1c6eLXg^t~Y| z4f#abht)a`;XF1oF6&>HJP8B}&lYpH6~t>Qa>f1XZd9gPVx%^k0tMNOukxi5O0KHh zeYNpVDk+`fLABj>41@l&wG31NG|WzQBqZDLVQ}^HVj(}Xd@wgEbjN#4{9KleonV%- zXy|u;3tAg)X05$jOd*|1uT0tkF>X82>vTNRpwqkw|ME2oZC->?$C>!`d#6}3wWMZ8 z^`%LE?7ulKeNU&)oGr|GZwJL3!j+Eue`yznEfHYiRv# zuvry%Y@8I+I>CE3y%LfTLccJGQ^Zikb1Pjd!hUl7!JW25a`M~h7tKw*(VMD9oAk1# zlES}+QSj=qce$eoK#dF2BzkyUL^W1}uVXhbMlcVBO3|`lg}`^f?^_!w?I;c8cp zApND_{8ivMEh0h}5nU_h^6lEuBj-IJd43>OJ$GoRF^5(r%6>p)W?3!RTQMfQY zrH@?0GsPDfR5b=hftfNoDs8%}wQi?S10Pd5NXhNLR7KK{@YEv<+QAb+3E58>&_t9P zn}&|cX9qv_dIOXfF|YSweN4UCR^1rLCf{-K@h0g?XlquzdFyX;0G4b{ut4Kfkj!)1 z&&S}a{}-uMMTb@xA@Tc*h>1f>#0&T=Eod{$p02&fYQ|4}OKIn~<6410eq(lnxhmK2 zMSEb4XZ?ET$G0uP;F)^kB}*ZFq4hHs(N{fI(LUN}GiR3hjVr8E+mfM%KX7W?3PbQ; z$6ex6_t2sFkCvn_O3ZT6l%w_aAtm94y@dpZCTf%&@E@>RIlKg*ih3&-2z6Xy)Z5<&ay8!t*tB1mzXM?5IIB%LtqeRoAHS5qRLR(sfg<@L<*b(!!8n0(<)j-CHWiBUibyuM(V z*Pj=#m-n$hTwEN!WY8A;ohonbgIBlJ3!y~BJ4CV9qs#4V`ud`Jd>P9LrHR5vx(xhB zwK1^-%45p}bEm-fD_hW_X{9vgdx>Tn6TL2>*o$~3F3%!R=Ie^pcs7z%QXr|VZiVKk^QI%e_Rpq*!>_K>fRYrbKiYV}l>@oLI+c++c$eG4wu0mI#zz^xjm=)>sc+O5(3BDAt}m>WHSZd0@OcaLJ4mz?mZY@ zms|vfTfVo;b#U^a=RVn)jtBF0%D_93XKzWe0z>v9XfzX09uq0r?4R%x1wgCkHePP; zWQYnpogc)`F(6AF!jfg$;tmOS#*0(?aFE4Wa`wbEl{BFDY{}PnuahpV<)#%LmtnE} zSwhu(O+~RXPhi4)RKk%k#ZFNW0MX5IIz|E&LaJu?U&L(dLiCdr?pcXO@lZ9SsYmCt zS{qV$?5TnBV&Y8JB*0ey^0HUvRSy2R=edz%wfV~gt_kRKGvR=Q=iNL6_RPC6E4{q0VQHMfr*%$cmMYo^le8Nc)< zxZf9rP~XTAfklrXIUT9*u>BZMEg5u*K1p5A)s_D>a^~uBcBl7~)g%5MQOHJ%@zV2D zpCu>5V>jVn(do#z7rShWc`(mjy&A9V`}Y(oMDM6aaA?N=Lgt=c$<$^o0~m@6PO=_4#@pGfe*= z<55i@ZuuU!8-2#h?&rVY*ysag8Zlhd`vAQ3Vk(9Xgp0)YfX;W6X*)V}K>G9XYuky* zO4+?Fx&P}0!0OY2$AZ9ISh6#C97Y4!1;8H?6AE>`uoOzr!bOC#s^PZiv2{ky?Z>66lMU8l2@2B$jTWlsc&=#z!R79OuDRq%tWX(RAcI(d zhRt5k8%yHoCuJ``e|9(P)g_xu6rc_4;q*)0nXE7-Q4L6}vhr#w>JmzIKHp=bL#heR z;T4+;oWKxErU7bH1smVCJWkqNz-Azo4*7OKQU-t$szvkKM@Wr4*edb)T{=Qxrt;$* zxRR`v7tM$Q<9|(COu>zo%maj&A0P=AT%M;z1i~frx~6IZ{`g!(S_mR7BlRO<7Kt?s zW|;Q88H>WfSK+%444d9!zm$r>#qm+-p6M74uK0X!5YBPl)?O-b6_Y{}@yy|d zU)NBry~w9b9!+Nv5{sMnM6W523oqRl(EMG6aP>l&*?11pU)vqD|7}NiLmcHZ6gXq< zEtEPX6MZYLdW#0=yI8BVf@6VqsLwGZ;YqvqE|N|w^@zw*r1u=>%3K>>fqZnrKtr@J z9mRp*fs|)Hi4zbu2C{8i1%|j?flkr&Wp16wP=IT6*|}CyG6p}dh=lYMUo)t}?8^@(%uhPSlcJh- zDzV{5gT5VS-)QW|G}IDA+}Qb}puat0i-EWtGMCkmqXYn9`Mg4f;ng7?Q4FSN^W)}g z8}O4oj;m1&A7Bco<5Z~*7ks9zYDhB9vXlzd~2=lJX_@KN`L1tCr8yrY|Nm zt-?EdIsI8RZ@yT??e}9;H3uT2#CpH>+G}Ymbcp%+LaqXx7^w(dG-Y zcg+JIaD7hWK{XDHNM78rYfvdtLqp=3_T<5R0S@EBAr!bMvfBGWS66i{&FU@Rrw}wx ze0LQaQuR;fSdv-^%wu5gjI>W@`3YJO`;7IU*Yd@#{MSbb*EL2h5)jK*kPlCWCveRh$O8;A zkH+W#*j_^lYPvq$5vuw8H9Phv{GJcWPyfyVy1J%6QK{D6XpR*x6R1Da*(D|tl<`dx zQSN4Z>Tm_t1mf%0tA1Qa9A{ed%Z1eNP>3DODTgUF+jBl`zGU4S zMjqOWy@8s`21v6D8}*&(z^Be~(wwFFo?GP@1`5XP`s5_1&Mk-Op@RwWKzP$?s52+0 zLSLBVX05%NbKmY;iZYB%I!7)ptX||zx5d{3+^yUMNqu=L)Nc$fD?upDJb*Xx&M%-_ z>0A@GCIEF$w833KtbsXTRyc&2xm)+svfozp@$aesho-L#XtI6d-3aLp=`IQBZX~3U zkS>w#28oUC5~WL!mQXrJ2q@j7Q@XpxcJ}_yIbZi>AD;WUp8NWxMUv$gZTrg)^ZMPm zmy7&!`q#zvl=&e!kNJ_KbQ|qFdd3Zshs`(OsvQ=oea6Up;t4}>aANVy#TrRuhO8H`;qZzEv2q4d+ zzioAELFJY_#t)*Y(gs)81!HAeB1q(_3FQdH7RbUvg^}maXbBJa4s$YV+9njJCKxIe z32+`oEJ+Tjc>aC-E4$q-pq|x_E_1a5iZmRz;bIZh+w2y4$6nDB7P?*+HD~Y~Sp<$SjYRj1t zjcdTWOI2*B?UQ5DOe*CY<&`4_Se?0a6FuVqH$d*^>V$W~In|6zGsHBIY;vQ15;E7y zitjexNhq|8Fg|i6a`;fOI-=Cm9cE<%)%tvh5OVc6I2P$V%SZvFG64AM5LZ$ikC4j$ zRD16*bv7hwk=h{kupZ<$)|AnqKXdR&eB5|dps_Gv*^X8|rjauSJ z`O_O^OPj~ETX4TIgffDDX&CZA-DF6ca(8k*im&6uyZxx|7Ryf2nomMtBr4Z-uHld( zF~LlT;V}EUdoB91X3^w)ke=$Rm36=mJ&Ca};2WYwG_OGQ5u1YUkPpOCEBJ_=_Whut z-!m4c@;X|i6^UA`yPBBHYfg=;&3G$o`?8MI-NK2gwj=jHV-SAAKn^=V5D$nA7mQ7p z=YG*v&)p-Bbnb<60ft+RXzjM$!y~3usLl;eMG!k}JBivynBNi?i40T{P4r$WGP$!x z&bY$AHu1lW{4}07eDx~iqx7fj+i@rzac{xNhy2ts-}H`Bk}L`e;Qqo89hpJb5fuqo zntgn^y8T#f*}DUUi?8q((Rql`>(k=iSmi)qgN z>@*UW_Y6_#W*PNZZ8xVSdWB9d$hVfr4;H8 z`S}fybWu%P5?-pxj^HR<2)T`+^?=E7C0E9vv2|X;IN!DY0O%d%n&bFlKD}u|XO_zH z$h;l#h=I>!tV+s#Fb3qvF=gfsZ(&L5(qCXnwP#LEvwux{am^s}>^EI6D*UmQt6uMB zy)$h{Q)aJ3@Drd%h3+B6V`y;|NgLj%$w`wkg2{zXi0c+ZBs5Gsb4^?pmTPVE0}2!AffnC}J)tjztKXc~oQgMljt0|P^vYUG z@)fHRp`mWhS`ubI>TB%@TW))^2N3qXdBJ`+fn9*)heQqy6`l5L?0W z-vIqe0vC5(l;1+}3ObxagXPn@r`HpsN=~J9vOyfX?CIvxZN1r~yX&T>n_^J;=CgQ) zULop)-G25~faReOa9a3hp1Y>t%G;h%oTu9&Dm3hGcoCro-NyYSmf7fp{hj8twjQ6d zTZ$`H7XX)bqo|IwqEvs(tV1mcB{JBFU*J&{fK9t$BM}-0Dk#|BeB_sGc8u~A3EP5c zXIGmID4#PgIzLWNz%xKJy-STEtw|J62#HewUQl~5_b)s`wVi@fO8LEfU$t_hW@}Z?$5SnnhKmx zu!&FFI_ z$EL3vf|1boaDMR+N%poe5;Og!-Dd#IO30hCu;bH$Vnz{~0 zQ=TCBN3lhRzrVSIa%i-#*K^E+WL#SsDb;C&r2$K0-rRq7u9Xe1Lem3ZMCdIAMHe33 z?X~CU)PLK~hdz$HjU&RXOmdbXXk`+dr4xD0rbJ;@s{G^qm+gCL@t;_ZFLL<^)MTdY z<1NBGmgRJYPrcUb)vc-{dHuwU<6*0u;VHaav^pU_q;ws|ESPRtiQ`IS*WX;M7`#nqMy-xtqjAPiHP-PBJL3W zQ=;J5?&X^76tTFg&;5OuT5RibZ#Go>!|z~a1?G<#!yn{dWKy%#m-v<5+8V_;dh6Ew zW=)A{Qr9e9x)ibpCq0xKYnK6kwA*^GXM>7H|C*e3(C2tIXXj`GR=3;fJj{_Cc@ta zl4e8hpa&37!9uARydfRYr{T)~HxaTT4eT%pPyjQ1vgbL~@4n@V{hCwId3s`5)RR zLqq8{8%*l;4PfNFUCrs?9P-Ep?&m@<<}MHo4d9 zKb{}~(O5vT7LMDa2o}Lp$aYt&lAEI+Lf7?q&*!Aj{6PhoC%bY}=$jPzf3N)2(b0MB zb2;ZOu^*_W&%28+%(J7Zzo9IX;9}JEFfs^?PV(+x#q@RZmP!St0C%DoXfo^p6l_h2 ziT!w?9b;@)^x2io@z7D?tlIG)>P+nct+0xTTz|KhzM=0+#j2wDu+b8|X!<{_XX_MU zk-SLh-ciPE;?Ynp_!e)WlRC3p@;2g?`c3)l6{;O~q}g5t9x4-lQK;(Z;vIcfqznHb z=8h87{lM7-kd&RZ#}f)qgP0mT;aowGYFPC)au4HM9rQIwNKA|GRMjnnVmas z?6iHpndL)ec1LsEyMHK$2fy-z#g&}6!vf5nPJcVFu5?fVt&s8FpBg_5q>acpj6)VWTPqr@YF@ytLKSb8bD?oyIQ)~6i_(;jv_w^I5%qe{( zeWV|rn@ZLRH(EWGk__i4OVQ+s0VYFFKfy%njf>Q$`z8wtXl$LtZu`Wt^#fc zw70<_geb@OcEncQz$%-b{&DCmBPQ??xuv)QzKi-G?JhbKl;8a?k|7zOF?FEQE7;w+tG{lD%}~ ziW(!nrb#v@gmQ0ValNUi?JYQd5Ob(UjG zPb|TG9lpWGoWSe60Hy{Ft0vH!Ilswvzt~mBK*8YAy3$(!Ci~L|MIlBnta^Gz{Vwx} zXV&sey3;>T({?qg*@FPzH}UF3jRX$){m z*PiSE62Vo)+0dy~DA5~v`e(f&GuAX_;!Xg#ty*vFU86Jd^H_Prk3zW2-W1J zUr5D8uBNnML!;D9M4^`P?b|2O4+M_K;0hGA?Z2E4GAPd8IU^z;WJ7SqwngTyE#Op) zBUib*dxYQnaFp!Lm?K#+bBF*E_NIAjb!Q`=bu>eS93ES>f4e?Qk}4&pPh6wRHI?23 zs59%%dmoN0ktLbWUb-lNG3fI=o=V#sQMfV8EN@$DC8yCi6u_%ZFFhXyv_I7C8DQ#+ z+Ak1*aZoZi(I!KPQL-=@3gGsP0M60%YQl{g4$MD{EhW%MiXnP$7zSYRVm+Y_^b&ME zwqC=IFgR)dkE5b?;bgvPyK>Kj`MPP4a5gBT0MaC&vAxA1lTE<=D)pybYICMvDAA%T zDofnvOePaK^P3zWNx+^=kFW7~l}~6Ak*Xl6K2|`^1gWYcc_i1(FY>S!&q#v&aF&Ir z$I;8>D}jB~*Z$wK9hSfUKHD)}o`{EnYFwe<1a{2bfnBu|0kY2XU|$#pvH?4H58+je zSjd;vv*YDQTy?BuV_4q7mQ80Fp)8^P z{D=)9AQFEYNDIucFjfLJ+5gJ4b~3Va>2y_>4TA;pZP2mK(ow}0x#<=@IAP8^Y{bzl z=W=SG$q`E6#**?TWT7^}zxi03eI-XFBM845!%PDnidwBTFN??!qY5<{J|#r{`cN=& z?wtbrC3(Nhi*u`fk?1A^B$wV`ULFemu|l~~yg*M5MKjvB10y%PF|?u{$gJ~cY_x;X zvn=|kDmTVPj+KJYxaUDdGrQv}tX7hv_ZdVNj&3nhl;0AkXF=`SuN0gEPklI4NNc=ElbCp*1ZyCbS4fI` z7gW7YfnY$BdvyfIU|$IwNsM@X;|K7@ZS8;XN?!FkO$Pcyk*nrQ**Nbmi+M%qo%NYH z@PK^LDxpp!#;%Rc6l&t;L^;BkL#GF^76};I0%dL5X(4QmC64Hy6p6IqrPRnu#0P?U zXvkH&%-GGpCbK8ezlrYLmIJ~(n6{q>k0*AY=?$m>6{Dd3-*SEQQZa!c3(8K3?iSSX zR(liB1grhDwC8_6$K?DXc3ejplgg(3jk|Y8Gl+hky6bAHkR!Z^$l(PO z%fJ^v$^I*ka#H{~@#mGJZV5TA(cvGN=YN7F0bZCDJr)U^+i)FC9R}?WprZP&`b3QQ!iIdcoXe#>+kP zozdC`C(&RDQLfRmV5>g!7FQ3Q0ep-+-k8IeR_j7QpP2q%{cxDD6lkZ#)4Yy6Cg(NU z!VFm$(hXDZJp}fO%3)_eqaou!E!h!UTOy}`O(X@Vu6XI-b1t=;B)X{!CkebVV?=qul!{#tqWEml$ zYWwJ#p0O!Io^>8<*cCFdqo+tHa@l@Lv-NJV8G0#^(OVMcYI-w0lwyVb2&1o`zub43s=V9PAQl9#$O!6BIr4<_Bq#+4Owe- z>ZbnKL^Avsd5O&(HQdr53=0)Yi~g->dyi4`Fe zqkP+|z!2(TVGHiksdsBYb3B%Staa)lroEK(9;W`6&2dw8x}w-Q%#Vx+V^7eMVE+0> zNa}+wryB0Dvr1pr-M9v~&$DWW6LAM}v~AGPc!(YHkX$X!gXSt%00+zpPqjoyRjm-kA>qQ^r%aKYu~ijm(lDs^AdIrFHocQTy5|5dh~5 z60*PCa8jU??&y2NsTjF46YwnnG`^ZWqGeM_LNxSVoY#+zTs<9gL=Gm@86Sjh19zv&Ze+pNv1q2>#$KmEiOe<%YS7zijw*JdWqm#(`r=1tls4WX@!OT%K9ApYsGb9zNOIM={Moo846>eu+4H#Dx((^Kj#&>q zga*|iZvL$!AM+d>YaPVK(gD(Y#xN76HfUNtfi2pNLQ7d(!Oe1#H_z<{jZF;QQ$jqf zw8Yw!djJpX-JJJnVG`E}B0{%n8%<}18H|GO^Z7>`{;CgeKnH5F{ATc6!i3Fs;Hg0P z#NkOk2ImxUb9STUa=#fh_`Fs!Ns)vU*fTP5%$NNTH+cZ+3i;5N_~Zs6aLae&Qi%9V zgCI*mEIzFNLxk|UAvPx1?f%2lT*(DaYapXT_pcse{ivrh<2`2dRbD4fM(TFGy;dgF=8O{CnWonB#?}z%Am*m8cj4Y{tES(az{HvLrINpNC`u?qF zrETDiWB>EfZzbv=A7X#N2M}<^Oupfl;QLJuVNf%}9M{TaFcd#L7m71JSwEVazvFS! zk#|#J!}kh_MJ1ip^m-)Q-5bMtu8J>GclrRk%gV8oK}XFY2aqhBb)W5BOku$BxF-*B z>#O8)^?Is)!H^B=A)j7zkn6wq|%bVf&Z105dM*&f=qPY#U79G>l=bTT?Y6Ze)CV;nZX(KMsJ2 zLMWHsG~@J`#HM&ihG1VB8|5`Ikb9%xC%W;jq7VFnxBD^W0@gH=^)8!NBXl(oM! zID)nFyM2|i@N4>Wxz*0<(#Q_Mw16Hx#e>>pl7~qDpOfuR`Sb5c*ZJb`De@mxXx`9M zOhD}$&IoS2gAM~IY3)WKSn;FUf8hojuzEDc*=(2PCXm3ki+41^%B^S7RvnTDovB55 z4}nO;;nac%*Vw^CgvtL~7q+Z>);SvW6720-mLHIzh{k@z+MPy*a-0qQ6cTSOMlMnH z`{#U|%`mwf92y~!&&4vW=Zv-)^6NDcK$@xJJUinscCuv_7R)Csb5w^C{tBAeF#YT@ zExY2JqhpjOl(*Cc(tcVm116=2PQsARPnx#g#z4tGBCmenyNR?2rWE(uDMr$w?f9`W z#iFxtH&rznJba&hJsvY(Tk8%XXqWK&o8;OZ_=cdWd=*5pK+51_R&%-jumMUk6=``H zU5*X7ZNGCK$8R4>RKK5GlvMX_`h>6~zH)CtErJQZzzH25&Yz17Zk%Cq&r&Y?jQQ3$ zf2Fd}VU3b!Nb~N2pNW`>V8^sK?wZ;68w<=7p1?V}#0pVR`I}#(GTD(7! zTmnU#{=XIg?TfqrI?f6RI+c&ihP=CD>jw>$SwW&g;ol2qUvDmp|ZHcUshndqLdG3;y`AnLFL4m>kzj1GM}JB zLI0h+XPjij?wlTC+GjBSt>=EI_}G8qk50Vssmj2=0<}APd4|&+w-?zjkM?zzYXvM7 zO|yv$M){(=1kDTj)e3rY7wM6?DHe|>G&Cl8XOtz$8-tl5;@a;<@D|Ax3;1u&{-IqL z<>TwqaaYR<3r+6M00NHke%Q*<(jOm?SkrIB2H9N^6-k?!OKZ&Nl>ba+nUt8;&T%Y_1LmdkJr>1^@`$-FVs>CD3WGfcSIbMNZz>6|s={a=BKoSED@hxPNSZqlEJX z@_kH9I>(#&6uFRPMDz7)>|+|6!X6{2=5;}x#^j!W%8_M^afeX96!S;kZ7KPRUeHQ3 zDcK_w>*|uak(D$*HD>d?iiI4ib>n)Ht^*DM>d%1VSQtNxB`QrCsTpvyRS<8C2Z1q8 z#krh6yxQf5R*TDd;u^U=`Ns6aY3eKH@=w>v@XAd0G_Py>)DVo zG=w@iX;A>!N(_B8;RbSH@P;jAgOcM85sqG*=LhT{vW^{^2FzxG%~Ab2+if~1q$`-# z&^OFM?|D7eDyRI@rRDaK%yv`Or_ky&3e$_bGS-qQw88BY@pppZ!=o9$zUZ$+X*m`X ze#;p8jW>7Tyna|4FdT*Q50iJh^~V%LWin%JXP8wG&yxn0>(6zK-^O-h4??b!5kisv z64u}wE=hLsg1Scbu=6jZHL1oU?$*0u)e}5B$^b*ey4V#Yjc3U zX#$G@G$;?|dlK4>> z3@1s2RcA;9VZ2G0;Fo+y>tapw^n|0EbSo}qq7|-?q7^qaUbGl#?W8H18yR3Ww!Yg6 zm;pNV=c-5bQ}ftp=+$#4)i$o%x3f1!D853m4gR!^!kQV!FB!j&tco%KeprZR_b|}U zSTdJ`DK53#)r@%EXT?+zgEcaR@MUizKs{kme%bRsW=Oco*(X{23PY6FPn>8Ak`2E$ zz(eJQJts3}XaI&7@EqmFY0(1DNi#qcL+&G7lR}3T>mzwln3(q z<76I1x0ObF;Af&S20fKjJ>zUl*%uKn1x?03PyfqI%nI{+{5qdQnAfJ+RyZ1GY*^mx zB|-n<*OI}7s5{k~Eb&n9;L2f>(9*C2NcYL47y%I4f}CsoYbjoOr~>$}U`hF$8JoXM zsKfHdHOjZ6%b>-Np|yZqVgz_1%!yI-hrkc{TO9M&~8%d79Ne-PD`B3ch=-~tu%5B?Z5Zn;**0>$cmJCj$A z8ps^OBJerS1!3>9Czz*6?SfnlM(=y_ZH7RbbrSI0kDTL(WSHnFXItg}+PMR8FxI$V z;Hu7>+)CXhc|IjwYG0d+^RVK0x-JU})T(oTVectD;O%!mLp4%~nKSS3l1Uh`JN^64 zC0Z4H+LPBp$zqhW1JpoeVArDFh<%6U*~YA5yW^g>`Id_H3D=ngNBKt{V@cl*$&@v9 z2%C%04uMq?MUj*|Pd#43+?)FPLHgyAb4w0Tl2(S)lKXqYM}HU><7yIYFQfXJ#=aqT zBU9#beKX^s*$Uh0c4Eh==R*hV=LyEo%HlUQPyqQZ$dd=WT8u>82x)YlU%a*XF|e=jxS}#n=8Ae-z^-N=}AsqWT^iHOFfs8k>YV>x)mfjLbH8eJ*J2Nj00>2fpKZ6 zd2EdG@NVTpyxVyGOM0@@tgxOu58}1fV{=nF-Z!SkHTx2KHPYlZ$Omz9Z6D^0vVhWJ zPj|&M$$8~Xq`>#U((@So^QicA9+*+rI*24767DF0HP>v&PdTv|pmVpc6 zRyJ_ROGa>4N&!*>PHBD82r}p3cN@x~utHW{_qvXFAvw?of`egUXC9iue-H$ zirM)(Ram-`J)NMjmM}DHzvY^^v}-*kEGY>Hv(l@q`L=nz zK9edEBQyu{MfNvq{7$&y(AR6>*mTnC;hG>0$ ziDnypRdzdf=1byc8eALv2KK&Um8ThP{2_`Y7NPkmL>XEB1L1A@5}P`4K)5T+Th& zFAld)Kc>}MY^EC=m9RBZFvu{5`+xJX&|*p?3ybsA1Vp^_O?L7o#QXy9*zwy~y|cvb z`{|98ZTxaq09;|!AD<5XL#sMWVULyO`#iVo>{)6wYW$(mme@Z|vbiG#7@C^_@$w^^!V@jeB7!Mud*r zh}+XpkxcbSpcTojz_9{E&eK9Wup>m9@JZ;R;^m7&8xnj9%T2FmK*{6XD^i6TTn#^z z#OwZN%D;TDW-0UP?%GjcpZF?(-Qh__R~joKo6S~>ddbN#oEZa| ztxV~M)9LVAi%ejKrFHYcQ}7$FUi8JZaH#oXp)l~19{6pvtRDgrWf^>JSic`GosF7z zv=)LwlR<0O6$$wnGW+B{<+XLz4XAo0H-*i?UXD>zY1B|9d|5}7n5;V|o5j>%Q2&D(PN5z<&-Ep(Y-U8Bm)&yC!&H15MG+>=mWcZym@(%U56;#`ScJ&A(9r(QR00rus zX;s0gFJbQG?S9p0Jse6~FkPX1nZl=}N_*Gz_@#>95_5rqb-1+)OoX1YK3o3E-bHIM zEVQ{Ku z)SvR~2;%F$&u66Bzl8Eyn89RX)5wAOPt+njeSg{J`E*A|R$Y{$r%XO(Fm_(k)#s5; zqRy}JW6EZyV+xo$>RmSw$T!+fK@>IF2DiXaV{;u|Jp8*0rUzK?1ee05pNC_o*7dQ7Z9t-7>k&md?(%;j)4 zOsft=eFZ5pRsn|i~TNu%>t zA-j-TACJ_&r7JZ;f^Sz;vfa6=LFW>27CGYt$lH2 z*1CxXqAaO`4z1=HFqZ&rQus29@4NSae;&j%pSR`p)JE9wa+a#h&T~g$IM2&N%jw_! zi`L;!4WN0H)GBm9Y&ujqURz)jS@SNtk=zipR09|R?v1R~$#Io`m0T(I-zZs*=`1Ag zz@7{SR(yaM9^WY;;d}1CEPz@pKt(P(x5$J+g1Fp+OXXwK`-RTG>g)#Q83$Vz`}HbQV3+^kft~k}Y-H=rWhSj(k)J|3Oz6A&C9Zt{832 zdM{d=K3IF?>=q%NUSrtMjLf#+S2H&0LILnB z&>;cc#0`V55MUCFlI7h~%;ViNmnoC5R~I6agL;n#oFlb@a6!?QmQ04p!Y~ruFk6kN zi@*}_v5BFrwM37p1B94J5Zs;ztZQKh>VDXQ^x5+wjN97XbLz9JpoqhJ_gZLqi02&> zxU5^c;RI<{(bmhikulaVRa`Rr7~HdJC^~vNo5vv#g~k&$-?$_*U?gYN5qYA{FX1rd zbNHCy23}rJw6B1>`IdHdz5(PV4-=eJ@WmGA&kL?HM0CG_&0_(?@E}1Q4s^P?0cV@; z-EN()Z+;fccESxiK>baU8#{(bBqa32(s%z3E(y0=PO|M|5v+X9EgO#3NAMy!*J8?l z|DSK5`(;THRHhxRBDviAwigDBOk=TxC9xvX7*5xX zaeEZNx>O_!*aM~N{@aG3hmau_?=~EBO+bAeQr3Uk3lmvMC~UhfFu{+{Z+rqeh@xi6 z_0(d{9+Fs9qJC?#OkzmD*KQYiw3=?C_0w%Rq2;D>76-HYlgV{%`;KY*<1iT963AOmWi1#d*UYIb7c{0{o-U zk=OY9^qPQUB4h1+>SRNyp{b>2opO^H8h2mCA2avn@H?r_C^a$^hl@-i8r0X4G}kmp zy~q-mWzUr%qWBACBw&DL1LmRC4fOL3p{*jcI*0^Bv34)8Y{aml8A+b(t-qrp2Lhyz z*)FZ(zbDyVITyEYv=qRa`X)yrlSX%jn+{2OlO)MJyRmXo{vJl2-f>oA)LeVCf zTxOZ3 zCGfyL>Ni zj4qeMJ0V?xeJ+R=v2;9s@n<=U%X(Fl83s#aEEG_(LH((gRD6ECpWTPi}Gl zU-AMoark=rMS0KOto=?vqa|o;yy^t?iQ+A!HhRKZksdqx+~m=!PQES& zkx?!o$p!Mf3nK^sQFrqwD7$0kw{V1S|4B0R@L0aEKcRUU zcx!?N*fZe!FC_@z!!HjoA5c~-iPybT3D?xLO%E?WH&E^BQ*S+iGEgX(w`@+&Z7I%ANnvm~P<{~G$sh2#SXxJdfV88aT$ddUMdp@+T) z{?dy-P&2-7E>j}wu0^ez0N^UF*omIXHp@59Z(ZM9*cHD}Gsp*>88- zYdAD*=j`1D`U|GTd-K!2ExNsX&XZjbbtd2%iW1QM<2Ma_PvS7^Xk_)Z{-9V!|40BBy=j4ma88D*PD3VH>=XOBf9OOi$P=nuqy4H8R+Jf**xtj z&>5!LQ1biq3k_5MVrdTA<4jJfvX9Z$+;`T za}3KT_PL8f{RsqhOWRse-uKW`*ugYG_+bD~;B#ixuxldihDOMuX^x1hO*~ifhhU)( zONJha6$G#xk0(~ReO7@}p_irZ(k~3m!T-|M+WjJ}CL@A=bw3r@g zJaN=7z;!c+6V++V`D#wTIzHB6bFKkI`iR-*@mrLKoyaeN)~O+uMjRLqS*g=j3J zDDKyhieTak%;Zjo4g*R$2t0^%48HWc)9_>n(rY?@OE&eB?oUu;2V5k8mpdj7#C&iRApK!TWQvEI5>&D*Q5PG=I2?q{B(kI_Qvo>kyuFcmt{(g*FT5n`+D??%kLM z%poi9))wiqK&gA6>Tl!pOLsJd$@9+n8_}qF=T_|g)4R6WmbxPjIIGHKv*ze%?}Nt# ztCmmhr_R6Dwc+y03WidhlLwNW3h334zazam-!K+QbLU{HGNRo3^t=E<` z@Ki&&>!vpwrN0`bg4_>E!9&87J*X;f@B>9uVwZF~dEYL&6oo_QL(_;WFh#1f*S|pA z%3n;Z*vHpjX_Ja`3xf1gcp4*kr9AdPI=8IA+l3er(Y-8EWHcNga&G~&o=dnv7wYfK!650q!qx01xOOKfhol`7S_+-kx{o?G?g8{v@n z{u2f}V(%5hCPmUo>GNqWDAyJ5!thEbJRK2N*t-4FYRP~1p->yqaERFd2TIy>Uu6=# z3H{%YEV0svpZc0WE-6y9?Yp4drNpG@SCM&q9MkcnbbXoHs&RJECP`5y^mopXXhi^T zb|IqZ+Y5Qn9L%Z^>+3s-FjDyVH14F`WCozP=c8Qp-s@$LAB!9w=I^aiKd%rX3*4Gl z0~yfHp^6k}aT<1ft#GL#DZadnI3_pkv1KCe@O{nCM+zgNd6C(`cS&XVv&m}oXc#1m zXeH$QxMiHdk@($V^)f}zRG(*ec$7jXOhZ^~a{D|Q08`f=XAQ|vVmG$Wo`V)>Yqzu^ z52|tmVR8{V)+jFJ%jXUDDcjy1bp~~aB{V4v^akyCVuuxns`7=9R3Oe#Z!LIPa7nM^#B!O1&2{3}yK8^iY|8&dAE+Y0Xw~ zstiro!as%155iL@(}dzmB1S8Te~K%4n*AiRj$ZtfxJ<;M@K+|KJ}R+9A7p#iBz;_B z70Dc|$+KTejQiD_k0)H9$Jf1Fx~|WL%}r!H{O~_X-nxER;LRl+iO~(nG*ai+g)`!` z`vkZmy>7a<5$Tv?wz{5&$d(28!`Md;>17}OFOm(Th%5@vTjLo=MylFAyo`lrvP-Q= zr}X_5F5js#v+19?zQMedr^@=*#}7a6l}oa(3ZWt%_6Qr#>nAD) z-k(n7AC1;U@Bs1Lfu**@hb&8Cs4%%sw>sw{76&zb_L?*q&47t8$1hEPKacF*dPwI~ zHDcvR7`3LE4N}1}3fK|~DfHf-1)X55=BCc)c!ksJI@E~j0YtDiv|?#LD25%N)H47I zpl`qVU1mf9Am?yMiqcEihTK!Bz`Dcc|AnQwKS`#TsvivR&GBHdH#7L7a3r5CX$5b z#}*$pF2n%+R_XelUGUI4nb_;v_Yd)U<9l3GP1!wcF>ha-K>kkMrZw_uNCyF$|o|(ED%c$V->}5*}Gx_P7b&x}3=sowp7gd}mj3MnU4%l{~%(|)lP_iu!%ZY`iCd_RXC&ckcY%G(!Y4m zj~S^>efc}fjaCJ(&If;o`4pJk58eMz-Zkm$UeaahFql6=cWhtc|Jwf#j`yct9i7da zskIr0V`Uw#7lAdf#uhu7>6usce?@@ZFBkuk8h*ywo2_6Z>F#6r;Wl|&g|)!!@uMpE z-(|yZ2dhwqlIs<;0>u)&*(rBTkl+cX2<`i%;&im6;Wn`!zSClf9R}@Zqrczo8>!oS zc3ssE#2sZhJK!^fTf%xkabv;JvJa2$C}N6LQ(iBbLq}&QLr;%U?^jM=Aq!0p`N|E! znRw`Ydi2^mpZlW&Gt#C7F^2yVuU1jk zpA>|&Td)W&eOQ%wOO5Fe9a9jYsUPhb*@_u5*vuSg;FSLKUK_mi7osIr zX;%JQ`S!^%gJsykb>4!l2?VM-81w6%9TU@_)qeHWu~n4bouC z>l@O?D79v^0lzvsQ5IZyWQ0MpY44*~FE84MDsrs0q1|6do(}IPvJnylWUZGg;$=>u zzZmNf$bV&%@)(M#|B5FLS1(8Sk$^R_B$2r#!GJ_z)cY3S&9SUI@X)%;fH8dyY{CFDdo>I4C_e@FkQWF+Og zBrtA!GRY6`DiUC@3l~-$f8t1?SEno;)GlV>26s0)OkwRM;m}vcsMlhKXPtJ}I#dzj zHx+sHD>sS6QH7`lo`?gr7kla`2k;<(&&BB7VcN9z9>a81t6%Z_W+dP`RyNy0Tkhu9O-z1n9`w zq+!8OFDDg1Cg}8D8OvfdDdO`!(zvzq0e%CYI5Q^$ow6Hc+@%X3rOX!Lks_Zqp?^dN zP!7()bJO;>F7G*qDYN!+ZAri|Ph7|K8A*&)D>ge&?_(szfnCz2SJi_tJf+3@Vs!A` zs}Myj1g#F5JcSpM((KBvj?qxvX=P~q%5g93o5r%opSf67LowMEHB-xwAuo>aXm<#7 z9Ng{vyEHHrwq;LS^2@3~nqfEfyz$?z`?Z?J4@&AdaAqCAp_$I#g1$s^_AHe%=^rV~ z;ThIpCXG$&XKD;!3sCrPt^1z6;LA5||3|GG;KQ?@qQgs74IoTHThPSfh?M2!Axoqp zUSx3`Qwv(nI#i`Gb5H=yg)*dj6$QNT`P6A^SfwC*0Up2Q#_a|S^#^U1mLh`U_p44a znH=@CzC-;}uvxTu`4`>C*{N~uZ{KEEqAC7+^J*kXieCs&)h%z){7bD#39jPv>co8i=8djI%g;PmcUJU%4ois@nI82hd1;L)}OK*~Uc_M6|nQU(@j?dvef z51{urW4lrt%$`TEB|(2tuSJ~FTnUy@4bybo>{^Rt%_&dTZj%qx^gs}|09+U(q2&)$;bJTW2l;pRum zNO!MAH)7_szf`}!b#No3zG2y(VCHi4AU%RCRH;UgE=*PHCYfPV;nyp-7uGwm&2z{RJw0UR1^kaJa zn_Q9!Rh34o`3aqp%;P63>K|UMy&%aRvHZ$LFXKZ$o;9ndGQz@)rq)Hx^zNxG zvKoH_XFTNeH+_)0H0)yguDPQsB_K-9fbrrCd?zn@cgH!itNE-S>P6j z!$!al3ye1JLR>HfF+xqG7IS zui4s*qx^1aBu*9h^k))>mo`h16*#1cD>#HBEE>MC5EM7+WRAh+-3|puqgP3^#=i5b zzpNx}A^Do7dJ?VjF?1F{JHlf`EuR5z$r%lrea1%eAfxzGKZL1y{29MTILt#tN%zx! z%2H66K#IL}#Ia>_Ss)WngLqO1mU2;R>2WGEEg*z^XL$hdJ{Y7^O_oW)$NpT09;|Sd zc#l4XOWXeMuCvj@d@x;`%(l37mK|^}eyTtYS_)lsQ_cV^bj+lc^--OqJ%Rw0_~T>c z@h>VVweSBLM&kuVo+~9k4e2^erQHf7BA17Y>Ue64C_v{*A&_wDFKjZP;CNT^(}-zn0}6NvV^$rheiUhc-wlE$vUdT)XN7Ba zyPk690>#w!qo0Q^yUqM?$Byy@fnv7rjn!$XzB>#}rK7t-oF>){#wAxAO;>lyI_}A{ z#Y#Ja%Ga>^Hf$mh)ysZI=Rnf69S+;etzhZwH0M) z8q2Gh_@$TFVasvOvL4zL6Zn;$!($&}=Pxob*lvykB4h}ulOm%v1*A`46jMN~=hRpD}P34^2wqn1AFJ3}INJ@q;XM;BiHruD_}tfIHCYic#Lm`wSBp+-ZdWj3Vi ztOlBcqxGq87Q~#R5pE#QYZXDAGr3!ut+&y6!gq{VFvw&Oewaf{hc30cyM-#rT@9e14x>N4}VNka=It6iT4KM`Wym z&q0H*`C5fPrmzeuwnHOZv{XmPUSqTW*%&-%n+y)J>IZzv-WW{LTF>!^hAb9KOEbwu zu9l1yy7md%zEe_wJvYwtq7iu#cK}(8J^n@}L<4^w!C~~sz_YgC=o0gw!I^6Dof|E& zpzVcFj{q-ZD=EI>B^+fk9zK9(>>_`IeHZ5W_LYzr@-i)rzHHy09D5dP*wBmrZfR99 z=DL1M9nn#?;?fI03$g>Nb`#2DDev`2)qB?S|8cjWKB}7hrX7}Ru_>II1{KW@x^C7> z-DyRy)*yD1Lo<90_Rg0H^DhWe$QI`;aLeRjXPtCF(9T`WFYF=PyS$}(yjP?y;OR{_ zYeyqz=9W*kj^&&1;wSU;U?EGr`a4WNo7^77l1GlN|0qyN+Eh782}b2A`d>f(OC_g2 zb$PPRm|O8Bq^1X4U|SB$EDNrS_A)0|W+!0D}iqylUUrBxk=C@ak@zGQ}2fx|v;%-sg188K*ETzj=I9WwyJN z|I%Hw?%pD|!@DL(4_;n@`tzMaBObv>6N89e=11hs$bl=<$&P26jTpl!3yFtOp)0hH zTZ$59jo6nddK~!PuKjsJxd8dgs1`^=|61we_H^<;_rh9sQklR&Y;$C86#E2k>rH5Ugtb}Q;W53N!LcxW8I5Dd+xksTgmX!(LNIMERw6Wdo z+(IM#zBu&yT=BkhdiUI{zTrss>yDgFhA*9?DQ2jc4}M8Itp_XVV9+CZ4W;UUsFOh( zEav1TBFgVPS=pg~aMvA8#>8nV8KU3uVeZ2Rv-%+Wz*po*f~+!`lTg2Nn!6%qlEyP< zKLOU4YN3_Fwq-q40(x;=FbZ#P!$Izr1BhItY>w}`;pC#BKomwrp9Uh~fT1to#MCVN zsvXSsZ=uk*96uWEUH86A8{OP1%{fO4ItN`bCV${t3G(3_@aQjpQqJs|j&7dHtK9Wp zv^1Bd&9gOO(grpLd*XcC^E<7OIL5pUVCHK5E3%p3DL`$D4#4+{2Q|o zy=YCcks^{|l20CE$cN9xcdG$U=FXr`&jF2k|5wc&3~8{7y=H zJwa)|iTT4Wyaz9JkpX#D{_>-X4yA##F?EF>jRFz-XrY1dD<7RQ&rf!OxrBo~)1zys z=#F|Vp#wJNN&GbG#XJMk>C+HlB>qsH1bg~OkTPI0Ge(0o9U`|ml~*<|vlb(PlPRuB zFGI~QWp8c|bktZ_+1heZ@tu?|a?$GFwkeyM6Z|*(vyz5)FMB+5#r&UyW4JPlGKO-; z^K{Ug#@Mlh!ZW3)2X z{#jnP#G!bPY`1Dmx*fE{A>t|b-b&J)TNq`|ql%0;)O65 zM7TlT8MCVog&W!ON?A>yUm46hc-J>QM&XDDFM4P;Y0`3-sQL47ASbSJwVr@$<^fM9 z>$g4l@yrot%rys_wFA+>u|3`9PIZVk>LXE4L@vB>)}b;`gO6ve$qwqBM$~eRR4vSZ zVa@fdv?=eXi%BPbSrzpU3sSEC?f#qm2oJK>pCmZf4s=-$Z(FHy}&p#HZQu) z6p509nSuIMzajGzNtkrwNkfR%WcKPoSLy=Y4HQ*iX%P8uCC3Q==K$o#_h!{GYA0Lk zT6Ht<>+g`uR1y}4q@IkHgcpsCEP1RkRC5ti;_StEk-h(9L!~B|mo=dHj&@c#f|Nu6 zSqx@UW{Q?jj#Lh=;{sW&@o-u@+Q8~LRLn_C%lLqV9S@!;F9n}96#$FZi75Y2a>vU) zLE*|+JxD>KpcRa@&-V-i*TIXcy?>6y)LYj|MF@t%dt}yY>D)^3Mhjo@4u!j{1dBXd zG3RTcw5T3_ng9deU5rgD}+8Fcr)NP>MD&&jKH_{0g@u3(hOjRtR%yZHD#}khC zC)@bpx(LGPcnQ+UZ-*e{Go~u%-bD@(?v$I48UV%j|5`d_oM3W?m|m-9xgXwhHik3h z;~2Y+hHqWVPGR%MSqRIkNdv@f;CkcJK}CWWkqpVRpBl+U4QcA;s_-`-Yt@DLy16Kf zv0VpD5k1$(LN6+a@+y-qwxfG=P*{cSXkRV`7%HJ6TCHWywf#5LH(h9iH1UEMlLSmx zg2qk?{`(dk$O~RW)@WiO@Pg@9vM5r&(aR!V(Y;eWLlL-b%^zeRHcpVDO3@`vcc?L1EoA znO1*{D;dnFnmnh<%CtG6pEtn#J6y{5?}%DT@WQ6?jee{L1NwBQ1+ylj$!*A7W&o67wqQ&m%tYv0vt!3Tzn{%>%J{xv z*mBGODt(sTcw#J3D#9EKw(a!|Rhs-OQ@LM(BMQMD=3{wJ*xj?{Q)pCwuN5-@Bv?Ky z8w~Bcfj1$9k4`87;ZHcgm^$lLI~;Hkt}_g)!Mh zH%f7ah+d)L)2M82_4Fmbiz6oCEX3QUWNE9K*ehMdcx7bogx*Wb9T)$S5Tgw4@1tI} zk|HRNO<_al7o8-I={Lr)FL_^n^y9aw2$vUAwN(}PcTyCn*RVzptX}5wnF{n**EVMHAOrpVVK+4Vsy*P*)-~|% zF@=cH4sG1FIs?ED9!KRCVu|uNh?>deyQ;QKP)`4^!T1to>Wpgxxi`eTQgipSm`%5< znH*k!);|`HZM<#x<3gyhz4`{Jh~mMRi#R=?d1!Gw9W-#c@f+tHXBB}Qd(;`MAZn7T zr7AS)xW6dn!hmuTuXlYJhI8yXs`O+{yzsSnv&E=!iULi3Hp&OtP?3gW(MbAUFdOF! z-5X-F-n43l#hi6k+@WpHCgcN45=EyiiO}B@;i1JMuy!~=KDWPH?!Z*Fv`SgOpV@le z_X@^0UknB|2VWC0qK<0MOQfj|^5c&A-M!mhY*2fLbOdVgDem)&wc@6c;+94AWbd%4 zUN%n+?HrPJTcNJ%l3(!7P3L`QLEZBK5;@ zp)wdY6%q&hfMgl?CosMiH?bmMET{R7=9hx^;Tq7ej2`rw+UzrZNL)s$;(K ztQM?#A>?zB!NfdMx40lExVLPRolUg%$A_9K$ibH55$6>L>jCtl*nL9h*NHn6FZ+tR zd=2t0tsDybb@PYD{x*g@BKro_X8VBv!DIFB!;KHI3B8wd`NsZHsQ7BI{&V2B0&Hz` z>s`Of509@XlRE-7%{TsRo?Jx$rho&tMBxBZTp;SVH2h%B4(4>STQ$O66J|%A!|m<~ zQTW~&B=j;RcYt&bs^X3OzW8_&DH~~t*%v;hA#fvs-`|TGtCtz6qJUv!<^XSngPmty z>rt0K>wUYws?M8-6Kly9lqJ>`aov89uA_jRRJ~03Bi{9%8!2D?UJs!A6mQ~qms%VK&it$d^lBRFJ(Vdbz3a#j%3K=Pl)k4j@m9RJ)T&0E|2{w_u zt+8S8zB8{okVMJfUmfz&Zht$^TP1Z*pY+SFcny3^rvFNhOkY3=jD#;59w}Q+AK;Yy z(*GYHZdPI$#xcaPYvhpSB5M)>lWsbY>rd~HEeta-0DFfzp{ofv^JuZ$HH8&=Rs_v6 z%CNoJzR#+x=UaLQ^HsOsL|_ioQA8B9`wJ;FzPYxmg)M}zR!O7T>uv{733I}WH2-oI5*0BqlBxqqU+<*D&G zIeYm{`_D52{olJSy%!{|9NB=39;Pxsg4YjYiZhogxFecXfglH|>^XLT+I`_T=4}VM zF=OYpmn#`8PbIk_Q$Z$J?lDif_WQ&$lhYZ|6|mVVk5mzP9@%P8;%58*c>x>+Nfq_) zCu3;<@wge%ol*pIaCc4`wjQDYt;0S#Bz`lp@#9+g+yB6V^vDEhFbB+go56l=x%B{g zK!CmaKepXFLLTzlXG!%+ZVqO{XYU3bPW(gM_@N$jyJL7CZJ|ap+1D5>#OLJabbj^B z*DA+#_+~$CosTt)|Iqdx_1k00Z}jPY?rgzu7di3Ykc`jSimjhg2{_4La8}JUPs1zw zTmjqwYTE02v&#S_%t*6_w_Jz->wyFvzuBX&Rj#Jb8c4G@NqCKvH=-nz7_B#xY8Ich zS1XIHxXF;>t4p-gFahE~1?-4E1(>vpKNxy?L%5maKxm~v3d%arqt0^Qm$bLkTM=!? zKUrAC&+|Mgm;tVJvjcGxQUBC-RB3@C@i5Sk6p^p;;k1#WkAruiU4a>vUOU|De00hO z1$4-JG$7*D1V=Ndgv&I@OL ze!Gq*wn-)+5C@ydflksCcA3l;ZYbhB`_r9^(fgy9(VRc;M6M_;Cr!uX_(W!r0@91&<(1RJC$#D`mK?UoA=L9uc5t#sRz@;MWLv};$nXD_88 zRcS*D&y4(V0U^H$ZbjVD7xuLrBn5}tZKf_mdFDm?YuX(a+v0^mUo`lJQ> z4F)O>*<^4GtF;97gaZOzpy)eWt2LTn*R$WqN#7nbWZfp5RshKq^rQxTon8HA$J~2< zXr2yfi5MV+oBNq$>|SLv&LityeWaL2P?<9I>!?|()2Z05V2IG?iiqtrU>S52(~FNghMS zZy<^rf@(JE-?h(H2Onmt)kr_N*l!o^Auoy7VvP|Hb+m3zGaSlL1m|dMG0>1^@c%%;EPrsmWqll_DgjlSC87qq-|oUeR9W-G6qncRPhd{;K1azbsvPGma{) zOQR0*8x6&Cj@AiWPKcQONQ6$j2FEG0apb^z^|i z)`19%+sZPfVB|+1__gW<3BsAM{ql1GwjdvBe2>4d?zVK^5gvJO(><~a2`O<2Y9CBY zWd)ZO1E%t^Koo84t|&(k8>$qS1l)cHKWZ=bu-G7yGg*yLL3`*Wg@&q>?0Y&Hn%|2b zp7w2jbnWCVg1^>s30sC^18}Mvg>cFrDGbojK8e8t*0|qSkOn;g5mfHb5F2dI`M4r$ zk%Gk1n3a@ZGk|m-?fZ_CNk6;{s6ETUGSw$Hv)b!juZ*cvV;p+Y#b>htNmw4C4*2dB zwLnYI+mDHM4q0G<&oB|*nnc{f44&3aD4&N4na}oK5f-L?Ir8!f8?SkDP}axp8VfOu zdgQgC$x#bAOFX@}JGKV{u*Ux9s>Q&S&G{WvBD#(KIO-!=VxU@pKOzhv*q-$>C|Ugn zS-T-H|NWr5<|1OX%Ca9HPNZs1yJpSBq3KnGdUQF|v(5dc0qSEK*xyQ&xn{B^er5&2 zcBl2_k6(-)Cd|(hI_lzvW4bK$V$Dk{NA3L8XqK6u_KAe1PB?@$v6L*oQxCVW>{sZ> zT}0>_$*d`vSSQ35Z@{t3OGJpm7JiBIDo1^GKx1d(-#R~m@-xBGL8A1qo$pA;VCl}? zGluT(9|J|$O}W2{tOumKR==Y2bZ0_Tk{)-x@Ij_T1u(S`<&`5B@qyddI@MS&r@)-W z0-x7J79G%+gczlN4_BMK2osA_f=)Uogu5eY-#aeIK#@ppw^is)@SSF^y zO(=V&t*)py-9C}AP4kCjN>|cmEcBe(eHcJznsai1r)ud$rsziiigipGUfdTJ<8ms~ zq>m6HF+31qnql5A21)@oHP2>WUBM99wmApk7u(vi%H*IlD@5iPQYHpdrdR%2ck9v@;*{dfoI$A-;9#RTYeX-UrIvKNB-%`CqP+;Om zql0hbbR)~(6uKGpZ8Wy&R&Y|&W)rTCz!hK+QhoiniZ6X=eKLylp6N%LHX)@uIZ(u; zrfXECsSX`7#?k-rB>SO>|Kw!Z1pdFvYN|$`S~89fyZ)|A$%=82i#Dl$JFO!n_zaHL zSz~SsF$WD{n=!=^VrFXY=?lLHtS{B^gyR+3^K^t4{q@2aWL+>WzBS-A&>nmmRJ*rq z#t=M5SR4Fw_n(Y?Sfc+0miyHz52$42nEmT$@OghTZ<>1jQNI&!b+&K~XweRWkLqhXHY>R+O?TF5Pq-+o}QlPIu&$O7IL3%|=}=#@V9g|h1n*wPQc z2Drh^q=ai|wn;bBMtNyuBTS5vn4Vt&wN`R>1RT|`vDQM^j7%=ZDPz6LE2n1f>mOMm zBe`*D3o~39-dLx1RUJPaWk1EXxp$hq?k)WIoFO`xxPG^G&(6Q8^#gC}Z#UlNd7O?* z+wFtaV2^b9%Y{i&v)?68_$M^Jztwgw}vl0h|lMsGBq{w@X(5$@5=bjP39n07`ru<^bH1{>28SS=ySya$tG3 z>owW`j2_{K?tufr7sBh`b8lA#=YJyw^=5+WidL+8qgQ1DaN?-45s64;EXIwwr*tJH zqT_mEHeJBwExVjme~}2`#ao^6y^h8M_8ukN)qrrZs#ylIeL7rF$jj#BOLm8KBhx9K z=Rr9KUm(iXQzx(|chhghl!T)B-5Ac6RLiTNBJ&A8uvPQ61yehL7Xu)1x9X^25feRf z|14OKYiy?Xqy;KN;XyLr5G-x`^y-f#m3Q+=W7EchtI6v}hT*eSWiwY)YypjISR7vp1Sp33xcLTTq5y zU~L8`_@lT+&9RJYw0$9_nt+q4ORqYG)cm@dK><1mI1k_T+do5JrnYx%Z+-c~<3@=j zG?0tus~JRj!P2O1fkXY11|kc(>ziZ3C%*QBPD0CB$rXaU%W3!Y*w(sdZqmnnm^CuS z{dQa>B^Q&LPexR024ED#2Hk1mc|IVpu*1k6>z5z*eZeq&_Nwx|qEppIzd&`AMHeplO)B^zn`@SwLV2TUyOMX{+5_NGEP`!?0CD(4A(9(`Waynbn~^Z*l|;6V(e~05TS#N2CV_0LX9Lo{NptI| z&aN1(vZ|$1Te%hE6SYxQq*GI`Nio4MZ>(X!HPx}f3&8t#baI~Kh&?}FkF^9}E1ih0 z4T*vxu_ow#lFUS%XWUTBuX<)W3ftz!uA7CZ;U%vxZ#^@{=f5ez-m-$5Z(WLkZ2DB z6Ae+OAc0_Pt&SU!=q5DOi0}bpEfhckA{&5toMP8r5!SNNX}^>W$%3qob(JG0&Q@`2 zWFm6!H=umwNWr#)>|iA>gX+MChq%&!x9wLhl>~rS=XYa`kAh6`;}Ty5F+IO%l4+!k ziH^;Z{?Il$;CVUKeg5^4W`x_mT|UA~FX)tJADh|dE6MBliQOUfeBk^%==0R9XeK+Feq^EOUb_9JvuVC%8ISuy#N_GI*RYd5p@hvh zt97jJjY8hsPbUvQ6ftA5NZ_eE{uK4^dFzTV;W7x!xN-uV{chfpdB~(48KVWujXNJ| zBv-kA-e_TvfR?D(^QAL))SV3ns_hw0(aI&SP2ZTDTaSlb(h^Gv{zKe-#|7SD04tY# zDii>pfyniHq<_HLIyA~Cb#P04M$!TWj!D^;Jw|LAti;gw27=Pq5w8zE&S0eNve}`f z#WwhwHkkkl%m}&jOGST>D2>o(={KYfU2q8z(ly2O@@fS2`Px<8#F*#QQJYXR@0?T5 z7A5ICMBU8MxS-mOo(N>rN=~{1H*T$+2;qiDe3A+iw%+`MJx8@J8umQGv*k+9u9T#1 zlUM@+N$Mp`q4)i;!G{Wc4M6j3EFL0rRrDR4M*)Vme*_rX+5z;vFBZIrGI%1=>O37A zjOpYz{8oem9A;gEq2Yp1Wo$WCh=77ssz*ouTAN2BOvXhdn&Y(=S`|VoaOaiVTLP-6 zzc)3dI;iIMPMKh!fI$ZY_~((ZPxoGsEHk+6ZfxM?rgo2|T-sc_*^xMIe{0vQ$U0tC z27{Dx7tBk9U)}8}iuZ>LDYMW*HhxSDdp}1*<>6LMx`b=XWr*hacP%nc^a$cveM$$m zJ?%Nkj+1Jk-s_4?S?#+|BaG9Q6R_eTwM!{eEJnW1)BM8gP(^qNr+$>MGG1}TmSJH8nkgkQpEw`%6Wf#|kyt!$i3gLBir)J?$Y4?gL&kNgJ z-Cu6h-pkRL8K;{C-r5T(-&kjs-)x(cljO1uyl&yiOp6-~ObDOx$tNO%bRBp+L?TZj zfN0;!ZFO^Qq;bg9_~JhIUh1B!vc*0zbF1N^pkg}!fg;&pOot;Cq?zIG`&jS;U9bgytnEA zjPlkER=7T=#@2cJY%cy4uhW<`{NDx|pq@ZNOVy<|^5f5A5=u>1v+}rfGApW^6cJTgW$8ox5y1F;y)J>K#owKl(6k3p4?CRxfqf7;L zg%lwje#k%YtvMjm{t!PL*D(yZKprdRe;ZCCQD&0b9%qRjP8LBdh}T7{xEEtoaF1a+ zG9*2iLS$Mj2E0QmXC2EH|8aiJ-+#Uz@@F=6guLh<$tED$j*XKwxo9RB@s4zPvh;3g zP0_wHOxO`3?xZnKGvtsl#}xKXG!g~Y1JM(qyD0cJY>ym3zpuc?c?oOGTSv!4!}LtD z{A?S&+fb6MZCFusNkyJ=;T;I@53BujgIJpSVgI*?$Ac{ar**Fts!o^IxOL~|OKwM9r9_)00C|{uMPJ8Bx9KXm9P(4O=SZn)F z9$_1FlimL@!K;a=eanrsp(SP-Hl_bd4BG<#D4qtVctq`eh5Z;VMFz^p_76`6sjHV&C=?W z|Ek|)?rWhqw9-Tp$^m!Om`R@i~$M44)zkGsa_gr?tDg3HCJ5*za{`+WB3ugqOzThVM$3Hdp1Ow zmxGs@Vz&SygvzY_<_IxJRPz#+O!1ZZ(AC8*NLvFCuh~?2qL(p62jzZ@LUY6`yP+J|ZPe}JkbKE(^Jd1-H| z8G(#&R*3C{2)C$RIPo?-`p>=#p;euEUDwV>W4?c4|Keo4_HD6CYNJ8k5j(}n zkt#er$oz9iStldlF9C@$`fxjeH>Sk%!bSNOSLR56FpN|*tLc@D_HIP|Qdvuwbl5&P zV83}}d?@Z^*4tY1NhY~HOm_$q*gQ{>b=%4iB(rKU?23(YS)DYogXak}us(SK4Y9Pb zjQ|czaxtXI*pEj^kCgLC2B)~@H?Jg&jdy9IH)&wmho_(GuGcoBI9$z~9)WHGHH83w{0n$g>xTM% z$5AI0Vl1#r{KYNOudYEV*jwjzMqvL-L$4M~@TSZetx~egk!}9aL=U%)3Gspu-{+32 zOKrDzv<~1cm%Z!1c1oAidba7ac6`!LV2cp;|L);L<#1R^MG1Hzlu6zF+uGiiKo$td z1iWlRc5b4ug&W9(n)Zin_d6&a%Aq2x`*T3>(5-rGld!}i_sjMI^!x!THV`bo+{#ZR z-XTY?)^i}r(RW8`Fa6z@cs6S!B#WX{qKJ>`NAns3Ok6;Xvw@COr%`O)lVpkrJo0g+ z-Iw0y*&X{lA3d&cw+OD~1!UBTaz8b}Yhl4OvqwD1ae|zSu!qJ!Er(mjiYe3~A z#T#VmopTVe1l_Ltb|GHS(R&g3KbFwrv+6|%Uf!^f)zi^*E=nppzDauAs{Dflt@Ons z_x}ACnkS^}(@AOdyO>`DHBeHqU*OCX0I%RbIs>4z#G3 zQI&%y_@|0!Z4IxCBpcINo>uT==ChhGm7Dz}n9rGbBGEm0r=a)q~8>7XmoX}pS-tfzY;S*`~`wGPr2HE@=nkJ zIO8mcvpxjXvd&@6=g*qu^ zP+e3ut|+=j;%#sI3kZcT2$u3gE)N=d>PZUqrzI=jVjhQqT}I*DtWE?b-66PWsameh z#v)yBCToZVzaH_kf&$P}z&bnRYb7nuO6-OUT(;;IZF1@+FlOBy3>nPB=U z$Vpn%N<|>sd|>S97}}+*Id|1VVEeP;!`wFO<>s#WDHBzB23+!C5!FeVwjbQ4S9=vc zDI!i^T7E^gi%aP+6FmN)?(pj@>}W>5l*Q|9@=n0vYqsa|3@#`$+NXk9?W%!lpr{El zfJRHhz2WiDHVo#p08mqJn~b-cnmq}(*~wbJ>NKHwd;2XJhOA_e3Ou~Ehq^R3f+Xuh zogPLO;n`1=mq?wpy0I*S-LI2iK#x(|6(QQQ&P@|s5rN-VaGVMhY=;i%l>10|de8A> zw7Apz@E>a1~fN^Dh^vXI`G-bKy)xIeq9+XkV`pTz-Z+gaRizJY; zKBQ@Kp{~Fj+GCJwu<+PO2;_MhLvgsjLvV`&%J7*M0f+j5Ok&aK

    5xuPip24S-Od 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≫FGJqFiJSWzqOHHRV8l%U$R#@bOzx-)w$K$= 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 Ef@s#zga7~l literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_16.png b/docs/assets/cgi/icon_rounded/icon_rounded_16.png new file mode 100755 index 0000000000000000000000000000000000000000..225b311de322173d5456cf0ec878c6c5f5bb71e2 GIT binary patch literal 1183 zcmV;Q1YrA#P)~n;^kfunj1PHeQAP1=ENjm9?}gn zFFYnZv643&-V|=pQ`(a7v2dp;2Nb?`^q9*}4kumyaM)C%5%4w%E6HCR`^9?7%m~ZP z31-p-{pUQ+P3q&>nQ8HDG6Gmc86}LuIKxB;(aWIN5MA`qEi)>__k71sk&{?mtU(cz zWs;=G($W-b4bPkDy@=nlI%uEVwoMS?BN; z)Kz(^3)tR)4%y$h_yq)TXSuOh7p(vQ010qNS#tmY3ljhU3ljkVnw%H_00P5FL_t(I zjeV0%Xk1kohM(`;JGnEN$xJetj)t@vM}%OhM5K#Ssv;CY3Kj&ZAgCKfEs6PlAHO-%>6lE7jx6pg?$g-w|d|6zUQ3p z2teGb9t>|^I%&hkZX&-N%=sg&j~gZ>4#9y2sl5%dlT+#p?OMYW>LyiOba%b}k(+z! zSBYEIgWb95Ur=ih5GMz2f$mqUG^@982L(BjV&utvXtaq&P5KZ`SI#^u-5c*;wXNC1 zxCOk*8BC_KO-KqrQIwV5zjV5DjMdIUxPi&-`_U!1y&F}$%BPsjqxXn+pukG7#0USp z#g;~#-*cLwzh}^1W84t>Wg&-2SN=!bUzcm2)7C9K<1sWiiAWv-zic#UjfgYM{cyc` zjo*6LumZ=5ui_aGaS9N%hmBY@B3|M{(s$fL4Y;y&4hu}Uk8q@P6hRP4Lkwn{A8W*N z=TBmd?#5P>A6CC$DXI|*yg7CpN8J7)6-2=hgS9Zba*6L2j`Pj@du+vBq%LvQshh5NTku`xoENPt%Uq`F{Bvq20nW8D1NEyC41T6!gnQ zMJ4XZ!-c~H(K^-o1vj0g6uq@^4g4}u xdw5%{3esnRo7*6juS>dElZiJzG{yZt-2uixF?oLr)YAX}002ovPDHLkV1l`GCCmT- literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_256.png b/docs/assets/cgi/icon_rounded/icon_rounded_256.png new file mode 100755 index 0000000000000000000000000000000000000000..9717c8d704017bc4db9b1b158ae6f80ddffbcb99 GIT binary patch literal 52056 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ⓈXVHA3j?O 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

    x7=v$!q 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

    xESMgA*8LDD8PXO6S|$|%#= zkKTj~N@-XHlDSq^`W=dwrychzEAxf%7d1RB0002(&R?uI5B%KQ&6&IYrJ`mH%P|(a z<3x2=ia>0C;}O;XC^`z$Q&oXvuXPtzHR#Yc$r*=0CKi2$<@n|FRxsuP(oV}5KTRa6 z_$EnPO6MV(1^6X2aXC&=TCS+u9$o%FnLeWrn3)D6)433EO3H(*niG|yU!Tan9Rh`z zDImA&ie)NE`*_W;Qop4=n9&}z2C)7W?`e*H*T34l?wj`A%;RE&rwssnXrB1PXEpcz z?9cG-pMR&;OlCGgyBOKylo=ourN7^a#e;(%YPf=RS*IAlWH)SML`@$*kUl47#+=FMte}2L6hr0+#bE- ztsB?hF}Q)}7b86F0N}&&p%#i)3p5b*DDR6snO0RFm2p}pI{}k1p)ia0|Cjy z7TLZF|5HpsHd5};>V;l|yN9x~<%}U->(CNe}{PHPEJO648`hgJFI z+_6f_C_P&R7AwloOIMUFd@`WD#AiHQp9KIc0&$WLr}s&IrFW6>4LX>52CnBI0@V1b zd%DBl`cuuuZSOsN;^q7G(2E|PF#zzPdGvQ~X&?HvAL8?W@&-lCl!#z6MP5AkUH}kOUWB*-*wFVJYDyyETy^sUGhojJdxhwhlO`JZWN`I zWSSvH$l?t$U@5(!{D$bt^C(GSnP$SEoN7cR30(q;5|ih&07!S&9jMd&>Oz8n_}<<| z5U|cY1J+BJ(Jr72ND_xz(?+XpRyk@^eVe?gPXBw#5d>3dLMB+fONK@Ug@ z>Fuk9h#)=tMFuZPOits2h8a-Yrum{DrzaKnlfsfA3fj#OGfPQa)?7H&jS}Cjs(1{% z7wHYZFWeWu6+1`~gVBQP1-PCA%I!9x$LDx@+b^_7zUjZLpM3qjxCFu_0{}jl`2%Fm zeeA_%`$M<$?#FNE#oxS=sGZo3qrmeCcu~Xd?;a0xVCHBZ_)&xcb0i%Mg0T(2LNbJH zq6O0?>bz$FsRs)rN&1ZktySkD40PZgGiG#$xIlLBMZfHW@mJ*Eqrul>282bWLx5(g zp%v0E(UEH>L!rp1{qg!@`Z9B|Z9V$}2s(=NlkIy@PRiFaP}SK!q=->p`%$dj`aUy# z$z5i8>mN;zeZ@tc$n#XgB?ka`=yva2Gut2fYTo|vt9bUw+tGgEg+#HFlTqNN0=E^o z)c|V{+bx}kHhYMjHZH%ks+w4iiPZOy1j>?=00G+}SadQ=YUI{I#uJR3PwO2T7VCvo zjJ$&Biyr+<5|^|eNnaj60D#PDK!fT5TCWTP>XifUeTVIOAtqEBS@E<61}6sj17oNI zbPLoiNOx%&^pX2{^8CAa`jWf&&{w^Ga_r@gt)k+Y4wpOt@>Q^Ps8k9>A%T4W02y06Ns=EpM8e#41ghUQ#;k!v27wsd?qMMD~=M688A(S zaDJToK6PFe(kWb*804TsktK%McX%{owgU~rT|k_ne8d;AoVv-Jh(hh~`Ih2m`GFXy zmiC>3wblKRa1^)Of^y$V8eEb=>ra9{ghdM=?BIqUj39;=slg` zZyf*(1+)994W57YG|%rpjl~0}xq0X`nn!McnQao5Yv5)LSWbzXHK18zV}Bw0sFZjm z^)r8VA+>j*-w(wp08Vlm&8mD>Xa`B!rSE+ntC&uL;z9KNp|re4meCWmzEx?<^gIw7 z_m`&=uB!!5??Kfwpq_#18ECWz8qGlCUDn6$M}74c*C)3yzIKZy*Kbvazx<111 YABG|t6a2)9oB#j-07*qoM6N<$f(M~n;^kfunj1PHeQAP1=ENjm9?}gn zFFYnZv643&-V|=pQ`(a7v2dp;2Nb?`^q9*}4kumyaM)C%5%4w%E6HCR`^9?7%m~ZP z31-p-{pUQ+P3q&>nQ8HDG6Gmc86}LuIKxB;(aWIN5MA`qEi)>__k71sk&{?mtU(cz zWs;=G($W-b4bPkDy@=nlI%uEVwoMS?BN; z)Kz(^3)tR)4%y$h_yq)TXSuOh7p(vQ010qNS#tmY3ljhU3ljkVnw%H_00%`$L_t(o zg_W0Gj8#_^$A4>|b3bS9oq-uz7+_`uEFef*2(?6OYfYe_(Z<*aS{pGmP2tg|4<;r& zXf!p(n1)(gA8LX$E!tqz+7x0iMy(b`q?nqKPWdRE;j1urn2&qU*?al0&pG!FOs95F zcJ7&Ta@P8qT+`IU@vJJ>>iP6g4p>jKIVu*D+#>{R5`F_F&NQ#pg^=a;Z;AFYM&t z%4pHJDqtdNP%V|X5RD>R&rvMJNRX%c@pT}Qy*GU+ zro8mE{DQ|H4$3P}WB}~s;L7@2j}EAdiwNM7C#}~`alq8yrYIe7)S^j5goeh{`^Tu< zdy!(#GVVa87&$`uLW3zAQ3~>)6|CGu+wzTQtYB-ncns_q8r4u$u!(5{>TEt5{^7GOp4cGq z<$Vv;&piDL|5Ory1#Hw%L}g~LOK`SFT9 zw9kK#`0UR;y+m;1r`8#5TEX((+sC0CIK7X-+#uovLAaE=#PQK% z1arTPm>gNajjKHd>q4A>0|_gMV=_MczblRYa&&>;jlYzEk_1?9xgQb1gcVRtDr!T6 z+R#U@5L7_jtd3@P@7O!EhebbeFg@yUW1W7ylGAD^vFu2xo7g}QaZ~zVt@8^u1K@!i`E=K3F&IP)(n{_$Hm8I_tFn57kxuaD`IiASSInxD<{9jC3g}B9DDD1Hurs> zn@TG%>0H3G-fGN&b9>O1>1%;4#ZB}TKa!+gZg7qROxgvlH-XXmX=?7jgbFzXOO2P$ zJkIdMah$rOJbuvpVu(3I1^Q+nl|21dAiaGH$Q($IE*$B%@yad0}h!L$n9&n3}k!?Jnz69hSqUEJf#CB79SZE$G#yR5$UCEgf6NC?DPB*M0yowGX!aP{Vi$#;Qh zIva>;7zq$UmUiDyJ}5IV@*`q(emIMOT8`KDlNWFZv4R@q-nNY_Dcv$N@mg17G%E$Z z%qB)%P5>HXUa`biws+2bmxbJ6!`H)V>Vlm*&Q+Hrieu#EL{zCiFnwNHdb8#N@$KoA?r zcMfZdH0sFy6dr;mAcQ$>Ygym>1f4;JvN+ns(H#`|%=}%163}#g{UFn9yYmMiiZb1i zT*pW#ZG8hWK5|Q{1#ak<1at0&Qcu>iUS5?-aRqDpp5pT8D@-;{b9480+H#99>48Qq zP0^Wtj@0^j+|YAPMu;M9{YQ;-t~scV=7~F=!XAAD_wI{{L5tuJwTM~@VHY>g{R+MF zAEOv_W7J|aMqC421GE7eC))xJH~eetk%u9gXzHSZbgVigYNw1n_UK=9bm-3J7fk{@ z%ptidpYj@F|o-aZIMDpM4=s5_mlx zc-E`meH`#mjpew1HEweWRP~N}=|_Wmk3TeQk!6(nDvOhC$s%UQ=bGVWaPRYTvBtr!%0Ek1pvSz`rm~B$jAZ$0JH!_S*ia# zvrap`Cb*S*@yKU}#7Cp>%N!33k^S0tyF(s49zu;nHHB*d^JcJ%XupQU?CL}NIWK2KP>lO$AO+SJ* zOk^NdD`ApA5HipVyU&IpnOB;PAz9i6G_xk&e*6}E^O5B1%U)9Z_`7AxK%p=vdz_tHy&0 zRw=;(nQi7IEEdjv{kQMdfKvy`OJ5WjnJm|7Lum<<EV7FM1AKiz z89U;ko-g`N$b@NW%uIp$&v8Y%zsbGcI(i-}oj|5{MeEKecT`RsG` z!plvQtsx*-&T+Ui`HLz%83<|<0&w}?QZ#Tw@n7v^F`tDaX_b*}Ek+0<7$ORLfc$0L z8f&ygl9)5(*r?d(%PIJXNI)<&iawBcr77eS>fTgfauZsml+tp#({f0UDYU;aZ`0S= zVwfHoh$B6xO3a3`X4EhAxjfdJc|8=nnhgnYSA^LM!v5O-fHr(5bsq!7H$XS58NSQu z$qkh<=jmG^3F_|q4SmA|nQR}CpLgItpCR7^gQXOh6tHBqXy>FOT&-0M(9=m0&hYgS zYy(F;ULh5d7Mr#Bt)H6wvHsdyPfxm!UIq6WdRF@gkzDeB3b-G0)ojoCns&E?G%wy& zz4#MN{!_c-nl)ZbS4VuSPe5xc-nzDpm9zkVfvUhmMx5ZrQz#Pi@*;tE>T|HOA?i#3 z_CHb7_tK-efi|u*DVkV&=XfXaZC53+2$eWv0cBEV{+XyM8xqxij0rS3&cMmUx!qWn zOuMos%XBLsZ6tlR!!qRoe~Rr;TiM8N+86PHKkQXwR7w;Fiesz)00EzuQ)RWjYrpHL z5&K)2sSIEIB-VT#P6G-xQsr#$a-rr#8<&2Os5CmRHNQb+>3WKSL2r&cukL<|jvaCk zN1Scr?v%M)c43y|@@WP+(tqIb=XiCtj0}BqsSw^U9ri0mx=3?s$12fGZm$k~7k|rP zkF-e*`TDW%-xjsl zG-9QOUN>fkf>0}^+Ps*vE`(yG7;$URa}j@u@XP`n#NB!jBGjf8;&3?>E`gHwAPhyB z2hmEYhgO;>B@dihb#D2Q*uA0K{hOF*jh9#wpD?U`z#^f3ZS1A{>mJZEZK^p~ zo?hj5)nE4X_p{sU+Gn@trOz=xZU_phwpc70F(Vu1_5NP0P|s-l_k5+LU8^DP!45%8 znq}0`L;Vx5)W{yVU{t*U9kA6cI${6r&jcKnm7`6Ib?V;;)C(vzGK>Tpo(9PIbH6wJ zk5C1L8Ld<34_SlVOsy3AOc!iP-22Iw-=V9UDcU?G{oTb71_c=V*S8O#vV7ycW(A#Z@AP6$<$gEK^Z=F?pJtv04Ls=D!Xblz7$o;br(=nI#E} z0-b2av{zeeZAx%;TjA3jmI?|z%6yQ@sqjcLtr`wLodVNNP zGP8R|3!*-T3U-@N1f>O|#;@9I#q~E=3_gN{pjF_2w5d|CE23e8gE(sh$)^a7HCI!e z29goe8cC+QTp3+=htZKlWfb~)e^pEY{K4FfuSJNJ*x!F8cHSiYPVHObSF53r2&byV z+&ic$A>BDrHTc2Pmt_2q0q_t9{ z`qOls;Y%i;d;g-fLU>hIKrTQ6Xso}*x_2XW&EL`mUmGQ2ra*z`euS61EHR#!EGjo+ z@au|2=f3Oi_p3349rk|A8?bGnXdS+>X0XX)^D8KtkSieQXN8??1ng?iAYh`5N?}@cm40^UdhN^_4fss`9zE0%rM0|-2_fMbzoz+q1 z(6j2<3_VQ5y+HnFtdLy(Yb;rojt>C& z;C-+{j9{$7Y3QMdmU^Wmk z=eap~6SNk>3gL^Kp<$<7zM1$hBNR>`;j9V$8#|9Go zN0MC6<>^w9a3}KQEO+@^m=zQeVwznnpfoGO|50zgO(-MZR?mV=y_tf#0{8Le7KNLJ z5+SJk`ymI0=t-HWwr)qw2S&m=qN;k4Je?@FFMP^`Nv!{#Iyu}arw=B6CWoKc72;D` zh_2aQ;w>+8BqgJT4|n)I#*n6kW7Y&IBVQe&5;`2i9N5Y;G99+# zlnWfTy>IFMU}2=+wQ4h1RVq4Rl?-;gRoF|DRpN^JJ)bQ&&Qb8#5j1RaCvTk%l_;W1 z3=#;^Be$xhO(L0oRA=0zBnsdQ)5A0Xe{iW4-e;+8=ri)p>2zZlNQ_mz2x|Gbk!xvLUX%M8vo1OpWIr+n6;Nq<&9_j-eHbn)UCAwYc z0-w8*d($vM0`znE6v%6cDIs$#)+5J)Au{?&?y}m%VIr`X~-^jx*h+1e#?&Q z-bTZP`k$ddfBEO_e*#=^=lT97vKk4JEuolKrx*jgOH z)IM$4(dHr)`3ZQg&ci^!lXWig2l~qkcK#v~LmdP-19o2>jPzgz1nzshU(5L>0s)Jp zbAGsfDK49lcM&kGslp`DpQ@kC5L0B$$oTn~v|K&)ID*A(p8P%NF+ugqoQ>S95T&RK zjBiqZUo^8!mbQh%qr=Cwuc|7ogiII?r8D3S%wm8-n6v|Slq_7ut;d}Hu4f32byoxh zzpP(vUqq;Wxw3Djizl*j?%T1SvLjW8gPZgcGH6A$=!nZ$(|#F;NcyC{E)$x*S|zdm z#xR|%v{r+l7P^(<*rS6o$uPY5f*+HbxpJHZGKq$hmi>>*1%~BV(i*W6lo_Nra3ULY z0yyeudnw#@;b_ZVg~jHl9%`@GlJp#_DrF=Wt|S{6iKw=_s0Tq$ru;{9EcX_DE@ewZ zuw>s78r>Y)vz(SjW7|*e+8bU}T#knTDV7@$oyHV-p4`B0fk{k6e(zTr0Azb5c)HgI z@7QciK-|F^6M%f?{Q;P^`gxBm;51;$$B;^oEPfE5|F`DQqj-M`+qL_awAf5&j3bQ+{}kQT3#jWz^309 z=pnIw)PKl7IeV$fjz~T{f1?%vGNZWPug5&7vc7yCh#>IY&OQ#5FG<|!duZcDRxz)* z4KYyFi$No8qe<<3vJ%V{13J02TjSGG+bwm&g4!Pmiv%l|z7l_|6R}FS8qgvfkKl+S zkiKxOm&cT%Hq7aaPg)*w63D!^sJF^yWCe+&b9-9*PW~Q@vR=n&{3e>tJ+c=fHoi)1 z*uV=%HJ*@5jOiwdI@W`_jl^O1chc0uYTkt)TRPDCDa)J$9ky?4m= zE-^;KK-2D|3*NKz6bh3U%J!$s2Q!ybHw>vST-y&^yS8Va!hWX5I(l9|C5gq&4qeRc z@Tv>homb+vtT-s1_n(-&{p~|`@6H*~%zMl;&ygjj zM;x@Liw+#2>B#we-ubFXHZ!+kAP7M4>+%pFnvrL1NPqYw?vHhJP((9my*X0wt2V7SF_cIaCUF6|C&&^IOZo~iE`FC z7{w7up!d^rc7c)aAL3i~xlo6z(}0_PO(i>g2^0#_PRu_Sz!_{k^8;(~pK&#(Mew-l zm+$%{J`bl{x`S~7QM|w_k)1Y<1Zh3+r<_$!WF*o-5<28^0U40Uh*d>-k`8;^p|Zmm z)$lb_dmDWuy~7xyC3eX*${H}+eu?t0DA2Q|^Dx zaG3LcT)$X!%*Neq@MHG{UHiS!PtQu~h%7<&NYeuj0KBOzWbEIXVtW4k!P*n*)b#@R za~BXW|b|5Q`wVa9L{C_jw6yCic`wkX;I;4gDEkRmJby7mvXo3yHpIbkOT)KK0idv5I%H_ADJ`%LUa@x@K~sA zcCt$&xlBumDvaKt5q{|1#M(l*fJO)lJV*EV2>(c9a}{qBw_Vp9;9z(i{@;% zLASkn0s{hDMlPmKZJ#K_7-j=>bRt+moCrg)%@i&;qckgg)mm`MZ1k?sS%u@2{M9St zYRYPIb1+HAj^5070p8v1DPjV@qI+g_qM5VK^WwtetKiN-Z>K<~;XXz zFW+7<#nS(j$p>u;yv*E2-1Jw9qKBM5WNJ5ZRxZK0d;94$?@GaHmb#JnUVq8`gZ1>* zPSGMkVzT{76j=NEZBLuJvU-(?X}{VVjnj=3n5yt`xC4du*Al&HVBouzSMq1m$edbo zzw(B00@KE41>mH`MHNF3*7@q$d()oV@`mH5n__>c(nSj0@rdR# zj77Kb~N*Ig@9XA+T2Wy87M-+_ST^R z#)s_1VMOeWKy7gQU4IsqO3bRb?udr`p3C7!(jY*AU>Hdy`+wRpRzED2u{X=L5Wo95 zJgnT8AEmye_v^m(cRP=3cjzfMU@>9xo(1hV!>;~A_I8a&8=0_gd8;eNWt!*{^+Q|?U53t){IJUf)}>x`V;P#xwJ`dI&w#CFV0 z-*wc@SX^x9@}QVszuVJi$dGjULmo%Sb}_uob88s<6rJy!rZe3{(J42v6jg7wJEx)i z{^hiV!yVq|SJQGRywvZ(R2N{CN6r?CGH(?HO%Y%)o?KT??+P%?fnXx1N6| zo=6HQ@EMY;-KWi*WtAfzrkqUmdD>M>l|ACgF{|l~E$6V-PE7{)kJ`_2BE^Ih3X+3k zqCNC#%I(NE3CBl#e@NY2lW)DPPy{=&Y<7z^l_MTR(neOG0I-!Pb`{nHVdh8eXl=4m zh;|qkijml^=cva?*j^U^*E3J@L`3q%YMl%1{va$;)}z8Me4$x&a>YZdqsGaB-`Cm| zvqM7|lvzB_9e8+$UaTvhGhoy!H~?4==`=cr2a(r8_Gjk2-0-{unx*@*AB1GXbTRAx zaoad2F~=U=u{+!S*3|51&9|G^$o#<1_kL~G$AR6vg}I@#fh+AE$NeVkPXLfGC`V9 z{+309eE#c|;L*fg9y3=A5Z$YMe?_V9an4fK)SVQmA!fd5@VaYZM1V&~4pFANkXu{Y z>uYw2;D-FnG}61hp1jhxp=p9G8b02do7aXx|8-Vcqez-Q@KG~jX9~?{H+Gw?{|z632?tWAt1iJ($vf~b813I_m9RhshkRZ4MJWN58!C<|ql ziB=Oy4dWk8Ini+ zo^54Q2?wE$n_mf)N5#}dVNcB~`*POVq=+HB1BoaI^?LYN^9V?GWgct7Qu$P7Qt69# zl>v7|gY3b^BP_M20s~X0@vqs`|0$UDX7h%PR3hWwfNn$)V)BrP^J;F#2|=X2aN-Y; zm*RxZF8sz(zTx>voG zD6ThbfCXmK{nca!YDcULkM*LnVy_>gHKQeqwRn&=IA|2hYPSz)-<$_2&=w&inovAO z!>oV#oY8#xalG6$=8XbB4**LOCdO}%uv~puPWR%`EfSmLK#Ety94*OGgGnle(x_Ab zrN|H{9#y*QBXq8S?+@vVpz8uZ{tY!?gT4d4>w7=N%rpaef&xXID>P#go{0-qWS(}? zhK@chSUJp!qx$tbZGggv-*EU!$*A-YHm>;gwOXE>$2XJ9$ire z)BPeSsyq4H>~=nw3Q~ExRR5_}wP>VA%#@=9?aVLM} z@@@X3SVtRNQWsN$6WQ20f@gi=kNkeJmxPoLZzU-q8KpINDYB(!q$=O;&mlJZsfdE5e@3EelTm?THb@oBfaTb+$~#0+rmSL->5An2{yu+sX@p#6dv9Bd0$8VEbJNva zZ+%c34~ZvL>4sIwy=p0|pyH7~(JKc+cp47f%v(yVG?g?N_aFVcZ=`(k`PD=J*?NFa z+wm~GR04*YKJE+qH~{7p#(_8dTtovtnY3H&ZiSp1Ys+bxYLg=uTbB>9zE%uAegGT8 zy{|}}x%h|^9#D~&+6W~MwSwsLX69Wx4i-?-#GGb}-bNh0#OYgcsgyB)!NXf)@W2EMRWdgqZ0(zTt$jCqpquegpb!pPc=A(R;pqqE2J^`SxRz)V~A!@ z+rA+3j6vejz{T}EtcUR9A4HXQxrcp40DZ7gXFLwujrKV487>Eg|NZg@^%Hx|r?lIBa1qc~0z4?ZbLZQ+xm&z20h%Y+*c98Rt^*TA^zPwNbeAP$DAcERghBl^|b@EY>Vr zi!eh>M3!nDubYJWT+A@i7ysbUIB&i0=BV*N*@j4&vHC;!&qv?2w=qY9+*LPi$4J{b zOI%LKR~Fx)o~Tdnrp-Slm8=U^>ikb)Qr4n|e61b(?(P=b>tyZLW2j3CH%ZLI_s%le zjrFLUzMP&Cb`Op{9h4^t#H}v9lhgh=#9O($g(W6Fesf_m0-Ey`{u*P#Msis6ghUS3kT@WfG~#o68_53n2<56eS0y-Ud9sls>cQ3^}?dsQC`TfC5B3O}9gT6u5SOot-3zoFuj>si2BZuA*A5!h6tObFBqDqKPZ{ zxO%oSmUK;3v$?${8aF7P=5^&?{@7b+b49>byboANTO6ZO5-MXXW*w> zi2KX7F7dS2+H~txJtkmb+5d2Vca-KCY4RZF#gI>^k3ov6VfM1^8y^o;Su&cM^O;0`WZmwqM?5bs6pE}F3O>srop)mnkojz%Z><#U)~#(B zr5Ur9aXP<@!|=-Rd7;^suKC9L>rD&k#ZZeLV6OtfVX~z=EbxeC=}&jdzH8TC^X8ou z&Vu-nUkw!NDAUm z8ZmDtr;DUt`f@DrRjz;-4k(swAB~B!ON1zP!quRcM+Z@IRvsc9{-(Nw8~?d44;UHM z+I@?&#&ReN+KpZodz2g zZkdB1+Ck4G0o}sYB>^ZmXsz1sNW8QbOG^5W#y!W#s5 zn2pbh)H76>JB@~0vD)Ex#QD_7=@=SjZ89JMrPKyublVU3+xr0#xLb3x)o-7`SNr~V ze|vQ$@a}x3l5^exa#vp-@_p`Dx>~#5GP|`Ste6XbL--mIJST+;*5o(qI@JyoW>GFN z=n^!m&k+$M|9=)>6HmaG`{k9~pZgc0^fAHN8+#64rmC8g{8`-kMW28LaWjV53dok8RBm~R;x6iIEE5pHX5vZ24=EP9bg$$ z!j(pK;S-0G&3+$w@@bw=&rDC=a^ez>IQ)>`21 zO$ZmOd%-b-9ltR?cv)hlQ>`YGC%#k9)bgjIR0&R@Jr-Jb*(DS)>dL z;PMo_xCR~ii>o}HoMbDBKWf7*jjck(e*mY|iU56%k*^#9Mz|}P8=pk(f>z*a?{d{g)Dl>+9{$o)>I=IY7PSaX|X8}?7!BAc??ObcZBka(N zyFW5yXeb4+Q`=9@S(2uBKtdSG(MPSU+4$?zTIhb{kx8xH3wh%8p(PN9K@AxG6 zQGs{=o8!Fc5cyzo&3*SUxsJ%W6Myny01l`|nnq^*&eRm2>^02%8uE#WFDTe23?Ssd z`NAH~0)UgZrboaWwxxi6GHvL#Q`dImHA@HVhKn?l0eU5&2Vm@B{KHWd?@4U%VsRM>Fr6O_|N7F~-gW65nkzsV%zVrD2{!4EjK4Ws$ znkqz^?ZZt3YFXFOkgr^U1B3K>SOKRgNIxVAJ|~rZ8KCOKEvcgMCIzz6ih3uW{?ffO zw;yT{9NBl|SUCsu>v^ipx|pWWi`X0Hw}-kORP){c>E*mHjpf+iJk;9`M&A!w5nAQb;VfF>M4&G@+R} zZ15HpI|nMI_D)ah1u1g3Nq}ORE7%8kUZIs4jol=Kr`cX{c}~!5_Li+is{F)A_ddQx z60eOrVkGqE^%Gdi(;jau8TI)+*x=VQ7zuH+Di8sE=0}7_zC<&_D{p(?H2oype^6k^ zq<@$_2;&xMA6EV1q0dRMJ8^$nZ^VHMe`ex`Wh;b=k;FlI@+2B?u6eoLGR|_b zy&X|*KK$Ab`d3UB3lev`Ox90=`=X?SP3!LnOpQ`EOsK9b;6ifK-~5#%d3U)$XXa-e zJ2g`FDG$3$*&}xG!E>9w3B$p$@uF`F*p~HX zBu#UqL+`2&%ku8YyzTtbGcMlUF=GlMk=7@7prb4it&jJ`m~!M{dyoK8Mx!A&2X0(m z{wJ}iOnxgkm;Kr*)ugt3PfIXE?1XOIx8;Y}1(CAeQ7%Bk4O-{KY`!dD+qVQbsrbj) zX1A8?sNb%u3#6(K!myb`Twl|vaaq62AK}2gTm&)=3`->mxn!s=Mx}(`T91@+ecL1rqdGy#zhcOmT z&Ip^z?{vQXeh@R8|;wUS}E^fuN=P(0hl&s_^fDc{-r%`gBlgLwU>YXz57cx2 zh+jkce6dG@AB9<)1GlDYd^~s#bI@d^bFokLdHUYAAAQv>bJmlCJD-RG%*4VD8_Bws zB@0+Y8t?7*+K9%YAjf7Sq|c_BL^h^=`vJwdwjSSi9YU0{w zjIH|Ou>07t|I?ts%rP=sqPqk|g+MvnQ%)VY^V;0tT@Ibm&6IvMBeP>R){Y2r(LVW| zZkpsR`(YDu&4+W&0Nbvnri!ni7pLwi!Ei8iL_tp>d;Lv#+ezWB+J8CmbV$=yYr{E z8&03_Z(?#?f|YCeDxoS_w<7N49r>w#EsB08jv{`K%dmw3BLAgd(-;5^$V(5_dUN-F zyX)&{^)W%Fk(8F>_kT*L*Gw}vA9rXl{cZQl+17w#O}=~7OUd~L(=suN+3ClYc~WMoJ+Ew)UzgbOdd3f}ygzzWA93rO!5RhTAPE!T8L`wt({yfNZI})NFpwBo+3DWdo zez3q&fcWy#N{O(VleriXNRQj6$uL#=sY`4OugNL}J%3yH=KObbb@0!@J_meaIdr-- zCbq_JyggM__CnnQ2QN7+Y$ef_sbL;Qx?EE6`HSw4zwxx9Ll~pY3-RHnJv?v62amp_ z4Pq31gUbmLx2FWrjQc(Nv=NpAO;sY@t@N?<+4jpeAb1$Ba+cA7rA(x5Z!ABe47}R% z8&Tr+h9L9%;Rb%vaHie+MXd%LI93=Z_p~;fCo9q1=!EI5xeouLSYI`dbj`I{UK02? z`R}uO?a9?|cQCe_oelASV-B=`RiT=uTeiuTU3(+`7z>r1)i^%S;)C@ihES4=V&57_ z{o?DDRCf+Q?;6bbv_h3*07SUF{A7`Fj$lwgR^fMy+;LontqlA_aTUNDi{dsMR6dU| zbe|UaT)gY!2e0;d=B12r{)|a5}{j=u6Bldo$K$sM9ck*}t zPuky4Q$vWT_taO`YXcQO> z9W`h7i7wvZ$$__gP%a=&7fr0~!!VH5AoD<&Fvv2aoyjLkY_eqw+-`&m<-#jn3uDQo zNS-x%+N}e_Eo|XxZoMM1dtCLFjV&iJlUgnh*2>j|clwn4%39=$bravWI$F1T|3!qK zu*Y4)lU0nWATGZ}nFTYs@(OqAQNNNNCpkdNnMbDyWF^+ipqVLW^J&>{fa{v(H41_I zbdQ##XZf-l)I5NHZ{+(Dc>m~48i>ZBnZ*YaHTlLeZoM1e&=po1aIe=xOnd9%{621< z?$NRUl0$smx zi8SiJ?d?7~hynk0t9|UBkMQ2Oi+x|@v_4dt1kPc!XEI6#74&o|xJyGm-$KEy6p?1e zPvmh&g_y!u&AU2p~CUxE(16+Gm0 zE2vL=?b*&8ZEh?szTdXOr_N`W?U9M}3p_5B((a39`}{fsGO%9BxW)J(IcQW!PuT~HR@ z)h19*9oT1PgfFN52bLjcnyAkbQ&ae@=x3BVVcu@ulWL^x{K4eP#&^)NR<4nA4ahZ4 zmE-Xv^-GHLETe1jWl zBMEiG;|W<6xL!Y8LV3f2?T1vq)YzT8T?1vx1wBa6I((ewct_+L;S@Dmz&iUN&4Y1u zT^ZZK&crNr&-T=0?+eQhqR#jpsAPzpq3xa)9iE_%V`c5@dQqga1yh-8D@w;S#NTfJ zD`D)vYrKF@)!1qFir?LZawmD3UnTD#s9R*#yGnxj+peEEa zlnV20#EU&lD9-e8KuR$(G@=TXYpU}J!I7^3)U9R9;X2&G!a?VFeaq--gQ;HgkNpGYl(y|8^{_VC`2MAYtyE?KZHj`s|0(jd$@=`n-A{?IYchgr6fl zkqnL>%zgwzAjz7?Z9R_ZROe^Wca+UjcE zb#2O>jnYrbk6o;bE=IuC&q`pzkf-2^iZ#-8z;;{z-lq4zoI$w7HhFtl<8iV0>0rPP|k-rm|tw3&swE}yCSvkbX!!Ao#mDs!3C!r@+W=jAC9 ziN^h8H^nQ7Hq@vtRh~C>p9FN&#H$7mR>O5DpFwyw)-r>xXuFL=WP($2R3XYsCMiKGB9)yrGz=2;`s;fdu@0XwUzn-H4FCtJ4b8 zDP@}!C~W4bUF5ue)Y&&HgI<2p9z^bMjb%}-ly*A9+s(=z{^)>!k5p9Z{ka%=B1A1P zsLSkiyfC?QeZMS&b>1UrdgLzIp!0QpP&ge|aiw*=EGWf@l-9;&@~`gu$5`0g&a>aL ziRYW?78TRe#23E z3;nb63V0GY@QYD?$7#+4ke6g*n(zO)j)LfQGbAy6N%K^U0RxYfursEBa zp>4hsPOpxxVjJ=)66JFaTpszKE#SbM^CbZ#7m>_)a(KuLe#%>BXbT8t7SpE?gkLi` ze*FX@DWRM^_*-j~)G}se;e1*FNRnN1C$$X|3WK{u86HeP3En>c|e9?Ni0t z&Y}(#*Nigdcth(G7#&Cl>i+^7EWE8do4-`!09;T0Y@XbGyv1PYcy)+U{JVsmX6%LC zOq7B<@hmX?`xr8V;LMim#td63p>|(pu=Ri`$3c^}I)zp3{I=S?+zgM@MDp$?cA5ib zYj2&Zk4=rCo1_iPvE%c{Y0WL&o77}6YXq;5Pd$9ajRB6Hy`M}IRpv9uVx2G2K%odg zy$?u!VxHW7Vsw+?lKGA?YIzxmjKBA3fd$CbfV)l9TMHg^ZvK!;21^x2zO|+ys3roK zI^F-}r0Y@Q8RrrTqwza(gm#B|=z1-;!wpKvE0(aM&ov&p?_(fqQ=iHE-t*Nga|$<_ z^-MY6knWoUKQ;mf{$D)turzGng(J!S+u=3Lvr%+IZjoWfIjrYTY>J=lgP-Ly+&vsP zTtA1jhR3z;Z~SL*Hga#h+=_bJ8Ocxl_G5v>_xFWp%rSwxu5{&ygrTQey<;VCIo(|A z?JyY;e*wCIV9cW^LAv4D&6D@$GkWX;kC6ROcseQD70E%;gEh`tB;q7T7s5yK7Ruyk z;q_8ZW-|#Nk}&#CVJV>QK>;V3W2#2Vz@1ijhKYgYaNvm6Gv?uKw>`xr)@lAeqW2l7KVj@Vg|G&hFpUkBGdi8<$o)(1t`Wq=rhu z-kND+oqxcDqFRz1-Tp_f=)td`PdgIZ&U7=@BE>r*LE*PbTC5lS;zN+x>&+eu;e*Fi}!Q@NlO#B9v3q8R}I--}6SXvJO&y~5( zL4OoIvF!sWM6?Fri4~aB0z$+IDMk-@lhjZwR=qO6aS2)pd^PR6OdF1|_i>y#0Ngz6 zMfhG2i02>?Hls3yf`F-|_kw<17i7U6JhV3i^_Rt;+0F{#!;FWi?$hu6&j<;7_aabdf7i)(<1-hSg3wdO({D(^0qfAxV%3FHee8)I)n#WNEDS; z*DAeo9G2mxku5Q*_fP5lN0;x*^A@Dg;?X>Sd_<B6wA>~)$W?R;2 zkU^ftG6OTqlQ^oBi1q|K@Wj|D&fAZm3k`PO=Up;Ht%HHwLS|>=@RDaGKytJRsa2dc zae?yqRBu*liQv<=Q5}kT&U{X~8?QWm4qYUuU!k7yXCbG~%TNgebd=fO1AzU@}e_>eVB7<+05e$mtt3{zFb&{B)!3EmJot`S? z?-G-zv^3z1)SyVjkfZdMwC&{2NgS3(N)R?4ZrthuQ+2k4S)zq|e296lAJvt0G?z`C zt>BpC;x}@zjerq}#GX_W(N>z@Gh_(?B@;_(Muc=2inb$0h2-U-{%6u-iBCe8N#+Uf@W+ap zlWNGzftMwR&FeJA8f4Fi9_YOibNG=6>WqJ4is-l7GekQga?0|T8_AFDD8em@CIsSk zVT5cm+j$=c`S^qehd?#UUB?@Zohr53br4FfB(_p9X?C+fNu=V}f#s6L=C{f5X4pSA zXx~*8GT=lVcGz9w`NRTCj!~hMqvf|*CTmxv#7{Uv=}J(@I-o&0d9e)GpiQ&DZAJN+ z8*hMX%}phBZr8@}-v{QEfEhVY$ioUACu14!4kXOe3^+<*{Qc(!a)9Z!`So&N6H=H! zsk3MP&2n)XMS=J5z)^Wb5&ao+$W(q=)Ibb5(enAJM}00!2eHHX|Lxoq#IIqn`}_}gP(d7N&7^4F?@3zvZyWL3rht$1n;zwj{Zt%_ z>EE1Ns&RQ^tIlVQ>f8T63ozl>)CKRj&b=fzBJ=R6L)|5>1wR!fnE0ML|X{VNAr2{h;TIpTW`ekkyq|y zt;26}nQA=ubP4xpcdLw$4)4t>Mv`=1=_i>JNr;UcU+?_@S~S`rSfA?VzBD`!`);Gv z*3j;RdrbtXubOSelVwJ690GTfwpt-?#bo_+D!bt-&~K+8O!-pG%7)dA=i{c!FRlQo zLc#Urp1=+mr}+36n>#BDCle@J;^a@IpTOl%2jtBwwcAt1=74?wmBF^k_6h*B&0lY& z{Vuz!xb^O8E27hUX8YO8_`;i1VeXffY^&}Z^69(3kjZnQ|9LQf zZMU^|C=v%jpoGxYy=>YugJ>P)kt;8^3W`#CZ{R#0ck*_BB^(siQ|9o4nSGTzV!tX2 z?-S|g4{LOt{w31@>K^8K<2R`(u1WlWceE(1G~NEsh?w92Fe%Jv^i4 zW1wq!;m32WAB9>mM$I~2j!GDDJD3dgurG=@?Ehxt&4xF%it_fKKMfVutx^hari zeiE*u)dk^{dkQw~xU~y9^2pmU_w$Iq)U+$`%%)V@N(M#RJt zuJBii?L2M&BF*5opt2rHlBJEuvCp30Lowc(2Mb%Ce#Y7__iP&$ImpV|w;UXH>MpI} z^{sFD=3qQE$9sOha7AmdExlN6^o=@EOcXo#_uHJd_=t2~yDrlnwieV*U(jjzEMFe# z&pJ_b~7ll9a0G%Pw)a;HCxMRVy)369!PPq00A`HpP#{(PtNcawWkS@$Qtt68px zbhd|X8lBf7SVg$?MSKx&I!reM72h!7jpdm-jDlehI-eCtYjS2UR4)#m+)YdHb>6|t zetyC4P(x-vIP(IudA$+PjHSWm7W=|bo)JSXxHp_R-a2EmMlZ|oiWftUF#wD{_p3l* z;Fs*lt$&XPzW_SdZ|n7HfG1%krbRB!niN)LTaj`p#vMKAY;l1;%=8(oTi{A)6$6??J4B+A>jI;6Y#NKH z6Tou@u35Nm*?!Q6%QHj)Qmy|7T9^X@X9}k8f=q%IksWszjL+{^UTm78j#U{;WT-^m zXgy`U3_h5>g~?GbrlKB2i?p$Sb`7@?PeE(-_qRWvSJj;5<}UAqe1(i>NX3CcxsJip}cYv4bBSJ?L}>}bw}7K%IFl-ZH5F-dp;-P9P8 zN_%=m>qunpO1r!in4?6=l&rbYPysi@&uHDg6H!rthnQPLV>CXp(3uu@+T!Q`6$PcM z^{!pPLR?yu*>ee=`|15|lqzq&S+ z^M49)eHJ1uz9J@M^a8>RpMyH?CYv6@U69YEB|nlglhF4Ks!F`#;A+#h3vGB4Z|@~$ALahrK-$blt6Jn-=k&OUo2en?KuMAz zYAA1kAoY&|fNgDJQVwa7L8yW&3{DJ@ZkT1EwTi!f@P_igo%Lo;Ypa`JJU{opDn zV_oN>Zv->J-DrW;3>NWJgu}76TUAE87DPjK!;jMG{DnB%Vq6`m47$*A;Fx6B~D~ zXYDvoR+uXgiOZy?-{H#Xw8{6P;>G|ic7=OOdYEz;)}ee6PGrb?s`vBHhl~>UtH=kE zfK-fSnc-r*-mO5(%Qe?q^J!7_Cnb`7W08jO>-(tBC$Ams-Vk-SfmuGK^h|h{0^}{8 zV;^)HiMIdLGYyBFxx8$wV1)~fAjC_?~Yq%3x#)y!!NxtB!_)eBy@{zkSys#jl7S zd7<9&p9Z_{tz@pr6<}u5WwGcL-gb$r#8|t}1z$EWO2z6bX;S}NpRtYDKYxL{J;4ZI z#qk_J9j|xM9zoytj*6X;Bzqa&SMSoJP0yR&JXcvq`|;N(O~XhlAn54!(M{Y)b z`rf?j_}CS2;eXG-e(&{8brkF?*7Fn!;e@-?QsWNV|B_pC=D50^649)bCXkZ&e0uWj zb5A)!*kpU(M?H3ns^PnHxS9hMcP_H=E4Wi=DJkcA!rgpjR!TroBE^GzWz9XO<=cUj zYsw(`9q;Xpzw8$CXfXN8uzTHF4BOu8q79Mmx<`Th7vyYJQmKwzv~p|cSAt9({V>tm z9|;ia7M(G%j>nUhntQ!~)E<>IwC0Q!)*8@hOLjy7?AjxZ3F~beF?wV^a!`M^ec|Y zKdw3ts+B|+%&}zq)Pb?tS0@Rcbw=zWQ`!v1WB6ks)km!SBd8{I#c9tGHq*`BgcMhK^*- zM+A!GhJB1Qa|Elex!H;U6OZXvKiKhs>Rm$d=*BaAcONh{bRut0k^?zeg%4yOJB1z= zv&VY-IzG6+55)GAdi!4yuR8dhQ4zAq_s^PF&avpu4~1vH3~~Q0is*wectf&wlDw`V zVaQZ>rO1Cl+aCIo7H)$a5IW@h1%xGy2^>6tBwd6k@UJ1*wL+vk?3>T{0I@0y7=f!4G)FH z-d1(qrcX$Hyy8L%2`D5=2mEaWpfs5I<7_Vb6Ba}j=AplcrRiO`;IZ)F)b$6GjDdvy zGKAflv`@}u?rWEtq`E{O-3@5tGxHk^=Yj9Q(BtbW52fCirW(6XAfeUE-G~M@2Kw!D zyk4Jtk8$XQPhKLNUkIV*a**>qj%8++t4O_Hpu!nJIpJa@b^##DAyliU343K^bg0^-=t?Fj z1A8*QPg0h&ll_Jl+RI52~Iw9s#d(3Z9yq z;4o((5lj?Ynsu<(R~0tY`&JV!PVdbx#o51YY)JaXon)ezWMDz%M!Db1A&wT)601cd ztCA4*wThNnxxSgWnsw$dr{L^D*KzXA!>#)mb)5;mTtq42Y}?t!`NBhSJ|`mbR1kkO zrBqE*LpwAIEhrb+~^~@jn>G4zl@wUJ>e0zL+tMCCppt zsc%#{zq0dix+nO_?Y3J@ex(_Tq6``1nzh`HzbbH}8nCXisly@M`!sfkz|qzL+ntl1 zew?d4AbXpLI;Z=LOMyjgC2b{ko3joDiJlI$w?G%e%rw7P3|EW$T3$)~P_*n5ItCeq zSN!8VUB)3rJ$Ge@m8q4;0e3PBtkfU11?6R28NR{+2WcJ97a^$*p2sf{lY|Qe^f|>4 z8&h)-Ti`0iE}W)6J5|7SzV9e#@~;R zUK-%cx_wv*>eAYn^&6kl>}D@T>WTuWQ~!1clAjiDD}_w0=->db->%JSfe7LdCwBge2Kg$-={-_pTCkjLt1@nEVEs{f5m2@d0Q=T57_wEp2M9I=s3A^&(V)>VQULY*xgTuqFz!wgVfahnhN{o3Yd-l3bbvgqKURh4zoWO&cK0%d zSL%wrF4(D4E#z0Ayh=yd#_FUa!HqjqN7z2^qwSl&%@;=&VcjW|bm(!Ao*$G9%2!Id zn6uXT=qC^n$*pFX<+Bt*q`nzPA@lx_@#pbNx-D9v9&Bzo_8bY>3*U${!3Li^yPi4z zX4dfmh|!>K^W5ylDfY3?IP1+|#lpV%2jJjia`fqUz}mt1&_*o+4Z@FUrfuUz1k$U+ z#JBOJ_-ghL_x^hMd3zX7&RpZ-;&VhI2S#n%Wmn+%*5>h(4wD8GUd7hpic0|y^QYR} zd4|rVmXc1^b(e(=Gs$ZJq<+d;lkTDDydujZUfK;ZGQIY6pDItSHK^Fhj#5QU@~d8g zJsCN{o57VipFNZR9v4Uw-T$b;jH8TXPnPs%Cvup{x8Ap8HsOr7yN$EB1bjzg&ddaa z(5AbbsoKl{UHLvkR7J6$Qo0zSfI_A-Xi|bGL64RSgZ?PQb>rro+=&B^-13f zT6sO32>ragCqB$-KPn%*R|I9ka&hG?h-)lAYgINZ1$ESt7s_j!)0|oLp-f?e>G$6I1z#3hV@Yb9#&yWCaG7+(grvRP=rUu_ zNoIrgaqRkCC%M8*YYsQ16LPOd*rkqA?O|YJ%#NwwKl%JJdf2G&Ut+wSXqV_gkfHTo zyc3}2HpP-NsIIF3RhcxRj>B;-e{viTom&f*OThNXJ_oYsTwNMI!DWz!j%WE{2Lh_u3g6ocs_ra@CT4py9T>N{zgp76BpBDb)2kzh9J|9tH{)~Fc z3HC86cI2_f>Uw&!Il*^v`X3a0u+_=moma#zB+Fd4 zcrZNwyJuwD?kQ$Xf?+ycRBd~)FJar$qLROKgVZ}Mztz|oT6)cRgPx>G;lfO;58#O9x3K@-LSO#JoC5;xtbOqONC$O3(4L6Got@+&Z?on6`*)=Oo)B9AT1U;>8t+!kpI!)7l)iG{3B1>c z@Mc+FheVO(q%bl;SdET@#`C+dTTj5iGN?EX|M~!i)hc#w|GBUUZfB_5i_P;@wA`+3 zL`v}JpSyYqlF3e2Bco!`EaB(3_to!xcsXxAP|067ui}X|>8h5W48@jgMyFl(vb^ja z9a?w|b6}&2&^{tpWurbaVIC%uu{peAe~n%#qg&vY*u6O{N_4d!8<=yTufGSZo&8mJ zIhBFQc|9WjB3-8woGxaGN)p?P)8F3I!5d z@Y6uT_Rj-zny%`LyDK7wj(Ww$U(`0qfQ-vAQ9UR6+$F64mSjIs4o{aa=kHrkn-7w! zLI6<2!swq{W4xVkdTJm}n0M2=#bpz6r;$(693}v^I~iS%eoTnAQ3i#59UM{B#?;!5 z-nHMgQIZM~(^poF6?P{1ppaFn5{~>Hpq5=-gekGI+6b1guriL)Rp^f{%vyLiwD@Ji zzR^Vm9L*>t8{E<;Y6q%+s57JKCv|-1kxnsh`ZWZP$;rLD2wKQRPTUIaLS2_U*C z4Bn^-I1m1KI@@)g@qA0paz5FkVvPrT?0G&3n=EDI!=1B|35;2O7l}qV^SHtkQ>z(V z{Gx>RO^h3#2Pk|9U&=C>L{wDA#R7;}ed$ZUos%uZX~G)9lwob*=8}mK&%Oh5ugt-; z5KyAIVhoe~tyF;w$5SZ!0N!-t{c_C*QE)u5ORQ)fXm!R|cZNBO09n34`s`vCjBotW z^C0#xYejwn(2LH&` zo~gtc;n&pf*QTsTHpl6o0VkS?-PFAHbq|c}wM}5ZB5-%~kGY(*oIb8Cb2Pa!zED<7 zkvB3-42($i-CCVfUqJPmpjdW-Y76*)2lnh_7MhqrDFDre$(`REGjhEixmIc1i-3vz zAX0$oPd7LEpeh75^qzF2eBfulEEJJ~tx`5hLIjUQ-M{%~2co^aE@upYo+mCIDTu2C z)zzN^(`VK9*>v?5m0N(C>AZd2y*~Nr6}L@_KZMb0ZdhuebKqa#UVVewA+{4S&_AmI zDiNzSL=5fc!5PX97?QqpZ^>~OrAuy4kL)BW{S5h|`DH(lZCC)~a3IQJ(%h}>OTg}m zGnrrhTM*YjARk|4CKcJpjx$O4g*aqi>pdoUu;TA_4GXkd^Mnay;}>jIsW4{-LkZa` zol0r5Mmr+k;K1jAHq8H+Vl%o&aI5FLQ8-<{-7XV-%UR}P@?pXnvt4EWL0i?z0y5xF zdD$nqQILR)2&d-d|8bK;`ND;!0j)Uws|v73hn0Vh`8l2l*( zW=Pr{)|!1I!mq2p-lgCIUe4_{f9xi1zG@wqU`9CSx;3d>Jd2C^AtSP#pFJs2vyc)* z`J!coRKUrBo~N=A%YZjNC+1!eCeA3}a9ZfVK&T>w1D6Wz@3FDMaph^gTFD~;5ZAeO;JqB62&+mRj|DgJQs_|Q(5@@VU} zxhCDyWlE_;ycKWE!d6Lc@Y&9q{QlR%_=`W`OcN(bTC@Wk)`E44(T@ZZLEQ7Mf|?eG z{MM`Pw6uS|uC3l8qS>Yp?(aikhRC^VwQs|-A;RX99O602t_Dh(7Q)dP)GxiP(D8cZ z#Yy~tA0EH*S9cyNsHR%V<$JU8tNz9RcZWwJJh@xiLr9d40W0$izpYnOiO#McN z1%cGc*10GQBkr!Q#qP0Ap9NTU)gdpDp}_WMM|t>ezn!aI$vDj~ z7WP^Q4eq*N*??!lI+%S+CE@Vt+TMb+wDo-&6iv}{olXvGoTa9%?^A32gRWt;wqQxf zS2F>{G|4_LCPMGfZ$4<^z>6_%6G_O%qS5Iw!Tq6S+|)gA_(*yn3NYuJ!ogTDEPXS@ zcA0EO&*Z-NRpaM*mzoN_-?uFWD&{45&FWS7$Y8)P;YJDjOvre79&*l_+c z8H64{fA+9+GCMo5@R{hHwsit$gjX*kRrP8aUxHTZdzFRG0Pa#SmdD_$2cL58yxGz* zx0y0&hkSa{pze#kx!KIVFEyK2$Q zuJ7vNNg$kTCtd;7!oXEBFWEs6sr)VpTD>~061bf1zg=}Qu(rG+Hf~9pd@^?8f3o;G z`N12!BfOgW*PKFf>jyFU?yCBm(0!d+5{-;DPCFD$JZa$v5|2iDR;B)A|5>6iPDjkw z=@@`2OdVAmCM@1I9@OEEbQ9^~S$qd~sp+?w1|kWzui*zP_gwozAo&?>Vj%k=uJQER zoh763v=uVGzx_*bOAFLb5RV4#JsFb8<=hh?r^5nsMn9M{jznzhEkF%kr>aDuQN@QD zW3=VkRLyU!gGL9G{^c+h$>(JVC4eJi`rW@v&yDgI$-J8D`N*zM zzK z&f&ZvDjcW`CJe#41L=I%C|4qV|LoF*13B-q>vjR1mm%VT>qY4UoqBZx`6lf!Nis! zp_IvmF%+=93{)VR36r_z%ke?fC&5v{KKl{e!O=if&chj~P#9B%rdX|wDkq8rp}e*x zZ%X@1_zLwYHQK>gdUM^XfFC6Mc&pP9&UdVa2S5@-% z=1DmHTZjf3lZN;AlIZk(1~gyw;#Hw(ocd1iBc=w_k9s-$+jY1w=AVbI^MYcFHm;Cik>}G=Bgvhxop16nwFO>%|}!%wS!48}ng~SKRGnN&i`v`g2g_T+-%v z(I!C-M1)8?j-EDv}-ke%8Q_-*r?Hsb}etTK`+7fxIeOLj9NI( zih`94OraN%CZsBz!&j$AKhsOlP#&3zj@^JIDYREUF-np-1b~${&aNmL2uY|#Ou2Pvyj`F((vDYAvT zs z`8lP8cypFsuiuic+gQ>JOupItsWiswy#BtzIt0W0_j%{m<$3=`UG1@oruD6OtGNQ5 z)Qa=^M(VVjWdZdERU+MSgUlO6?!05N)2)cT`B1I5kDZzmzSbMqUqNywMBL+4P)2~S zQE_Lg;iVL6(r46!I>U++9yU)WHN7@)Mr3zL6I(xk;vRlYuQ>5M%bVhQkI*$Na?i*j z!b6tZuq#>e{q)w_7gw#D6qqo{geh#|^$9sj`Y!urz01SMNUHYg%4k&OQKo9)AB>MO zt96f#J9>$JdK;Cd8(&RP%oCMGa{Cf{=#6IVUSAfqEGQw7d;)axS91`VG%t`ws6xF)D`}iGcs3?bTMap3a3K>V5tDRW04+X_hS!I&teark?q0q@7)49%0=qqg} zc=Wc@yb~>>gy4O$Gx$26o>qM}>zOyH((c2T=JXflBeGS4gBGTwj>g{T><5Kf6a#23?FFW ztXQjfi-3??735kDD4dto!8Awt;@>}Z(LWrR)&4(**p*+J7s!6X$Z;Kg8^F=~^%pMx zUbbs77AWQ0FwU1SZmT3_nH5`=+5={5ER%kSFX|{5*3NhJPi%)CUQ-rPkq24l}dhFV%Gbnb$7KfA7 zTQTY|C8POvP%cg#y#!$UVv_71FEukf&c<^otvjso3jIH#fALySzW^R9*lkW0@qb!$ zOaJ)ux=ncXG^_o`A<<--$@QUk(ioEgflS77dAI-QT!7C{HsSsptkG&JjxxLjzwg|Y zVk(|U59!gAb(zJfMF(&?|B#)ySZ?$U2$Lv>+CjffX(Yt^#%Dk<4Z4!e4;{muo_&Dey=E8Q1W1{Mlbf2X&9zO zg%lOKsG?)lNnt3qw0|Ax+Cl&!WyepnNzI>~)bF@%DM?Ul6h(fC6jrCls8P)(wl#z; zUjI5%j~oK;y(QtRcC|8_=D^)Azd_-N(Zq?nir8f8i>oY&@_MBT$?de1$uA5|ZR8(? zU??KIbs}(~EU#!){LblZ?5IU-Jx5P2RsrR{FW;HCRx*5shLSn-FL{?iDlx<-{|_TCm2`HAaZ zxve)4SIU64jgMmvQNQ0rU#iOSo2E7j&F`IfyM}+dT3$l`$X~Sce1(+odnzS3P3lox zM7P&^{%tJE*SSAvWAQZ=@ENhiy5a~~O}IP73yL;=WNEgz5FSYw3@3&R{(TxG77bSapcy-8yLHKPc|Vha#qsebbZnWvKiHO21@VL>1pM=s!{zPjA#3g|qxf2T_V2 zi$dyO2HJQ|A}&8db<;U=Kd-3y$Dq-K=_e_yewZuL>oBH8Pi#Ak?Y6)8SbG#@hvyVU zkxPAYb4FC-GJ#6ZC0@d6Fw_5ogT#-;d;@}_yE$^a90AZ5cyC|D+*w9m{;@kT``!L6 zc4)zPZZzy*i9EDGJ~McUecnI2bcRu`9v2uDw;JUXq$zZm&&SSx6qMKCT=Z48YX-62 zTVyrxGr^FpQAn0|PM|M>p8D~}%1{(7_F_UNi>yB#tV?L1o49i_ax%n8I)w-4)5b24 z51Du28(lht`}&}syF?w3q&h!QNK6*brnwzR1|z z^XF9%+B8ZZ&wVFAX_~BV^((LV>_rdhI-l?+6MquQUW)BU;CXAe{$=xl7|QGYBn*LP zSJ`ljmScr?j5P7S&LMQI9F={_$XClbs|yqK*`MexAK8zHyr5bJ$sW)o4*lTqU~BQ-&aCMH*GCZ>F`Y)iDs^pSzi%Jz4xFY5B0m`VbzYKHy4m186$K{Nbeph^d4_Rs zF3_bbs-FpyCro zI>iPUk}}Zbj?X7$^S!q&Se&f5OusA8G>qhvp*n8?I60MC{Pq#w%J}Ua=YCL2W-1px z`^bjZ3zp#)qyoL<*QOW|$Sd|q>FZPCLd_~Gf*{hkD-dt}ExzO5(lr%um^1#1&8VNl zBcGH$(KY0*b1jqCifR(@-|~2_EoY4ES>l z2L5k_dHC#0sCaQj0dF>FTNoo}EvBD=G*yxz+@N|^?aLYSCI54|fP2$-zOyG;)NqS) zS@+YZKwNz}eJJPAAh2gloC#P)|$h=zNu-X}64s;vPXEEhgwt+}7 zF0OB9`52iBI=j1-)yL!|{Swt4zob3zUG+o355G5p-0sfV+O{AG$m!FaFEG50)PP%h zz%BeyZ3;jJ=7e%U!En0DSQCY%Kckt&o+fiWwy}c9SQbc2PBK0A7s7?pBq5DcyR?<;YtBXO9{ld&- zd7#M2ZQk|t-#S%=OYGxmB)p4m%z?7E2LMw61@bks?Q*&V6TnN4*w}YC4-7m)4FaGkp7Gb=vy?UdwE(4C3u5aD z)pHEK3 z09+mSNyZ#~ZsMM>z~eN}@5JhNn;<@11Wu#&+%sZtYI3}HwwvnQB(l)pp@>nyD3lpY z(f!aRgo-I_U7?49Q1YmB8P`8eW--{=9Z*6JEavBOJn*I$raqYV$Z_JUT!d|#sDi^& zTq3@3*Xi@%l5l+WYQd{-#PAa7-M#xu;$ig@*wnbaa{<@ulIMKk9p-h1QO{0>DyCc- zZ4N2%*z)dV+cKM&25%d^Vg8*riC-!OxExWLi%?9-X)CiuT>xbD_L9dW|8S!oxmu8C z#g9As5onaUvyf)<0T_V-OO)`xI|K0p6Mr*lBK^@$XGp|ppd(dU4t!{WQW#8*uOEm9 z8R+3(vWvUNGP)?u1m;e5!P%qZCD$Gu-d!Hq$OFI!zc&*zwlS$Uz1vxKnk>L66{B>QjVHQ^xm?-z7vuvr;-45RFxC&kjB7d`|OlI+R|Ye8<%hh&I=Ht~Sk z`VIWxF8PS)f0*2%7vZL8WuP5(m|4#buJ#LUeTvurVc9!`n4|M!mV}Z#$AVnolkgCxFuS5U_21PeGNEDY^G(K{CnqA%)5%dYI16 zG(7&ut?Hen%#WQ*c<*~OA{6M=nU59ZeC)}4Zn{6jPopZ(#xbrA?RNj!2TRr6jxC6= zk6M}86vjsn|8I^bG=5JzmZXXX5`hT%**A7bt(#_3hsgF>EK=wCK%n-f8x{3PNx|0Y7DC4cn&R8T*VQu?OT>PLFy z>b7g#cey?rX9ti>6n=Q;5$v`P2JUE>_)Hm!+tXj0-{@_gf!QQmIOAm0FaRYX4$J<( zF`Tg#W%!Tbh1hQig*^4!lnJ(8D)FQprw|fcO%8Vc8{7s75N3SCSo?7NSgzc~RRFY{ za#nh4cGu+BA_{qr*Ulow1ozE|i!yKIuro4-PquN>{cRSPV78 z=$FW{A{9hM2(Y#>E?hw0>~D_kW%I9=Z(^-quJO;2MkTezXd|dU759}*;K9xw@_s8! z>&Ef82P1N}4`9;%+;uba^kBEbgjY~jwT7IWXMQB_3b{$~xZB|Pn3}`s=3sWm*ozYn z+M~aAq_Ptw&(|blM(|sX>0K6|`AL{v<^^wDTK9eN;V%oSC&i;%FZv9jr6O29SoY|r z15_!-Fl2cj72#k~=34_^CfUzQ$A6t6zQ)z>p+GMq1bETgyTC_iA>3$x07XD@d?-Gg zCP@hOu?J+K6`}Qkm9VCIo2lzzbViM4$0SGOsSfKg9m{o@!c)w2wt>#|Rryg=?aJ}5 zEe(WH2<2`%-#>51jVW(0GV=R|uGe5tYF1sqOamrGzuXUftijPZ^JLF#7+v`e`3qEb zMQixk^&Ss~UEIX~9=mKtHk%&oBHvWgvy;ughxrXT&oXUxzA6Hvhl*5|8!InuWiSxK zQlVYn1%8mcA6m#-)vNjG)zpkjkiW|)eGo}1v9j#J%|&F2Bye@@c7u_y8%ng)rnzIW zxYomdDIuB)sfnY?D=Xu4_?@KB0)ydeuyHrUf6bnw8GnG9;l5kZcJI7^$u#wt)-B@u zw#d8m4ZF?@Q3i-KKd!^X({2OGD#Sldug@P&2JqC$en%(HNIraQL)I1yOFg)ESs=N` zp{&+$q?Mq==ZoA`r3KPslU6%F63axc5ewvhECpn%2L#!8wkBphIBa!W;{*PM`SWe^ zsoDW*l2uE2m-GeSBLsOMOC``yXzhDp67MAkx30XQW4@3z8f{tNg%~W09q*EB~dH;Ktep6A>R36>KMjJjvp&>wO* zYaB)}Zw9j(jNi3$~nYhXE-N+oF#|C^-e)mm^@)#zgfW+b#VkH+QtmHV)Y zCd2|0T?4AV+QNo##bZ~RC&X4%S#>yD+x)>~$9iU`g?D48(yKhasjx46`TYtGai4u)nkMj-|V`TJL3y+(t#L3AIx&qWI zy){zp-YL^yqyTPJX_KIzVZdyo${lRlSvL*VXy1VkiOQLP~xlEv5RCp`kp5( zFstm2qbrX}KmTNeAIXA$`in(g9xMDGO<%zk1;ceaK@HvAARyA9G&2GMN(w5{NOuU* z%naQPf`Wvglmeo33=K+mHxkla6PM?`>-!6PowN4Iy|WD;E5g2XQU#glsd7VI1kX&G z%<{d~&1!`?`&=($)0tLw7D#Xqla}iSOcnk>79H+jzY`EO|H(3g(R?fLQVA7pV2P}H z_u+`l;I!=|DGSeIu6w{7&cWy)p1WY?p6#<*|+? zId%#s8iXr;`e1XUP9(lsQbA1P=^MW0$-8(DtMtH+r&*-4lV^dV60QE+Ez#gW^*cIS zB_NlQiEJ^L2-7WU{Tjy?*?$T6E4PxEFMM{0=WIGdH)A%z7^xA|`6^?X`iL1AW#9Z$ z@T9&2!aU`M^bM0K^9bQ?x6iA8#Q8?Yt00-?pO#Y|Yjw^WqQLX$E(J%g_tS>G!Z$P@ zsZa)yyZm*azEOJ6uU8I}_iY@L@IRv`@|F<&Nr>NcEe#S1ly>shpG2|6UlGfBg3n;D zK;?!k^>K}%clx=wFYDNFma25X6`Wv=<02ZpqA&L${w2%)>QS;YDRoBb?a)O;hu09P z7>IpMB11=Cu{~~`##7c1s(nRMsAv_DMEq-S>V^Sp&%3mRviO^}j$+tEHZI$sbo<5`Vk|cU*QT zG(X9n2Yy>I?p&x}p+(*EXgYIu$V~8Us-Bn7Vcm%edu|D

    @mr5+EA^8M3lAom#7L$zsZ+Z`^EoI6P2AH))@;AbMKtg9ib$UaO)tg>8q zDV#vxG7Bi$fA?#w^qP>6YO=Z^L z8YSuS@&Rc%A`*V;%=idL{1wqn%nFYiCE;?5%QvGN`N(5|jDJVOkOjP-^NMqh6wgEy z-t^#&kOF0!86Ur@)LE?l$MyiWR{IWi%7`Z-D+vov0^7yx87apT9Alm;5pqfUvf5}- zOAq4tnE(=eWZsb?0cCZV4Xg?LtlyJkwN-p(wh46;HL+=dHZq!jfbX!h@ZhC;Lpldh zOFvDmDtFP_^JH=6*jmQ3M|<5d9n`_2Z9dUTY>n5T?UOW0#ceUr;N@OeW1c1!)NsZ@ zdJ~QKMVInWLDAFQ$`?tqBv$w>5JTJcOb zlz4BkeEFaE6uYH)0+FgWL%ax^R2*%}WhIT2z#D;}+|uu>Hhb30TXq8(~%t6wgEc5UHY|=#~fK!a$)e$FN z4#RNjG90)c&(97f*RBh38D=hNUw5(N-A;^E*M=zw4kh4$52g}*h?ctg@QSV;Nf0S1 z&5|en?g-Rrt6bwy`N6(V^I+LDMS3LXN7D)Cj-)ZwL(%pZ_g$U`Qg_bECtbJ~?B2 z==9{zB>vja_!V#?JWHM&l+F0*i)}cYkeR?o7Dv72UD=7LjQm4#KfI=|gB+W~mfbh$@dzua6bgBV7j8NNtShUVXLA2a{`e4c*AzvAsAp~fJ` zspRE+d;euT{}Ngz+8yE6?VeYd&dvm)Kg_U(Q+MM1@D2hCTKIL;%|Au=z{Zj079eF6 z`uE!;+EXmKaF&UaxcvUQ>K=WyGZLcb_3lsj{ua|?N5_^k(nw()xjMQ-icwLIZJ@EX zrXTjnHdoK-_>%h+X{JgWnx_YR!@`;}R4xI}3^T-2#2F@{Twbd-tG z@M#*X0WC!vSNHg`ZX_4W*!VLq%59jlh{xqg(qC)En|J#nu|NH5KpGB=C%*JEX&_4O zMk)#^?o>-u#@;P!7IEa;FgruWaz)EBw5scH%sZv@943#@2Mjm$6VMYO`KQxF*nf7* zrXY!8mD)U?===9>u!}Vz09q;a^5KGvkmT&U8>2tdfU8{3eVYA-OjSS?9BXFL5PW`% zGkiEn%8;76HKfq^2Snh`gp9h1h8Fa^%|=_(*3idGSM=z&Y58^iP~g?pT# z3`P%L$Spj1RgxpYw{^yK%%8URlSf>H^rMxIwO}z5%uy3n5=$ClvOgO_8k3IV1|<*5ftDsBW~S z24@anits7wr#;A+tvA3q zpclCeY1S0iSPdS>{ghPwR3t9Mu47z7EjSAL$=}L8SyS0|@b^&N_hOjy85?8Bjb+?s za#gD>{?qk|*$t|nkC0Rsp^qOYzW4j1GkPJoycGq}g4NUTY3zs(Rk>|-vBEy*QfPsm z$Wf_H^qxNQXzbW#gsS!Z?GhV+xOb9#)qpBIKlK53e0|Ub>hyr{kmg%luUlQUh~gb3 zd1QL%X1N9K_F~T`373zZpE&BhfccK*FREFSqYgzQX-nFcWCelaf+(}3xIKmMDVh5D$_kh6@UDsr5ICc5v zS9~jlM!hZR&TBY6%pP-?&^&>ke8>ho#G_sBpQ?xhD4BWL98P9MY5qpYcDPgR!BzcI=Y)+a7j~y=RhfTp4U}%6}wW!&wOE| z><&R;cvfTJXM;&8bhwwQxY+CZ)RAQ}Ev2656SKI$FvwLQ(}6OK1F{IGjrbS{KV2qG z%CjM@@Y9J_Op+8@!rmhdk*4$>e$O~;c9qGl1BxLR?+6<~ zymK2G%Q7^UuMs)coW8tOB}B?o0k2!vX_9g=6iZR^;Z0VJ2WxrDcf65@^LT!d@eNS( zP93-M1tEGJSLIF3b{9AnN_rpyh9xR@QQ!nEC(H&tY~zHgpu?y9p63Vnl|IV%CPrIQI1rJ^(4<>ebmY? z9^WJpz|H^h^O|2P_fjqwN8`*cZw@0UTM4ZEb&V|CyROcsa;24-*&%cYvQ_puuVwAl ze{1(Uu^bFJo6I!Vp1D_AF#;}#nBq6_uny9Vc;u*9WB0)nMPbYNI;Sk`StSO~r&#jL ztXYVhAf!S=%^Ix;1{B_37!s11KK3-4CR%;XRg8cAG{lsK!=X}DsX68>&jnx1RYf^$ zp&QEA-0P`(flE=Oxjn~3u+1-efQ)Eto`WXyZnAIPu}kh9Y#nBkHLEV7|@if}QDaVI%?^9;_L&!9SY>j>b}-RyJTL`h^4yZC>#=T&I|qZ^yU6!rgF{z$@09 zSSH|la}rf@b?9?NQb(e57K^}>4k-4)-L(CU{Nm?8%e(K!}ot}NgLEE)O`myk>}%o+a$@oZ}%Q*md4P(w!A>fM*XN9+Q@*}gvRe&ZKC$)?bd{b6pzj?Y$dc zO9F?r`Ha(jYw@xc{EtB32RrD{A$ z0B_u<7#X*mdz^8K>r{GNo$Cu7BC{fPU6CX?=;=TUGHVB`r7rrmt=0Kn9ZI%5?zr`w z8_Gwx`3SLc-@4{U2hwX)hkk0C7Bzh z6tar;jwoAg!<8&vFpG^ioXf)gEY0!$eu$AA9mKMdK6D$v>!S+TnZPIqWS}x{#=X$v z;W!;t9C0W@-3kHK*t5B) z!8x^hW&^gJCP=lEcIaw|YkopctfrX{P(9PT*5I43c+KQufq^sDgxn{p9(k=$+kWut zaota?u&F8&qAunq9mF1RektmbQ3t+?ur!PQWWD#3hr_4)$dpPZnnr8K4A!mHchv>I z-a&|4Ems`rr8T5xMcZ;XY(b^K@Z>hYu5+71^lBZ7m0|#3icS;daqPQ2;RtwE&%y0n z@}_}z^IX)u{!oFZ%ZJi*DZcy}@3Bx;4Dd}&VssEvx&rHqzcJ8LlG>K>LCRfhB3cW! zS8HNkRK+sHRaC2w0;#@yY-kZvfK|xU<^2c0U(R`*_VX;t@G{A5DLeV*uzTk4Stw2{ z3n?ElEn5)$N%}D1C|RYAqcSrlFLlY_hbTOIX;Alf)0-WAKCYmGZ{d(gWIxQHODO*d z<0$jQe)GW!kTFz!{9Zxms5H;m7hww3((XSkw~zzKwjXy2p)YwbE!BAm+g67)PrN zb2e$DyIJswsDqvkdbpA}c&jGq_i&Cx#)NEo^F40zUFVOPY{#>*Qg+qP{rmMw==D6% z4v*t>R{Vh~nX7Rx0KJs?sriUx*&{<2DK7!Qbh`&NP`2%*I*0G8$UR9(f5tce@ey}ULFwXp44=GZ?Chn~-6QeK7UTO7-C-&}SA z7TiBZ<(*r98Zeau%q$ zWX|7lXT4U{Hm6xBWaBQzCACN=)CkKDb+#HiA9@|M9G)+@R5wks-Xs66M{`f2KwzHv zn+!WGXDTTnFtv}!l8Yde(G$=K4d)C)awezj=yIlVf6;ia`t*fVc&+Nk_q5vS_o`*3 zvYjpzblu^HOMyd63ZfH!g z8{3r`)VP$h3wVB1vdV|P#~?HqnQS@$0#Wun?C~bmfv^rOYIm+5f{xt9ROFeE z`w_#RWKO$r8-eSBhOI3#bYf3DPy69GGBIszeF-_fP+=j=6-fOtbBx)}FaJXP+T?!g z`HqY1&)8Gyo-R{NJXPN`og`N~W8e3~Ol2mG_>{VtA}`r0UEXl^{Db|O1DuN3D}dv# zaySSpc)s-xD`0J^e)!Xq&)1ynW-=68!E_`cxlYGFi0{D=P%~z+Q77DH`jU(0R%(*G zUQ{#XX4s-Y(6x*T=Cxm(M9jFF__-%f0t}+-%7D@tA`0<_@Tz zIDWq-d_Wh5Oh>h+dwf$mA9)Df~KV% z#L|9igr&v-xm3s+7nVm*8}emAN*>#ezHp;Fs9t8oc~@H&W#la!Fg*as!7U!DvtCR9 zJOWwUa=;W+mITO2Y1NXT?E>)hop`xpFa*Ho{6GLKZA5c?ozA8Am1|P8sJfy^L0~_h z;XJen`UWxEam{=+A7{mD$IEJ+Cc^0*J_8g7vMwhKVv}GYIRe~Wbf3SS65iEQgl%r6 zI42~|_;FrEjt%f4g8qht7iFUG@hAOezfnK&hOpJFqB|qL$i0byC;ddfK7(f`3e^z2 z->0-Y``#$kL0eoHXxU@QPkzC_h)|?lY z&`s{ppO7jwqAHdWLIT!PP8A-5V(!{s<31%%zI=6$O5kJm@O1?74pdb>_?S0RpHfttsrz39dRQ09QXgF#AXXvb_*!@HPGx1!sbBX4+K@EQOfChGk!) zoZ3Z_Fa|vOt!BDSX)DZ8o$%nLpZdh#m+I+f1ASq?|9ZY*uOBJ~3-62OWVVvxiqlw> zj<^U~U$mhJWzg?jDByZMf}G(F1aR>RZE*e+j((&#^@goYR|*)gY+-Q_wE0owbkA9t z#Oue11^vhDzrMd$4bCy-GpIdOEPnpi0Uvgi+i8tSmqH(N|5?MlI3Szlu$D|%zrwmO z>`td)Ar*j}Bs+ZKH=eY<#)D&1%K^fO7LjRnk|M43L;Zl9ghk4f4@dxImptqtRUiEc zUh0fg)$P~n%5b$&yzTS{qTK^5IW}&vir-El&10l~EMbn!)qm~A-_yjZvpsE^PrA3$ zMpFBCFZ&{9r_|2!fWHzO6so>f zWA+0wI`-m4n$grV{666mI1ezEP1%&kCopXb^aI;aJ1D-gE>6|7X@ob8{eyX9 zZJH(D&#yw#j#`q63@5i!zWPgPe>XddsN2r~VbqlNhJu;XrN1LS&U;69gm7 zh$6A`UzXdAt}5d;I0D7bcJ+kN_EWc8Q#e=HYOdL4&StJf484cBLjg+QBM8^rY|(%7 z8?%hdZpt&g?-fT>Ve@IYeu{J^@m=yYyT5NM;D@!cIe|`cAZN(wXDYEBdI5b`+aLRc zyvXKq&J#4#8LOAx~ zy|~s1dGgF{!mga&-W2+5PV4u+&|VFmQhEz}f4xKY!aIDp_G&Bp^b-^F0`eTSqb$mv zVH_I(r=IPe3#>odIebMPLBvS-UDf<8r|*)@MHxHATw+scDF2I8Y2wX?NOw(A3BJf) zI3R7k|uzv98JUJa5J(jQ6y?a*@C1@`_?B6w}5)l-i&%S_`{4qpv`?m)XQj0 zIOxfzieBE<4FK_#0W94*-(f4@EDp4$STSBgnE^3bJORva<|@)~lJOA}VO(Xdzak>z zbg_&*owoq)@GOs$3u{xqH~TjoNI$}oNKQSZ8mWXX;2jkyWm8p8{&PT22Y%py6W-EP z++d$10ol?Ab<>llzdOLX4jnILLJVub-wr}#AFBs4jy*Yg1U!y8R5+jDZ!|f?F10hr z_XFY?aOhxKCN8nDuR5uO$)?DHwS4#`$f;uW zprBzZ=>T2!1%rRYV7%?Doe9Rt668l-P2_y}jWJal#2c9W_BuW5iES?48I3-(c%ub* zdRmA~BnU08>Rr&U{-;+%+(!K*gn#oRFjJi@e zOh_kLs-M9zpNp0dAO{G%i($r|+5weh$GhWmMEQVLgM_Nh%PGvhrliouNox5Q0yly? zfAkFcf@`k~3di*CLE0z$9rf5@`!=X1_J>K_vBj&~>6JsC+kza6SN{+VZ73vGnFKTc z`9TOS!v=2VD7tcJxJ+`y5VPEW%e(b~nqR?d^L~z^LR_oK8@lP&-E=oq%Bo#iYA(IN zbs5fMhkUj^M^HF$TRBSUV%yq%It9f2q!*781cW%jwkKLLs2E_ZZ5p3dD0h;;FbM61 z$C@^dM?~Sw!kRS~576b-)jkLo`LzG6DnC8`7yvFH(Q)#5#|)DcGXCyto}mL&Xq1vP zwnzC@u_*uFEdcG}vr}`YX0>Iqyt(QKHoX|<^C$ckzg)BaeS2A zT=`|*EN|bY(slOPHu&Z>5@1>{IlM3cH|Gl< z02Ad2n^P8m3sjwLsl4sh5hIO}5mC5F5+fG{T6VPvUl4SRMhJL<0H5*nb){bZ<&UA> zZdccdVYD@roj862duZ3-L-j}TuzhN-a7s^oXoP?JHXrRfaI^A@xEIm~Y3# z_+ie`E}wD2d)KgzdntxZevTH*uUznqvhZujg##GPtmlrH!U0C7{6$y6GY7$yXZI_w z%jcjbH{}^9Fhca)aOIG|ws-;sz$ufMxo>>Im4k5?IN_@#fk=4w3PqptABNlp4%BHM zs$(k`MG=*)$w^LR%3Wvou32#Vi&@uZKS~>18@1(mQ0nD(wIjNEdE~kG{nGPxu(H!k z8o{;{u-j5!iegd?mmzRbj?!Kr0$0674G>icvB0E2Mt=9+kfcNU3O|=LWk=po4Wl2gNY4S0xu1K)cSGt)OKDjQX8NeKHi>sZR-LtG##nH+lS z+jJg%C(mN>+BPwPNZhVQcj8l)a!FjDT&})%KTy`qnc>Jp2q2vt*;(=J+5Q+gUcCd$ z8ToGS%C7Zv(cH}NpJeYCCiV55=1=Yv;fYABeAOcEcI-XtIMm0vt3LA{uHI)wGzHV@ zw1Og8U>LD*C@`cp6TS*`MCBwUoIyc8-xAQdau{0pZa*qQ7kcXMakKq`;59iB(X|69 z0APz(=F{jUC=#5?T#JW9CW2ZI-nsYqE?~?Teq1&V5b3GLQw4ATcyD{B#?aGfQb5rn zOosPH5tL3fdh(_p?5{TP19Z|665H}SIdeREzZ=@w-HRZleMF6j@bGh@n75W#Am*|e z_>7$CwrX4Yc*rt}{^CGbX6eOVh?hs3UB;*+m9~VBlC)F#H8Reyj?^V%T217e2`I=A z{|YowiW^bys_O7thx+#lSK^X5Mn3a1BagRJE+ev7nicm<)lVOAAzU6e@8>&ThC6?F zJ}vnNmVB7`)-aLhh;a${30=*r~k?py|3(dKY4vFBCN5Dat9}55!t9no`c1k&-UJaLU z<(Uo>`t}j~bknNA!@c5@^__5c|W zv&P)rp;X^szO`Tt*a9f^Gm370TB)Am)I3T76R0YjkFEP1Oc>Pv?U_FozHBCRZ`onf z$PxQWrNVp{Njym!&4h{Z?20fdp<2CJuY_b)nia)C2`J}5hP6aV89fwv1fT`ZKOBkMidZf^((WYE&2^OD+#AvJ_%1;`QJ7b25>l^i zJpdtXWj4+<(!q{1 zSpCer?B}nAIF$Xau#@opTM-lgCIW-Jm*9!`T2J4q=#ZOLk7xc~d5P6$%av7%SI@<6 z1}F^D$%4C9bWJfQkxJAnTd^xB@Y(+&bvL(F;)p}*WI9IJv%`G>w|iAX2#xI= zr5f=ZpoS9JyEjA^(wG!wNsJJJx{>)TjlW=<_W?8Dj*m!rId6bp=ZaVS{y4aYit#a# z2BS>AN(aeSZZ8Vk;l4I}cm--b)JBlpdRlOwCNTPUWl`&k&Lz|<6DEk6WC0ed>wbM_ zWUCej9?1S}-kpb2u74S}4qT-GTHRFwOONlU3s95VR}pewbCDIs<;>K#o(TK~7Dgpj zMuwh~fr^L_3#)ykqKD_NPN>}0l<14V_m)v&?30rBcda2l!) zRKTXl@aP4V^5lQ9TXNufqQHt#@(F%;x}bmEEx~J`ELqaax1&UUQ@?ATfU$m&?dN0{ zIt?WqyvLElVIayT1%gssQ}q|o5an;YILhjFi@#fl*@rlKba2#fvmvss|Iz=mdv12S zuUI_%k1q8Q=BI`?Ydt5!%CS`9`ceEAet0NvZ0FTM z9Y%N|*h4q*VKQ$IAc@|OhzhptLQV>xel8yBs|Dap?tEM!A!x5a&Z)Y$e7Z z$?zEOOq$g?@K@4vpPqi6Pxq`-Wq{rBgT&*Rc;L8KBH8VfVDDw0NjLDLNRRl?%X422 zbyuw!5b90EF9wnC6XlUnf70>J7_W)^aS@X4O6LUG86%|JybX zc;A>3wVJnv`qt5p;b6SOp!KHX1No)wFw);i4~8E^t9wrvJil)8uX+PIk$gv$wv!D~ z_N5^|xaqCGnt=`qB0%JB@*NQj7pmkNE)+0o9?lXYzXN;orY+<8?{oRnE;;%U+T^%c zl7fwl%~#JoG1BXY(hGRthcC1Qifu0}O=3A?WuKk&t)#^C*xzjxhLMp#jcohFmke#I zGOr)QPdUaNnt;{c>4WVtry~ndnj{l~RG2Go%i!QCJ=#pztpm#rhjwmRM~?XyEV?z0 z0fv38c1KgBST@+Poy!GQ(x1s~5&BK*hTo~<7CzaI0AeHP1MZDyOk=i+u=HrcDuZiD zFzP14qDP(m>IokCYtJY;xo;&Zyd6L5^15qTUMzs|Ayf3H)J2&$KdCdTCz1%llkZK& zM4Cict0ne!Iwf`!1%_iP58Kt*$T^s*6q#+Dy?e5#l}@)g)@Og}uH7!7>pVZpL)g6) z(lxL={9;c}jZKk&NBGG?e;&wC+c*Ey4Pu$9!*C{A+&rNqnvlG3XZGCD`sJG<6UZ=< zWm91XHbYgo6-v-7v0GM2(|R+$WI{h=2XFK{RA*+cBzj!8NbTdB)3yG7Trvxx6u=*M z@BnpOowZOBw0%aqh%TWrYwee#lL>jqHsl_JfoXRUl{%LI?W|)X-u2irZ}R~6KPB-* z=j+b@vKX0f+9m18V>b)BjV~qsLYb_4JmsP&C9a|v#r7}^mUI0abnoalrOR>mY7rxB z80I#MHbg-LR)6fp+VXc`78utRpLa8!*Ch%+W}uX{OkgMc)d7FKicW~~TN^T%xnM%y zr&i81TU6$txnMgK!Ljxp0jJcFo^k7|j8XF{tZt&K4l*5f3sx)bLL7S>J1=s5 z$)z0GYzV?_bq0r{K^F&Cx6!+GwxY-ss%e(9z!Rk`;SF)gEeyFnkg@h05g2kly#D?~ zXii8r6(sew)eVjV(-S;O!K>cz(t!r!lwe~w5 z@iP}GsFJ!|Q_|`a;9we<<+hoYdkiex%6mUo^Hi@M%wG>hT$=q4T8WXI#^t=>Fu3zh z<%%w2FBNW{1C0m29bHzAA`jg5M6x^oJ04&w_QY;*A%*@%h}ax4tRw|r{618MRn-gQ z5zm659#bHJBbp|!zOfLzAOm_Jrye&=4>d9bnZm?lyWh5CM#aC}7)47{JF}KmC)0r> z>18+)Wxt{`WfiqzjQR+fvE#GUvVJJoJ^CE}qL%u?OVEVmbV0MAvt1E~7f++XlT6UF zq}@x0wc)r9m#P+i>Ji5k`P{UuylQB5Xxl^kw6>$7_xlA=W+h#j@1k$0upy|nljbF3 z_|T2^&TL@KOb37!8srY&kzJQ*jMcjFrPEtQ^7W9KgOOuxkU_H4N6>m}hm)7_;OSc8 zo`>2GCO+kB6a@{BIh}MtIYnP zrJcOr1ChdqCvVT&!ihoXr?8CBZVFC*$Lv?gpZ!5gFNid{qWOuW;p@z+Hf=EQqnVZD zI+;LkdXC#uiw;jYAFVd3#7GrZir$&8u09NBZMqN1Z)Cm8@bxzDaaOdF6Z&EWdbrf_ zHqxsQg);`c_?T~Gea582PL^f(S@N&;-;TF@^+dXQOjs3y>NX8TdtaF-8CHsD*Z-n@ z9yOB5n{v}pa75xo+D3Dz_p;;H=doC;4+0BWZcZ!z?a^hWt6pYY5yjK_-U&+}Uph-@ zkK0W?Fl9SaB@>>3z6xQxR+-&yL=2SS_S=)%YU@b5|BAApYt#^#V1#S`|Dba!y!-E$ zJx8!@6hFM_uNGpw8p&(rplJ4n-od7>cu}gpN0hX9OilgwskG{!v0#onn;}APV)Ww| zXt&)9&en5gsu7z&H+UHk0E7I^1t4l5tkoF9wPqnPr=w4a9-Y$PMi%GZ9=-VWL813aGY^1w7Gcx@|AC}&- zpIU;OR0*-YZ&^&wDeIWGvWLgKQ`fHJ!|W2cptYNkkbTNpB4H%|(Z|t$vqg{i(j2er zlQ^KOc=_$W(xgg2FQ90>_;9jgjL#U~N!-}!^H^`$kGW(!&beFTuZLOu6jx+Aum197 zNx4@<3Rmb~KQ-~XX9FfTEcN?77D#7WoT-N8#RXfy=+D!$hpnxS$^knoh6^0vB%tlQx#L)F|N4FFildS?>L@JlXbg+2nxHnko>h9bT&OiDd(TXMz$8O_Xz zHR3TQYFm-Lc7)0LsfxbL!pO{`Pph9^mQth9QO}Q}{?s_)v9ea7q9X5?WKsDe3N{MA z<~-vh42RJ2^fhl#IR;&PM@|D=a)YCK)Yzrnq=Ejw{{bx%#?}1T5)P}DoCwr0mzdk? zT{}I&%{CNXy%bs4V-bs~BQM9FzqAh-+(f+)z3ccXXbA1)kHuSxd+vRahyrN~MA-|R zBsH!mr1dAs)m^z$p5Rl8b$*9vC|nuVS@S>cIK4tCVbX|LtJK-<0&qKo-(4OYEu#Mg zj``5Y(Oos%+r-~ikpLOF4fX6gAp8{4thzs>xSrn=Dv>E3rws$CePDJE(oCx25VLF} z04I*q>$Y5pVNv!jq7*L*w63Wf4J%Za)sU%7P%*}8~XD#DCwz%Qg+3K zwSy^pikE?YWNjXs+QduiYUZf4e8{`rL9@?5XGyh@Se`-OF^Xqth%p|h-_oOk?icSf zzxi)uhA5$Cp1s_v|Eg+2nZS(!-^Xf;vGqlzli|wV;W$&U6;6w&;JAQB$NTvk#8TXmcjaqc#p4GZNMJ9c#{ifeesk)F z-8PEPsbZ>?VNUc^V6~QX%MnK6rUl|#Zt_6&KM0Xxz`#lj73Ts{$|h(AWh>C*rB?~4 zwFLynMUEvT8~OkI8&b-IpT4!k1ArM-UQ)Myb;dWZ7=P7&s?cLnZh-N>bSbs@DC%y5&k+1_??H)AmXc?OsKesh1MQED2 z(;&)1nak$D?IZetqVL#a^eL{+dIrb-n^N`Uatt9nL*Fg#hD68(ex@q}aw}-zgrW`OAaAgXp4x zy99X6;}tHKazMIAcN%V#B}TBu?9fm2N25q+&v5`r)yAm>AgbSC3xsV9;2j~!D6YbK zp($jQdZUV=F^1{D3bX62b+i2j44HGiB2;xdUy%q z2pYTiAm5Unr)HQ+OFcnuNj_4OCSuYJY*2dv-XQ#LOC-{{qm`JUc{hPudeN} zq_{&K4i)|t6hnZij7%4wn`C`id!EJNrJz540NF7Kr)JxRU48&lzWL?wuyz@PUR||T z$y-fAo&IEhTER9N0Tu#~5ep?_2$H4s4FwN%?Ol|a!H9jn z=)=n@g-JY*giN)YXU(;5=&wpGixsSUJxUTxdoGEhT?vjGngG@S^LiT29S2v)>|5$T zp*v%3rUI%c$&^yfARE((9bf$~b|KaTejQ>Z5eVm_;{E5FYB(2HBP7%OvDo1W10>oa z=?CtsDS>S#YHFK;cAuhjQ)vs{3T|=$;v(o9a~+0`+}W{lXp18FLv%mo!+7u#Vwn&I z`FcaacGM9=BCAb#)&lK{wfQ@r>FeN3;A`qTU@wYvvzpZ}am9^<02+_mHtnl-`qGxu>n%pa}N= zawk69D=7Hqx|v@&Ix2*pDwH*xFd+NhMpaA4`C)V0Y0 zG5JhQ=N}G@j{_wh-y>7@z#KeykNR- zrb&eF+FE5pYp`B9c5=J29*vn}KtP5q0>~BL8J+90u{T}QA{9!bR*DeRDp35@eCh_n zsU{=!=?w+^ZJkycM?9H(;YuF_ zp)tW(!jS094`f;Rv5(uo`y_8(6pkK~N?sc$0RN$jSdMDuJPqzoWbG&dgi0Bb-({lk zC`DulEM?wp;W62lbjuD;dXS8?{)8=t=q-=8ms&x7fp^I6e+(%_h4`tUI%IxeTjo5kQ5y_-moA z+GX5^yJi{sPzik^j^2&?w;YcF)ag*nd>CwTYgta+F1jz>Ld9US>Ft=aVDVL7Z4}Vg ztXo}}FHQSG9>}Ns?4A`T{z{iwAY;lp*u}5SdKdu_bv3{H)v0wf*#nGOibN!I8Yu?r z{Q7Z95VAXsEnWdq13h42<)+OUE0cNe{2m#t@yQR*-tFHW zTZAw1gF=~(fR??0Baa)Mby@<^@1I>bA4@$XC*9Z;`lVR|1+P7F^U7ad8t>AUn}qv+ zEhOF-AWfSIXEeAoYZLGa;R3Axt7O>8`z(pw)pI>FFj4xv7VCs{e7Rg8|u|VGp@4M!0&AUV-ZVa~@?l#*z}u2fTIf7Iv{x7KiuX^bik(DT+l=7Qae1Y` z0P~)4TnW_?Rq@W#rNwYCoR6y4kwvJ(&J;#LjT~HYIZ37Foxyqj>#;#TK$M@adx%S%Jo; zoYNMruxU$5QJC|t_^jk>O#OrEyKS1n_N)5fe&)3o=U&sCabqf#qk7$` zC!sd#sG;r?-xYV3a|fqS#~)xX8GT!ZXG0gC9#00{@BX1wA4p3Iu201ocgxSFwm2VH{5`4$3Nma2*#@61&fcPdoz`feH{fk(z)ZmHYK z`XHOEQdWF|(6HvF6vuu)QT66c)`JvZ%KI1d~xZAs}C+`DT1+wv! z{8jL`i=m(9!LOuoWO~g;NRPIg9tkafU9cK6iN?zdH@eIA3{C|I-bMxg$Z9M=Z|xBzY787ch&2G_U#;}>c(Bip6|V>V+4}%Emre-W>WSb|NgXr(7q;k;JC|o2~|qJ zU{ax;Hj_D4bbk5#;6o+qHh;1MWyKuyF1-4JgNQ4)ttuc%!C)QAj~Q-2Q+?ch&voR; z*JA2W5K&>!NZ*OF8ACkBfWwo5cMOjQB8q=d@1Tmwbr(J80Vj=6|49_dVfMm}$n)d% z%%*RnMg{c8SpY(!T2nFq%`vS_^QXF#tR?Z_4C}uMfH+|Q^}63HfA*QC;Csf%Z;-1K z&C9K0O`gbK-N}*7q?SX-1Oiyk8=u@y4#1+|HxZMA%V}h|9=v~V0rjSSqX1r8Z{Dg9 z;;gitCBuE?Spy-ATiE8HNML`B<4o|iD=D&U7e8>mK&(eatg&9~?HjU!U~oBrP|z~S4tdt}_Fr-vuL0abnV3;H8dD!mjh)=EGWc$^ppV~IA zB)(KM{_0#yzrH(hn94MBqDG2iqy_b~9HunO9wmjKRMF*i(r2ld1@%ZivVXV#(f2z| zL;I_(L7dYhl<{ikRw;UYpK7WD&_(GHa41&HWq-v0{;abe9#ceS+2cVm#9-bx{DYX? zwHHkr#n*zv&(bW=u6kN5Pk0$`Rcu+JZuw`PFzIc4VED*$f*|%EOi9Np4TLtCtyG-? z%z!vTvEj^#d4i@G#M)xCAAa+c*!cWkbVKnng5rZJ31W(ZrFf`Cu6jW_e(RGe+E zuf4Ed);|?#ejNBR6M$1K%s+gc6kbhJ0r{I9CdR77iu`{(y=6cY?h`G%OLs_@bO_Ro zGziim9ReaPU6M<8BhoF6bk`EnDc#-OxpDdZ?|a`b`*nApd7e2lXU@zxTM#i$*4rSK z8T4Hmg$tCv>4rX@ z-ug-N@TC zq`Ah5iG-y;mfvTHUXxJ7eU8r{ds)DlD8y1%HeoP(*TM-Qq3I=XrU;5P`*}m^Wy)8L zyoP-hDTd-I`+gzC`3qHrSvkA44CAwh7leqZa9UE!W>F}&a(Z}03T2r0euG_wru2nc zxDRYIWFknszyN)HHqr|eoko2U*bk#jP=YtS;!Yc=cxg2SI44u)E4Yh)Y1JqSu1i>e zAgvJ>yW@Q;H7zx7^2*qeYrv$66wX-LzdRnU3ih;J#~!6+-LhtW>Ac8XIzQgJt=!Uu z^ng2deC6*=0FwARcG>mc%u4Wb;1tSShssfVqiIQnhh;+q+8XQw{{8yT<{={X0QY3f z%jPSa4j!7YX*RE49D&25id0>E5|nCxz1GmYf1GJeulg_9jz7}Hyc2U7159eBmm zRwTv5(5g}%sbn@O>?8JL;dO}eDni~j6x*VY9%;#h6%E?**OV2Lnj9PZJ38iun9KY! zQ?n`N#TwbF6J%cK0l*NV{6l5Z)Hu$l-9IrwT!2`VZZ^sN5Bf^bJ1x3$T`U8u$1C{~KaKI_~Zj zX!ns*u4T6-#U9x>I`1KcNxXeOawDbI-kZhwGWiQ>DHZ<(@_H#rFB^t1(k8yG3ss=ohrMk*!J}qCZP_-+zE5;qnpk04N4@{@E1$t;}(=Z zv#!+RY*nnBRVciFS*k(`fThBv>7sEmU!h+!aA!HkN$EGW^cvTKIzB0)_0~FfEIb!z zg7jZoFlOy&*Oer_bG3IFaBm1Yb1dmeQm5L&(FJmX9JG46F zZ;TFAco1?@fzbfzMc3p2jxmyQR^QY=E_a$n^&INqAN05d99# zIgOjH(FHdNuLnrzI%vP0LkV$p6`K24DnN4{O-o~;b8PMNu4D_LTNKDFuS%I) ztZoC&nQ{FU01LYyOidY< zAprRdhKv4t>%g&sYzy{bm-BB{<4~vt4h8gSqh~2TRFYqu0Cybz{2`)$RwI_X(ehg{RM!y0Q#BDWs&sfilLGx$hyxv~djumS%yo{0z4@mN%MG5#7@&nEA;Xw{}N=$Cw_Im;)0_doE0z1Cn7k>Vk zOQEf_$qeFKK#GRwCbIRFt|2TSw$5A^Y!-5JtS;l8CglDYi%Euk#+F1^Tc8ws;QgT1 zOvFz!15oh1UO`&8&O6+f42|F7@i#h8wu|FXd0}+n(mlD0Quy<`dMx$f*Nw*^*6i5Q zH)VwwMGdDWFTtl&Ud!Tdi?~^FIQvc}-s}OYjg}+8#&VL5f4NmEq{QvGlek%ewlxB$ zrjk)BG?{YUyoA4>*^@~A`JT!wOEoBm8^SkvxI=%6tk$(sN z1pmH}%%L0P$-!Xa_<;DMK0fbgl)9(#M2ao+#67tA*C02_7xwBWJqaWE6!QLAw9j#) zu4Y7na}87pmtdOG_Dz-(c%A_i_W~x!cGnC7{%7@y$f~tV&KJl_1PJuF$b8jZhN}E* z5=!*8b4v?Y&tV4;c~b#;L-Pi3J1tuFoKA>Znx3_H80g((V$F1z^ar7!DaONGFne-I zY@uT~_Dgi@tFFk_dA_oM3`k2u6E+b;%Xao@?(p5LE}xn2{=VZo=Jkjc|6ss}!VS)M z-_JJUcu?}$KTZs9tx&RjSj~!2S~+JbubRAQysWDIkczIAh8l9UV78uMr+6DGYM6mrOtV=n^RK688f7h+Fmi~`nr;>F!MW!!z|MT517ZV2`3)bses?n zXCBaS4r+fHhyI7?DW4V`9tBHalgTHRLIZF=_+)-^fCSsIlfwmqoT8V1WKS8aev7*C zCR$HWw;evr-&pSxY%%g)nL!S;F}`Q}frct~+%Pt_eBhva1d*_ZY3I9LxBJq_C0=)6 zx6E5`L_k0Q$8GBc({FxdN$o~QWuLB!{5yVp+8p1KGaSmupM(GE~boN53g07n$OS=u3TJ@@9uD-ye zumINSU$w@6Xpetz*zQIc?p+iLgDV`l!r>`CSL3FClZJi)AXT7q#B@^jv4059|M~8j zISN?gmcQ#ae#=ezT)2Lgm`HHSC%E6?)hR60C@=bP0ht?;ymtHj#mKa6t%`uXH3bGO z%DjQN*2AhlSeb=dA@6uN%+)1T4Du5C_dXQVa1PjEqcs7k7)@Pi?Q9VDHvBzlWQb1> z_<;jcq)MBEwy8D6S@J#-xev@>nHpAevj`qlb>++>dP>Q{%_Q_J*R%uYY1}DF-o2{M zaf;u*f5Ib<^$?xTrnc7>zpcL2eMRuVlyrlo)MLjQGfh7WCg+!)G_$sjZw^$L7B%I* zy;qCPN2pS$@rq=7#df57PD!msa#8II@DhB^jApJLuZ-6Jfj6gH<1{>l9)_Ru0Zs_p zGY6k(!KrdOq_E(G5E;oB^7B-zN3pyfs^1zOdHXrV*|CS=BC+)v?}Ik8xCEF*ecS~n zi|#fz$&dEEJje~dei{qT`r+!#0#E~bQm@kYCxytzp(Tgj*roegb$^;5lHBQOMrg3o z^goRrNiX93BbQe$#a&AF!7`_#I;u9KN6F=gka`^I-vlz^Mw`V-=BEEz($kCo4Lbm! zzbCDpDmHrcBTwi3cYdHY`2|VY*^N;wz$nQ3+W#@ZQinKjFDKBw7k#QO~CgF~)YWQ^$z_oQliD7yOC zCq%?|D*#!%`8AB@N{3OvM`Xwfqro-unMm%(IS(!u0}L@~P&+q(w(XqOJkkN@TLuDt zqdl6zxS(x*Bf@3zyY&xw(`f*L@fPrcEi#jI4OJQJ3jj%V#jkI9_l;KDZJTEKXc+*M z`YVs0wK5H9gDjdw2=V^B)uY_l@g1i;DN1dDI$}5B7xT61MnJx=(OkGo>&-7>6th?6 zPtZ0N3VCwqlZZ58avjJOe?om+RmvTVidxyA7tBHcEFJ4=!-WrVpX^;MCf=2w@x<3C zEI3SCguij(QL!|Nct$Y%tUg^ho zo5jh^!Mm!zBS;IZfkA;?9~B#j#|%+t7uw?(jSn4pal=|%l+Eb>8sab;+eNbeso?1z z1eP{z)FZc9eFXmdbj}10YV2yQTjqG9yi>49rq=;bj4B3bNG*oXxI|RBoC&?$Fqww> zQ5ZeUC0QeIP~CJyfL?!I)F%tmhJp|+XJbmok5svS_HnywQo9!PYLDKSllKL{Rp@E-sa0=X<)_)?lDw#D&idi*hr;UD-m_Diy}=2XK>k z-!o#Z7Nn5Eq?Jv z%jn<2wN(M}k^o1XDDO_PCSmt}tYCF*RtL-htPl~OJKAU5B#)nh54HI=sq5BYcl**7|320mvZc5ool%?9&r`Dodp(hn^U2H?IDAYms#~ z)Y>w+p}KQcPNC*0XBj_cmv}AweO36Vs)fff%LDi{G1E8w(0_zW>N-4BH<=h(_By z>q0LpJC<)qvR}H<~OM)-U04FSSzNo59!AtO4AyHPx{W zp8bS(APtGfL_7I6|2LfRE;OD7OQ#Oipz0))*hRD>z3gq z&I#6rg2-Rqb?$P5pjz`eW zV6Tv#OK8<0_PrhKED%Jky5t+<(!@VD+lM$~2$ zZVMW3-s$iGR8LwkgWnIuH6@k;+HGYz=lOdrszhw}mUv6hOD$`Ng&}VOCMI`B${7?r za!`$^>T8y`P#Fe5KN-)XC*x?M4y9$)@SE42KAf+%Qv^z*z!wXnJ4u1hX_4!wd04x@ zP14$HPgr!#aP!-1tpOsFD#LJPcI-ZRA-TMic_<$6CJh~)l`VSAhdpn^ns!__{;+04 z-oYe!gY?_1(@wM_FwlS2o-wD;h93w^f3aj`^Yha?#ar!0+RzX@?7|+g{;}V^+dfWF`UxzUt=OdQzG+W2XhZOzlJl*8`9s z7MUBQ((9-4v+2a^BmlkNppguq_R@+fNMw)yCWFr#)|$D?<{~NjuDTH)P+r$)KSy~& zit)2J!^F3¬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

    ipz5zO136gFLxxqg-sH-Uz4RHP^^< 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 pW2Uk6N^q+%+ZC^^q4n1fQ&i=g=@S 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

    >o=IT zTOcPPpn9Nj>K0q5$0Cn%0L1~oi*%COK@#xms_lb`^;84dk5tbpZ{wI!o27AkV}>lI z9a9>qG#7n3Ty#TJ=GYH4uZN258?@YeY}WNy+_;|KQ1a)u6lNsCZCInPO4wL-*Z*XcdF%^2}0DDd<$`>YZZtUe?2RHO;niyBKPY?9jg^lC6pud6>5( zpZjWpNw%_|ZiwEc@?0`3eu!JQQ18Q_y_St$9bQPze>+Yi+@sPBvi|Hgv5u^#DK{YX zC11_-+J}>)KH2b%?_ZzN0BDr;KWGYHgaB(K>$jMQ3ff*<)nqmGNHM(MzS1mizd@04 zpXv18e%OB$ADKbiEkQvt&hA|XqT$#YQs;cBH9Fj@Kl5Zpf;HtY!Sm$=i0fMx_K?cY z3tv3krCH2v3i}_^+Nk<5Cw}6Q$Y`ipF!Jk2+KEmbmi{Djmrpzi2@!leL@iPFR-Fo2 z+@ST?JLq-U8-}IB@1pAFoFH>W(y)IqSzH+r~@vYq~`<48h+%UpR)N^-GFl0Od2r_(eJ<~+eh{x z#;Xq#losn9&03%@QcG-f^}$@z434~dNY$ZBzcQX%nxO1Cp4@1Y-NT;s+V72d>W)JB zf*{q5i>O!dMPFIt)#|DS`7Gf%lz%sX`FaO#9M?nNx32=X(Smhdoxgm2#dr>hehd@< z!+>O-&oXe4LqgtC`HDmmV*H0J|MY=-$;o|H_Q}}TygDkv0->>JZ=j)P2=pdLN1UOa z3T>B20F3tNIakOO4e||*^i1%bkudGc-Omf2FG=5nV$6Ij)Y9$sO<1Z)nZ`P-y`{Bn z@sWFK0geqY_bK)3AM>ujmuZtaHc1oxvAt&L`AyW@(Bp5{`(Kh>sPd0j&1Pplvi7gD zQyeDt{5I^z)MTrTZkxe^+#dt%{e`n3`P*W3;4}YL4!s;(VQZZ^{p`dSllZ&iMc+uN?H-6*yY|L{hQTzTbLY8n&RW2`D zYFmJVaKJQw?tA8s?l9;0duJxPZD)S3iiV-wU1jW6b8vSx^Yc@wjBQ<#{49ABoZWLp`8Ds?ETo*&p0^fK~UI^HrSEBOW6pI_6E6(HpG7# zIu@>bVAHqpd`%mhwkADhNQ;mvE=D|A|``IhlxAEfMUbVQ{ck{E@gQ70pF!xJp zYC*jsgzNC(!S|%B)3CFxld^}Qsz0WKIHLw|$r8L2r@c(H3WHSTpQ#qLfnjD!s&Z!*w6@`$JDS=Znh~qtiw$XXkYsxLW_^p7QZZ4E1!2q44qgx^(*tt8!j>v#d2hryKmS_lMDTV5SJf6K~6qe`(i197YN4!hC1(^8&Xj zI&Ym{H+b!;55)MQufQ%jZG?fz1Ukw&I}sWLDpU`@&~4czMe)q0Ra}t$we9+1gT+PrbJe%$jc;R@f!shjmV0=bm)kN3*w;k`U{DV zzU!*hW_u;7z0Chu#lFpzy0Q-90WyzGi|(U0uzldOmRT7)v!ZV}=y9M#r^>B3>5&-_ z8chOGc@@GQh$&_H6S~c8j4NkzEp&qmmG^4Qe|G#^YziXMjFskgUfwVJp0O zS-)F35ev*Sz3bN1koCa6E{X>PbsSH8`bIbT+MYRe0+_lGw`#4O8ebL1?{|}4Lnjlr z5sAL@Yh5ey0F%s)1ZnTe>l?$|R8`R$tqx*+Ic3^%m%L_b>i9Rmm#?0JWAiCgX?8NZ z0N{nx2%3xPwFHkDo$c=5{3<>AFiMq2i_I+o(j3H8-yHcG+o5oxn%-ltYiR!Z36M>v zY(7yWWzhsJRJS>?bYVk6eJ6U501(mRm~AB58*&kiJg$|^w&-0|i4Mw~`1h&ggdLr;Q_y}QZ z4{_qYa$@=N%>1I?*i!_B%@v5%XShL<7b-Ug@RZ)_Q`(qNPR1J%?(|XplHgx}J~@{y z*!kVjXeCV_6$)YUe@gk^-&8~JVv#US#IqYm0`HA*>Z?PN^j=*dilhG&G_9z7? z2xX;wyegQjkFwQmc#by7%hfN+>`IhAr_+5kER)JXGWqi`nnt14Gb)u$Zx(Ut%2xysuD{mmq*YPPME{=(<+&GBh| zBa-aG0&o3nI~9?-zXK8;sH8@1mXp4b1iPCTLU7(oa`KG%vrgxY%%HL?TO`VcRnG>| zDJsAds?TBwMWpARsYT^HKWGl!4~i4Sc|3Ri7PMM<;uTB*`Y$@g2B$1VnX1?mXjK#? z4MBQA2@jIC`RT*Fs-c&yPA^6o-jm*aariya^9}Z-yO~H8Ct}Ngc>ZWPZgHK*L_{xC zsFphS@>f^Y^}wCXOyl|dS~~dG?dpVmtuJG(uNk?8ngKP?{EPU94@@nxlv5iNc!sVB z*D>%_MN4OmS!&pUfA#y%oKjk8mS&}kx^fEh9T2U9!2qkHvsTezu%uS&@v#{6g`8QB zLb0)-Xl5!1-6t39y%1_zdNEcaP0sjG0Aj!Ybq8zeRJK`Pfo&lIJwPx16xlX+=Sn#|cZ%{RW{^=sve3%P;8RueN$kiDA z>&R$9av2QO9vU-Kcnx{{yR++dssL7Xxpx51mA5y@Tgj0Oa0`W**5x%sTP&v>#u-_! zJP7f=xTNMez6%<$mD#MyPCbkR7%_$XmhL)Y>W8E2(%KmSiN;?lR~V#P%OQMe0aseB zcYCsS`vXm3_B%i+?2_#^vi?w;t)n;oVfzd7WN2xyYo3(Fb>+{?%A-CJ;h9PF90?Rp ze!*`CQZsOw_mCdZE=#n*cgvB69QaSieE`INyR9HwG%o62;r5#F=)s@0s760qrJ%ag zddBU)k@%6*#QOC(gbl=~agxI|nqTXZR(brT=|(&scC{`_`=1eGP7i*!b;k!2{`#P3 zN%Swr1I$(=s@{WTube5?KT$c&%}e|||DroiM93g-wCj5kuhs@c_RCj2@#n5DY1*bc z_y-rKiJev4)cvJE8^b<&O()GPpqIlPx&cr86-Rt8-^ny;>C&k>xO2!_2f@mHx;< zZl_I+`{{R_R3t=|myXh3w}+D^XGRj}6>21QuEt~}e*Z`~tvk)%>3kvW&J4ZFWjR;D z=I-R*s@F(Ez}I5UU_BFl*lboc+h&G`JX5MT!h>(;s`9o@U-RWCx9H1l+09Nc7Xb;r zYsQ22fX`CTXqGC0g&C25b6}WGht21o<>iatsIr^0aN3{#R5cCtilAGIzowPTgyHvm zE*gBGf)6l(^zir}dv)~bTKhN&mhaAV>9QqLWfDfdT@otKLFzw?^MC$4?TE|)_0rwC zNTWU5mi}5|2&b&yxk@nIo0J`7l_-d#_}N?t2ZW-7;Mf!(bYFdY_vp>@8^Rwi|2y&G ziNVtZZH%=u>w-s+DG!q1w%v+>3YX4=#Y2)iIm`@W#{VT~Rg1N-p4~Q(zHyRLYWZGa zB!siS1avK=N9=C{g|s9SKo3$#`;&B^+2TY~`=m^zMtP{bOWw42P*R>j67kLohq|BU z4Z+Q0sQH$!gdu~^eRa`3Uy!;+k0qQRb9kKOk9&qy*3^aEbg{= zrRNoMX4{zoGtKDB3teTIG!_2wQC<9}OKi&5fB;k-*3SQ3tti3FjOoCI-QbPi#)Q{U zt<3ZezU{FE$-#5`b(U-zn(XNydvH8rOLEO#X-hg1O$p5JN*cL|dNv_p=m>b<_o*#gL;8 zI;j37p=QsM4FeF|6CcS^+0|zo5rOis+5aIkBh7ZYhAYfIAMn3cUCQ4tB^agB*KrC< z&XnpnEx#aVy}MuVJ%<4rx!B|F#575S3o@M=3-%va-j5g?NjtpD07$RE+Y0ll%GLsW zA9jGhx|4(Q@7+8L0hTTU|JKJrzGPADbJ;`mtmFL>2#p<#m1!ZUaU&7P582~VzoL-h z%Ilp)($9^pxmd_|yWcpuaat<#GZE+o@6tSyoDpYI^?zU{!G(%rVy4C$?PN1XR#`KByH4Npy{ zhU+K#%vU@Bq9sV8NgU<&GweB+dW1*mho?Jrbu8QvIOz4R{Xb>Z7q}YNm;$IK%Y*nruXHYnkGim!AV_L zUVmE5pYFLsCC_0BLQ&f5RTvp_O1T1SiKU0jbjLJy#621LlAqeYU*%A@K#RCaw>bHg z_H*^`jrQtE>1WTceW(^77P{JS`eRFTqy2)^Lk(2CJ1M(m)}>`a`#aRIL?WidK5IU0 zG~Y|sCPiS-{~f*qXruD#ZHTI^rQ{<;)d(-Xa?BLv^K$aU;gHmT;aRBg8# zt}KPxjdfC}DKgF;HGacUc@MR{{&JO{A~P$6Q5>*NkjIq20CRg7Ky{8c%>WSLVoCJ2Tb&ZY8Yd>$T(YHVg{*U>9sv)00I zXG<0MevvXPMDurNi|#$ndj_h!mn9(}+-G{Piuwd11E00E>q!V=TJPV+-D~Zkl61{2VU9Jty2JVR(nuM>y4raeI4?%;8UP1)e*ivmFYbG&BmraXTG zy$=%;*3g&>fbRknz!3BGR9kwWrw4pkBloZD{P*u*m;%61D34zNCdA7vRCDFfRM8Zz zwJbVh8e&?fB_zdIUlN*WHDh5uJ31k?dwDyC{*5k@$mKA9`u=L z-Ny9KWv1%uf+I8s~G88YY|WC)-!H7m*ae+tC&ym8GL#6 z4*YueOY|N^GCxfjdm#gc%R0_mc~cvem10*Z{fAPE9jb(q5qx*UvaCV#ppoA}l9O&U z%@n7s>nQ8t+l+Sz-Wt|qE!2O>_lX|;l%ywVt|_~EU?tRi%6#f5-bg5YK|jAOA80c5 z51YGgY;IY?Yo+py_V`kCo>czlOyv>4-V1YH=R-bdmLs&xH`Cz(5tpk&c>l;Bli51w25EsZyBW=LjGa2g;4@s_Ut_VIN-n#@=H zm?BVe=4@gsC34^R$k)8?SX;H>@b>N;D4_sHwx>H_tl{v=$6tP#5ye9ydn+t^G#9i` z3y@@O3jm<6STwryw3S9Z77#W~1x=JxfB%|dX>LAc0+iQ0U_k=N#&5$&x&gL%KCiWT}v z<$8unPcpFnp|o<9-1DY+cq*#zAJX{S4dxDz8{C_=fTK8oNh8r_tq$}cV6gjN(*uF; zy}iAg0iC-GzT;y6K>NSNjeF;wSf=a3Pu?NGC=J+tYJZq0smj{H$MkFi4F0ExItP7O zLaSO}G9sGC@cLrNV5SK}z~Ep6`*}d#1Qz&kR+Bx||m1USQyh z##aCL&W6pF*qQ7|+BBd&s8bY>N`o`nS~IO_$>Q}L*3kaaNpiSHgz=nlT#QPLss}dt zZPPqVNq~0Yg)h;uy1oJNZ!jVvN`B>E=IbUEYtGXQ#R}DQr^w@5h92RcsVi@?<~^*u z?OYSz3V&zdx2u{VgD2Voux@bMw0n3&mcGwqNz#~eKBSmMgBRrq6POmi@fZ`>oXy+J z?15O~Awtn-8>bK6y}ErH{vsr?P2<(a7kpZ$U{&bLNFMw&!a|z>TY4fjC#|g+K{nlt zclZ(dpFgj^Ap0sVwj2Kvo<3dBeDr@bU1d;|@7La?yJN|vLy?s3?gk0zB}EVfq+yAr zOQfVb6bVV`?nXiBjvuvj!~6U{yfga&W_Fn!=DF{4oh#0{_)1pK^nM?^VGDn&O;to6 zEKY8tC#%q-naqCF$(4?C&!1R42p|DAfPEh(xo5itf8CPkzbZPjJj)^O;CoV-OTCh< z!u>ax;-Gb(YI-N2B2^VBAJRk^Hk}x{Zm6F1e0OM{ zGv;O^?R(p@EF@QAPm#-v1ha~$*eib$S0yy}X{E94Zw9d*C%r_l^Yc^uT8JYw+E`Fj zz8*04358GD1j?fryq8uK;xD;WLCd*%>slWLi6pX(=^GqdU?riFtEu2U8{`FX&WHgV zRXlfA^;kT zxHmY$s<|F`G7HM-ThBnvpOXdRVY>74&}!lnc&Z=;Z{l#dtzkoN0*#>0ch;KbzX zz9DIPsl^^*ifF6FcYpO=t#EGcc{v%jI-P#6NtiY6& zW*=YaZ9thEzmvjBl{D>c`=)Dpy8H-4QO?wv6lQ{jFFW>p)7QEH{p8@UcL50

    O0H`F!*BsM3EN*pq z6#(;PWBeUod7OS2z4`4s+;IeGpR+%0^-|2VN@?$N*8{N*OAqC0`m+EqQ?iThn-+3jcDFjl(_CH_ z(A081IswdOPY1*qAcKA+)?s4y@vz-y*Yn7nC^P*s+{93T^TkzrV$&MAP7K%Ro`yDM zs94a#4}j-bdCEKK4ljzTX)?fCpfqPrMWL)}c`aKm+}`_@;ctZglMlKDaD~e?da$ok z_lJE5JkkCWh?LY~%cO>?Gf&o2uAqly=M-Mww=Aw~a6PfGVI2ly(f_46SO$}o!Tmd+ z7Uh%matIlXc6?qjs90-cj+SwU@GEx6B*3=l^g0;k$ffnAUN_R+qW)r^IZeoeMS|QP zSn%9eY;WryMF^6E20!VE%8d~1pD;d|nktmub8cG-oJt@}dN>E29JNbaiX~T8|X z!(SB7c}lb>@;P&*I6)UhqlK0;v6Q9k^Uu+%KZgs}g5PX?$>cGaW69$iMwjZ#9vsg| z({i{v7|h^#kvqSk{HhpM*hN8ZtYu6fW{6lGT!zKei>un*L!KwrH%@QF)P zz$%Nk9>BTne5S>dxeJN{s~o6*p43UklQDCsfc^pMHz(fdxwbV(4}mQ~zsQHdKo@M{ zu;d`GmQhd0$t>6h>hiT`N{inV?gPc~;mMb~M#>M z{d-Fo3I8E>95~MGjB=fYcdq@hqq7qr{wG9%Hktby20X9vCGZpjgj!s~65Umb?|`D@ zI*{=^ckMk2QZ5y|?|?1R13=sIdUQD4C7rpi92c2^L4R0f z$C}H?cVZ4+f4?~OF9(hO@r=MO`(2$y1@MAuPTZLo2V8Z-adOUa4G;hQ5f6^37CgA> z8n=#|%LM#(Ph=9K&Eki*&F1Ist@exbx;Lao2ejWe!6oE%g&U6e=Y<<5QjdKH_9Gwp zOOC|073}{zOq&Pu7m)- z@lpJMe?2H!_szy@>70$j`gM$Dho5GZ;Lb|zL#O4rTe`W3;QW&*u+yhBQUkN|X{<9* z4aGUz_?pm;%*W%AAwW4|(1hn^APZxIUcVc?n*{Bl#))nY_qc$HZgf2r?FFuHe6_km zjKq9d2FlYwh?NcW0jbMvhPju@><-=eMNox5_4cH-Iy;f&s3&(qhkX=Wf(6EJ{}UFb zZ&{arILfa`Po?yOcLxm3HbX^HVBU>4l{+$l6WvSu1YvR%5+%BNRMMYBKF^^2Q;Mle z9b#S`Bs(n}qnRaoq=oHmpJYU!h1a|MOJwxR>v2!OjM<(!snAKC6MR!fma8Y_c6guU zoLxNoGw$_4n(Bm+SdjedH#i0IF+Rg#{eEW2(&p0p;4TOcE8>$$Uh$>8olX1xU-gdX zrMARhJnB*gbE#)#w=ye?)+T3~6^8=$+gWG8#=`xj(wMATsbQ9Dg zN!$LqxCd}LD-nOZQ`oF`uq%cYR)lmaSY>`ul!JYaXci5VUKeZM9?xNNQddX?uq zPSqmfV5klBt%vir_QX+h5}1%Ayq@v%2>;7BTZ@$XoKEo|6Je2EV<;h@;Ex{cU)c5| zACCmt6ac3P>+Dba)kZR3)wJz0$4g4DUN(jL=c6*{FY!b7iPObNs7OS^p z@7m2f-{Hgq{}?W2{Y>pFL11Od{Wx7~RS`E1K;c`CQvl~OYdo{{F#&4i{o1bke{SRI zcNUuVz|XZO!*y{&W}H4iA_w5_U4Ue}iNRA}kcqa)1s%ZHn|mubDW(Eq-%_3PTMJEx zHLH1lVL0F~bZ_qw8w5-WVmyvvHV!Kqdq38fJwudE0iQ4nk1>=0vTJOB|GoE^j20Pr z@86MY4(!aD{nXn!Oq+ru%yVNE_=6A72ZTG@3q>gU?)NrPCL0FwhxqsI)^_Q$&vQ`A z!a6&;F%MB|2r;PxPsZqPXsh zdNQs>_t+*U@#zo80diAVhrZ>j>z8zVSWui`aoHhtHy$0d71dk)02&lFQyBCY;BJ|| zx+=!xoAYH2^GF={y&AI?1{uggomyw=elK_Vbktj#ej~x=^duEwslODgRl)V8-ipS? zZ*u+6gOl8oMG6YDVy@Ei#pL9`pY~HFjeJPdUpX{WyUk&=;M(S6Iy}>V@hvjEvmk|@ zBJNKr&YuM9QB>9Y8TT(tGNA3uz zyi=66%Aio=mn)Ar%H#lpO6@E27s6ZJ5oE@O$;xL?PVKCOI`v!{`s#mtK@#NOX5GKw zC^(%_OHHr0o3IHg%HEc_Fk&bv@ebizkJD#xbBZPPw!!R;?tibf&C=e#%jP zq*+?}iH#%-O>jrl$y0$wUgZ^ceQiqwnh$m>m5=bn{bWB#VxA&^3KiKSi1qNC4oo*FLmRWgNHGaOno`(*n0%w`)JI0UK)tQ{_f`A4d z`tL5y&aiIz=q562(zE?K>-k+r@yN%g^&NpaAO>y-Jme;ZVIBddtH3vyzLNBgl#Fdr z;_RQKtmd5~yea0o?foyIGw7TmtYqlH1vU1kwW%S*;TE_(X4_ciGZhVIseydlVA&C1 zDTn}4=P7!CKk}fcv#}Nnm{F?%gjIkj0RSjIify+p($FR-8lDGrC=~g$w&hQF0QSn# z*Q?d8zuuJ|68O7b0r^?+PmlkLsB$Ql3g+x{cxqJ%?8GOM>yo2m3<5JI?r%ej_{=^C zlbK*OH!q(3nd$YHxFCC8B*6k7q@e7gZ_qyPSKpk63gaxSgy^ny0dxCKacVRWq|aUN z?bLL4o-8yl*(t$1I{qc0dKPXIVcT&{GF@*;Ip|Udx_2$iRB2N+KR2QBm{BV3z;L?u zsb#XSr;Qf!ww%dPPJS`elU^0Oz2)uxZTWIL@IG~3=3*}E(t&Z$`s+SvfD~4t(VX2L zsZ`YJS&yZWBUi=>bINda@HylfZ~B}uTeP@5F;8B^WaF66EQA00^MoDh4atMXv3-X_UBPiZ6QCQ?4eKrH(&QbdaQl_gPrJ4_A6%Jt5!9&ZN0bNUeaSe;P+kdklVHD z2tF7D^=9=7^?ffg(${D9@tgDTHv_engMX@bsKeiNqINCpE@`?nSUitK%|hSu3Mswr zg=^}=<{Nxk%{H4h37YCe!^yO)^7Sq@qs%ye!-CKHs@qK-Be!v43*lrahMDVr9SY)C zdkql{6u^7mC`P{+)hVT7fRAM*{|QVqUo51#6h0Z~p$i8QonqOTpyKH{XNe4Tw0Sa@IK0jOkG}>iFgnoY<>#a2EXN zZmc>TzoU-qP)uXd(c2h2V(uZ237lX5)Cdpn>To)BZwi5T{g;k@b}t%tm3#VQHlnpA zaAUu!-HYvxD2OIId`Cz6kKsla`1*BJ+NSB$Vea>ZcH8r{Z=zK1Lo+N zxeZI%1oTe0)OQmP9JtdbCG8xNZbVrjXRo$~pO$wOwz*dE{qw_EK2j%JYxHAj_#F8F zQT>b&fL~%t@X;6pmX1^0xF* zffvH7yuU^1{y?<(;IGl`W1HyKaH7>W_q>B@`XE>@sEUvnkWwsAOLT9DDC^f)MyDt4 zE+S?S2Y)m%h-`m4XaEbeFzA8SmGVy%aZ3Vri?-fH1N}%O1$kuFLDrNUx5)I^ab6tWaL!{T12FhT%ky~V(47Ns*;b^KMHX0^%~93wl#k50fb zgaJf9o2%?^j+#AA_v=sy$BDlhOwGw)M-P@NmFNKz$RPFIo7Ra;<5O{TE8`S8YBssQ zT91Xd@r)D^t#|Ng2-g{{;ZF90D&yx0vc*QlRJ;HEHTQdvajZ$24KGG;M@RpR^Ihj# z%K~W5buiW-@5)&AufWg6Qnqg3WG92fr;{~HlKfqAIo7my0{s>@sYMf0qIrL zjNsJU_f>N7ImWxv=D$Mzof9FDyfTmUJN#UMW|+ySO=A0^d)Yma$?FXT;W*d`(HeuC zj}(`fQ-1JUvN1_1PbQ14?;1Qp{S=4_!>`)KZ8Nt7%dHhuO*`(subHP-%v^u+>4H|T zYz?;C%8PL3hTL`)Lw+cvoDPRK}3 z-%9k4;u&4YB+lBod2 z8<5{?b^&kHvJ%2|6#Yvw0-Z!{;`r?RZce$%8unys+C$E*T!S9*NVM=8-UT^lRK^_a zV7CSKlGzl@>N~w2OHsEtPb9h(dVYh)1A91<+P`xf(Jw-M9q2ZXTVFe*M~T6kao5`{ zwpI-of?o@<*KZ?8kk*fJjqC+#Oxa#x;e;VrJY+};ERr5&rLO&;a0C?gUFT_E+vjt6 zs`a5+6!Fb&M!w9fjs%UkH%vn=|4(UEM-Hi#sjw;W-z`w3H+ zri79woRzgwQ!PCvBa;L*A1}VXWz^eN(8w1k`L*RqW#`vS<@-Jd=^@6ZB0bNI5LSZq zKdhtuomeg^9f9jeDMfN#<>s}Tsmk7y!HQvNJ+Gz9!ce&MJ>RMnywb|U$dDK36N|V; zB*e3>I+@o`)F|IPARFWdlV2L`iLG5w?=J6A&wM@!c#o1p=~K#^o6^)6>KTd4&cg-MKo>J7ZvOXb0#){>+myJLLJ_lWhdL@-0;tHlt4OLv%E>>;$|`yApXU+woO){ zJP33Cd~WP|Q9Xr^`jZ7wOOfajZ+A?4?y^8#Gz~}Zcx%C|Ex@8@oks51(8}iKEy18k z)?s1pulcjj-Lcl?UBKBBc*WAZh|aIR7w-FpK{cE5)}EGk^5fDU|KRJkuF@a6rTEIBJ=^;t?(g98Cy=A(fZ)i$)pZxa!BQ99M9D^%FyG9LmT$;gpRTe^ z`gXqZNP0O?Yn38fPFU|Jh`ad&uX>JQ7uTqJPI~zuJ;Hl|9pO3F*h%wzzA5?hcOcDJ z)XCiZkYQ+FB7G0bH`2b*?Fky! z4LbV${OIEn(k*aTgkkln z((C%=l|1Na?w{ZavhaX7ZQ!wM@(t`SHBZ&kOU2Ixs}3p6YrUtfV2FHS!C_99D>|+^?-&=n<>{zGR?3o`J};mc<}M3* zbsy@>KFRk=Vtcq$&G6;z&&dvlKs|7)#i&8N+a8|?rKIKyN>KD+>Z*1`>8U%H1n`21EjOBHa~d1W4O z_xyka1mUaPUHv$vOe?YHq1GdFgRn8S&D1hV9NZ(7e#qLcO-07@;1jU;O) z%JPcFj9lQid}PbD)^h|H1wb?4ti}O}Q9c4moM6Sx`vc*>^@5cRe}4;p1x@-RX_b<-?PAXL4mk<)nEEw!?K5ZJ8R4V#h##t4Eqo%@9!aIo* zAaz{(5L|67j_ue531j)pyYPW{JJHHHhq;yLX(AE@?|QG`RV8xe1idW}9oL66$eX)6abwDF zvv)rZ5E#isHS3aVcm4~71Ll~#11E-?%rt21;YIf^wZ~&-y%<l$j-)PBRZ|WtGcBR+#!?=L1wDpVeoc^VCKfIfj+%LHntT}k6!l=ti50S=cbC6j^@PMmkDAc4g6qc`tX2>d1q+Y;1 zikrA{kN-UP9*N^g6|V#Sb-y14ZH>k>VS}6W<5MtR=1=Bf+JmTSsT(5oB3E=N$&mN~ zXw|aF^=4Exh4a`sEdajxd8QLkt71uflWo$ro4<~m?};$h9GP;>phV7@D3Yn-IKTSI zB3sJxF%>MAe=IKXS`dU=!jeLCv%WqNUtn2Q^r!j7OJKxakM$m}x(9o<<2y%~8?EX4 z%pw81bR5;{nx{@zt&ZSqQi;y^u`i9ZJ&1OJfgY^&#s)9vB|rTj{DYnq*?b!O6fZB4 z_0U8jE3>Z(IL0uLHKYDXl+MDJH}tlaEV8ZK*)p1FE&=6v?*kcgrX-rIVz;U+V4S)ND3}!B@u8J-^%%8n=S{OV1Dv+_w2%;R1;kgj>!|F<-aka8?qh8W~p0 zi6RnRRQTfE93vCPS;2-GS&-$id2(bnh8`DB**FX)q4-7D{yJMGGEp{er_4>`6jlF1 zl-y?OEP&RAvr$;iWF%#_cAY|)QFkCOL9ir!zh%2Q=2{>Ev)nM0_1P&8_J})~_JGg1 zv-Cu+@myz5mTxnC8nJt%!w#p`eiY&KH_o#o`^vXw)6A4B!8;};i^piO2q++K3dmr zMwPGBS|A+=2mkD$G-VC^Pj^XdL7;`uR>`H?4-7;YTTpp;%dg@qL6yY$Q3R(aT^=*Y zMkP|s)ydMAhEJp%4xgm46y{UFY5N`JCL$U(5ltdSSg)P^Zx>Cb#sJ%Es>M;#v)a8I zkItLBya1`&!`4LiiVMpNFNLN zIKJbGB@qvney&=9rEQl-5(FEYZ?=mRGwp18Da*%SIEc~(s zBmrW_r=zZ6iTAJ0SQKZkAq@kA%NW-#@h+={!dh5UG=nwbO0Ckq7t$AWD+UvymR6Nn z?p?reDECe;{a37?G{b^^m*3L^BXw-`L<#SGLx5xb$tCQ*Md@?%0XdZ7Wn~~*#Ea3X z1LE`7y+sa{bu8WTfeN$0rQ&@_5L+peYX`ZA%6b z#F4R@qOi;KktW3dHfBz~HvqQNEz}SoQ;qS>wN9giG=zlo^W5XRe(&F>j!Gt3tQx32 zsTsoA)we$U`~v;KBjmjiXojzWLOZyj;mM-N%i{3Ib>B%R;lkImJRQWxiKjCK*|+{S z9VKMPSq{jCS``v>W3cgku{RO2;GD}U`@DTxWEE{rB+?IO1I-c%UK5JPCC}h+P=fU; zk-_H(D8S9mpar=e6OQnjFcde&X9)|+xg&L$d6ewBObDgd?D)dRkokl$_2u0P>pH_(W)xvcjI1$7n2-e2F$S9S_*37mpxcM?0 zQE!=Di~drc)B(A7pK-~8u#0KT$KyeM43K*+%`hgwBRoP~B zQkGGHOt1*sx}d2Jn-na2wz=Reu`Jz_U8&ZYPf3cr21nh~$MF5rM`2YvK&%$5B#HbO zU*B;~+==a`JJ8oC@4A<}7AjHP&rFCHSS_mOCXjXsyKBjZk-eSI+}`zpH)@l6(KaCi zH4h3sXh|C02jhPTV9Ti*=X53@3O($m+k``pZ+2~g_-=aIVsZ^>bah>94S8kbSrF{CU9CG=z5Hq7Aa6@ zXQ{RJIX?h@Tg3`6rIgA?cWc%1L}E@Y0N}>^oMOE%8W6B@oCxd>k7TgKFdaqEcS0 ziZ9wD4g~V|u7Vb>3kydr$Ag**38<$ZgH|P!%%6P1`4jf;|D%9K{{)Z71YB)=8JUn}&q*s?VmTj~s=9p$WGSz>exhm-x-8f_duuD#cO~i1VpaXIL%wB+S3BY!lp0Z(Z44e7sOCZF4W);@D7aiJLFXt zIJ~FGs_Jh2NAmrr+;?T*w+g+zODq?>C*w`nV|(zy zsazoo*HYlM_3LAccJ%yk4dtn41lTR;%x8t(nDgyrQS;mz&-0Qqk-PR{W| z4Bw8YG2Uw8ArDiq6O^M!Ym#olcBam}?tx5dA@A?K+tr!WKn5v}(Vt*)$SC5c zwWfQ47&1F3yD-Q1nM0FuAmL?~&4oUkX6x%)!jEF2W4JlOT1Hc3S(s96*#kuZY8t;} z54OGBOA-w2Ypcvy%Wk z&Q4)L?|O^7S1cXXzjR)%iHiv@WzzW!A7Ds)a@Al~)qFxmA0G%jTn=NeRS;3d{mm5_ z{cf53u`lDtr;?4zU?lyhL{l*}VZP8dy*i*Jk03O5uh$zjBCw#8ALaaul^ z$+*gCdkm^5=b6nZSC*xxaOPGTfxv zw5^JCVvIUt$~W}6@6{a86kB`~(DsNMSUcz^*GpY%DF>RE zxj%R)#}my6#WGBp7h`pJb1hhS=k;BS|4<%9Y^InO+`FnH>2@k&XMmg?dy8EKE_OHgU?K+HODu%+M2E=CnNFos#okE zqE?0U8Xr{ui5OFke<3#;^v)%({@$mh`h#`O3F=WLes~wBp6nFT8D_2`k5h)m`%Tcxy; zfWEy{ms`S439M(*w&h7KM<0b*E78MVw3zQ~3>`!N61$DIPRF8dwd>ry?6PkseRjT< zY?Iv4vbRO7ETiC_7+ zZPqt#9THR5wXoy)Ugh^s$pL2V=?e*j{AkS&?S(G^!BNpd*p5{~|G6a=Qyw76H6Dsd zWe6aV;o_hR`^qxeZPS08Hqrw%jAvQge=FLuyQBHg%)+!z;sy!+7}L<|>V0vz%VOSy z{ER8#L}6eFl8$@Jty3)M6)6ssULNZmtPJ((?z70wlwoC-4|sg%etQ{brxMDbsZKs7 zTfj0?ss^Trk&(lRf>(J-5oYrO*3RbvbZVfAGf4!MpL_W@X(O(F<-s|>_?r@@&gh}Q z=`*t(Grzs8?`K{0)Ifb75U+iS?bD*veGrE&xyMMs3<8t|6nxW&>sJrEku9vFqFCSn zoYU!&OI>hikqkoLv8!xMWPSA_IIoC9|H59T%1i;?xfI+}>!Ne(76=>xa@J4gv0fH7 z7R^b1Idd7QtU=<3N$l8uKip}>3@SV#<_RPTfH&y2@H))d)Kf zje^CXGZxv@T@F3(2J87ItkX@>l5A2bXF!++`uephsV>vC#QCP?>T6*0$dlhS@qJal zrr^2tl9^9m(;wRBi+xI3Ob47`{c&w0%pH>i;D|*w0u?yB$=|ugh34V(KYYLMr&F|U z<`Zt-OGR`Tz_LNj)lF^T6Qp}5peck=0sOmV^#?b@v7~Q+G`BhL7s)^Fwo04Cift;n z(Hju&qTh?+(`oG22?CZiG2iKmTZi%aI9}cO-bZn%GBD_rh<;<~?{L;F`PwSPKuSAE z;G3C$AjZKtbY`;zw{UcRoC(J@O620f!vC=RB^wf z$UeZ9K(N(!EEC1c^&&ucG}>MMyYt)%dE`D63HlKow_@m_2-zAYw|MG<|1?RW6`Eh- z&4oYbo(f-ij?1D$F)3)Nc|S%- zw{24Y2MMSZ68W4Ey-Ev>bRb}t6qd)?ZDg9v?kGUIiE(Yk@{OhMi3hoQ?AU$dk&u@5 zfWg%B_U{TW80~hPT2tn>se0rx?E46s=-U zhBDG#NZ;P=U$L^92njP0$|t>8%Y_d>lq;8Y0Cp-!wm6yHly9WvBrU4lpZXz%2lsM@ ztf)a`a**63q%`hSJtlzgybE2Hb`rf=bfPen>v&9Rr|+b{#hSWp8=?7Fq$xS7A=5)$HBqWP7(d>a5hNr>FH?6JG0!4 zSqbEJewUG|+S8#)9HXD?%}un1)#&H`Fwgmh%q2D0dRbDQ_|ifflK$s=|0L%*UJ2ju`T+XZ?yg1 zslAZ$H?o94Ops#)c(kyur5s?^YPnwkQb0iPC4hnD5+}Yq!94m3oZST8%3y^krWMgC zOZ>*H9Q|7NDTC)UsH3mc8EFhXoxJwmiyx3*%-7)OIw#RnllZMndq)76UyMctF*ea~ ziwOOVSg2m(-MK4NkU*7mWL1F`sGG|H(UCh*9Vfb_6VPtW{k4VP$`^j&WGMF?{odc5 zAOA*@%;K)IHvdMgNIz*@AITU$gv+n zEBkq6gDkx7uJPf7dF)6}MtTc|wIF90AKnfb4zga6B*dDRckWL`3hqnDnQ_-!VAZz8 zl>aKbTs^7B2X_(_j@430RiZZe?wV~0eOCz<(?xwp`+WrYoAAdgPdkS9T~-@B0jsHG zNh8W2BCY!mfr!Vw0Jbe?M{o~$3UQfF1X~QM=mbHt!+&Gg+w!6@u_%uu`qceg7@{7k zUu6|KP~A1{yBcci#3ekG5CSmTQpl5G<$EGLG+8Oi+(9(3{;pRXTk$Ry;scJ5*u0sQ z2B|dkR)BIx2f}GS`~&~y0X#XWEhd#cKnyG->@Ge?nd?n8$gbFO$=&d_c<^kkC|kpJSR8{?YS!whep*A(UAxjnV3ms*nlc{~geF1gg%bt}~S;v{Y2o7a`N;R#|6DUZ;{>-GBXn{qf+MSNK z>it>`YIvpcGPe_C3{3xf{>lrlR@2YfMpa-(-}3#BC{|Uw*Lxx^XAwy&%5jy>JpOl` zY$3-+WRmd?Vph=ccT1aZkhZ&j(J(r@`hMcuW(;@VF+!s@HuuSiM&9jgi;&86r8J&Zm9GeGQciY7j|qfD-^3h`<=*PG!5d&Iwq@6cA#(>{| zrHInoD0ZHaxxf03-9)tvs(De2H1&7UO}t1s3`%UdY&2I{2CI5eMTVpiOK(t;41;PP zCs%%h%zt#`rt!0ep>FNnCTdt)u`g{83~9Be;>XExW7@Lx>fIPWsE}2=E7{>xpI!r` z(JqzI$Ei;J*V=~~`IiwLFVZM|hLH#W#O6ZHh(K);xHq@`U#BzRnJ+xc^K+bqVV+{^yug8?YPBwd=@d zA;lJu&l^srpNyd-Heji9_vJ;Z(|!G`k=dsl0&psibbqeGts61pe^?@K|>Eh8l|%% zoCbUz8${rNr>aHwjSq$4DxUet1CNj{HeAxf*mq-0_{jN2ol0E`q`}Ao6xp_PEYMy0 zq|v0e5h@4+%OfC@-}lUKCPsaEooysG~OseL3;_ZX+V!>-~Ct?`cw_d zd=~AO*WaA)oe<9um=I_M0o}B)n8$V2&z^!4W{-=@w2$+H* zFF~AAN^?X%sts7`u<{1iVCB@Td6Nsh=EGYoFiYid-&Lut>aTjuKY;OW_d8LBCWUm_PEQ>t$PX()@KcxPPfP27 z2z|4-0|wBqxMiUZx0qbD@`Y({mrdI4jqf!UaLaefIs->8cblV*?3`<`^ao%jwc_%V z1pI6e$%a%FY}V`#h{!uE7rHOaPBcJ*b3kIe)`=Q$yfKlI#8!4Oz0CjNJ^`@{1@5ac z^~#?lO@Eedh5x!BKXvpqirN^al+VyYDV!fZ{ar8323|$N)W8mPPG-h}cQJSc_Hp0WeoTGgA^ zH@lOaEgWbT<&5!tXV7Y^-UWo)HEn$V8f;nKv{UNn3psq=-o#nk{u2IDIDtY>>}0Ua zk+Mg0NWB<-;cXN5$l5 zxWD66!r(x25YW>!NgZ|^|1qc}PSfjO&_{lj8l2c*(7|#++}-+8!9I1JO$+jSt#m{v zuf>%A{ii;dJKGuHf_QSbu#O4YIY_ED&tzw-S@(n*i{>^bMP0?Sk0eet45N!)h?ag% z9Jd@2C~=IL{u;1+{;}YF1~Y-M(Qh|KGQ<}tkGsfs$NLZcdT_+7t0z~*Ou}{6hTc>B zQ>nzO_8t2Fq9nD8D^x&}Bqsuw#-j^KuPd1BI~aEkiG)uVUKMZ z{2Iggqsiu$`wx{~9MwiNG?ez(I5QbbYa5%_%|4?MIMIG8d9b-#@Fd}cIvU8uABXc? z;avA`5A8>##JS<~h~517-Ju&nG#R{(imzy+?%RAdiSnQmcjn%%vmtL)jJC_1+kkv0EEGrxEf3cER{Uz{jbYC2?J-kkSx zVi?7V{DJHEwBRsEz_!ox9>7>oa=7u^?BiHpdR!97^F7-BHC2a2RV}v~7Gn)}j>#j; zLm`*t#;!^BtNp}JkXC-pNjBxP#tQ?d2s_=4tV?n!LGF|_fJzE$04BGdj(@~=22^HU z+x=)N&62*>e<%b0^O^i~yWRx@eDyK?-INXBJT1S@m8}A_t6r}L83BU5c%?KJm3Hp{ zPP8Y_ONa8yaU}FIQq41LNeCN-@R*0oq^qBwc^Wqg4@X(&@z64l3 z4Nfw(_0o^)^8np-WE1V1%dBng*zGbeLlgGpToN=sRkZJ4&e9L_Km%<7cTuK1P1p-f zcm0W)1F_vD0t&N zIK@b)(~(vS2CYci%>GtM?FsaNtC3s`Sj`KIc;W-9oL`S=ESD~TDga-8oY0MHVA$hY z02&G{bye=vN;2D~vH&d8l7jDKY#h;jX%_&OqX<)ETZeTgCd zWj|n3gJ9k{zMVPJVA=DNp2zhq<0+>z^T32YcQ41_YAMHqc!w?G2eUHOeuYPT-fYOu zhQqEi8p^u224r$^{S7^zQecd4>6l3dv(y0XaZ=ud{(Frc?+#4I*58LxU4B~b!}H`! zlb&Wa&I#v~*#r0o5OARrAr znwG(gx-Y#_y+^tD{^>hPCoT7w>wkA)Xe~gMj(Kt_sNj0>- zw|Y=?q99I;JA9j~K$+LgyJwyDWZLGXYb~vuJgdt;gH%9(f&<_MaP(eE_H*QqTcNKn z1p!{qOCNaM0JDBP;8%eGNd1pLfQs$=@R|{~JkNszB5;OMOD|sQ_4H5yg)BP)E59jW zS{t0cn^_XpjjXZNq+dYyeiW!shclp|*B#K)bLqn68AypbX$?aotEI~1sJ-DJ=d70i zegNq7u8cyipRaY||6KWlP%wD$L49GDRB>S%v)Cxqjirydo_4v;17R2qW%rlJs@sm0 zm;M+~i9q#Bn}fG5(Sl~3T)lm00g3WNdsQIOxbFiBy{;M!o(D)~K`w4&XhlvUNgTfm zX9}IFUBHzA@bgx&f3N5o>zgpO@rs;YIFsRNfaG2O20UB|t!2(`X>Rf{r}ukoFz5E5 zUTgMECJKT_52*EMKb#v1XO#D3=>G`&Nm2u`WLe;!N1OGQUMKFLMpwlSKPwL2WA#S& zjPHH{)f|5*h7U&YZnc(3#u660tISWqFdnJ8BILkW|9o9GKPXEnEu(Mw#9{fDl4SHijK99c z>Oo}#*?y{t?0aJO{wvLTm!}^Qjt(yUh*qGQgBR$(>MlLY>YE z;0AL;-D=#p@Vss$)_9tt-~uj!PF1 zsjnCSFnx;9Pg$Oe87GSYWZik;B%k37D9HmeF!JW<<*8KV4=8-M-+$Yi>-?*kbsDS} z@#mU|?)d;OhFz|d2tbDt+=2F)t{0^I_MyYC#}s$hCcJwspzp{$OmqSOd5fLL$Jxh# zQPs!~r33A%EgJ}y1e&1XM8?fdOMlHUO_NcG$}$j+5He@}?7g^IC$v#lURX5P@(+N6D)=BS2Xi=`3qZ<}Uz?LZweS~(V0MMmWtRvQ2#RUl zTYxX39wN84JM*vWV_>TlqJw*c86%{MtO0oLj+AEStu1{Do9Rpnu$~&WFX~_a* z4Qo39kn(MBldd2Enx!JM7_lYuzHn*FU-xB zq;Iqja@cF*BC?)--h1@GRsX7wW|c5R`E{37BF!_b%yPn((wb{&^eFa{c8Ex zVoffEO-Jqx&g)XztQnOQWb~h!rb3V`O<5m|Uu~Yh1^&l3Q6ZXGJs2%qA;ue~aba53 z{zo-4C_jQ;en~uCK$9AgY%00%2Qe{4*F3rD)sCU28~TilY5GovrEs^s4o3(gX!_Ur z1}9^_YzaXqI(ME z35Jjy$gud>to3Ed!HCqLbtTT~f?j_zYooButlNDb%0aJ(#%HX$)@)3I10&-vnG1a@ zReIM@7jNzg`q1?8I~{oji-(9v8=PPB%TAp?X?l9)7-Ec|l56&*h59uob@0t}K}tTw zYH!))%r$Dl3g=rd^F?*+gLC}tTx|J<%RP;b~|IPuU zahSJrOT4eVPSfG#x$)iP1k)t$0?CW(r4tj4lhzaN(g@B{rM~EO>?xt}bHgWcBm%Yo zRY5N1zk+h0hcy76+Te0-Da8mZe}H%8&ylIk^OlIg-McKIeM?&?JsDWlfrxDvg+Si4 z=SIjQGqRvJi`p;NvIRylYjgK}a32_@$LlYnMR0#X5|4I`}=X02aG4OyE$P)oiVnU7&P*)W!pDKRyYEO;=YDkiDY$U-cR}zJ- zEF5^6gu6Drn^%a8m9|xy z&4`%6zA}obwgaYNtsNY_-3NTb3|`UW=1iZ$k+P!~czzKx5huh1p6$BdsLS&y78J5S&ic2l?3dA`0ZvN}fU)Yw zg^Vg^aay$&zXHEKqwExNB)C;4wAK7bwq8?nw!JO32H;3fcVtKbn^a=K(*Ht$$FC(&B3e^JK#<6sa9f- zwPB`M+#7^XT1)%0{=E-E8pta{IVGcxD*N`mUIGIt3#8cgK(Ll3yLJ9Ls=Zyp_P%&f zVBwaNJlC}6PM&&5oK+!};Eg`ZAvZiy9cmhWiihmZbx0H%?uJ}#OeK%;-8Ra;)4yvE z+^5d*A#PJ)vS+ZtP?NQ^byx5d+T=Rgw~?Hf1sgkn2Im@#y9|k!4wk|VV_o8H^h26l zqbbTR6k=PyaSibKFqZCJvKC@nun5Ue5}CY#?{Y)E_@=bz%po5sWU;84@gi9{CTEr! z)_T65om?E|YA~n|#q-PHln{|Md-$sb|!nJEOgR@yZ zt#h^1`)xAFB@~DqJO&VFQ9C8h)9YHE+$9}_b|SJVnCE%eZ05nGTxGuRX8c13WR~YV z_*}-qswy10Ub(VC@;U8PG*LhiLDxC3vuv>;O`Mu( z;VDet$)mHsdll)Yc;nXJqyw#VkRE6WL5VM}pHq4(SZe72UJ^Kc+k>BQ4LAmC0q8WH zB7nY^ zf1H2qJy7tz;r$KZ2j)lfN;cI?1%6_b77Os;cZ|7G-{VOf33xW^7Ao@cJ%U)FDNQN{ z+rtx#t~=F&BsS3iA2SOBR}BIp32m>a((<~hgppYsz`rPy01T=EfQb4)Y%8IqoQ!-3 z7+?heN0rxBwVeIZ(qQ&WqQ~zp^}0eTT!9JqxL=K4lu(D%RE5={bsq`<63>WR$`dOk zU){>uk+%){+X$BgOXnmIzfs7F-C?`S!18<}eygBdnH&bInQ!d|U?BONdgCkj>OX~M zug)0hPo|(`qL3N#ScrWlYnaz3U_On~P<4LA{PaWMq_fOFL#`)`t}^23LPCjzc8QU8 znbPT{l81_PiKqR4#ucx;&HpOgXtnPOt@#=5bF#CZm-)PLwno{^@0aq%1XQbLuuAZ4 zsK=ao!bT;+g@4Jf-q{^x;+eeTs(-aH!aX=-gMIq$=Yl6ZU@cgZb@Lk*6Ni2;>v(~i z4#t;S@l$=DSX##u_32)O;Xkrt5{w<{M`oh3vk`#a#UN)>_q{HI-# z7!@WlDbiaFZ!AV$h{s>}Qa-uLTPow#Pj-gtkQ*wOiF+zpjkLG@P6nOx=vR zOf$fq=H8U{5>_kpbJWL6PQZ4PJa;Kv&6xISIFaI#TFxhGoB1#-(DZtWyTGm6Wi!1G z(dilmFBW9s=YJ4bXLG}8egE-h685E%y^&$Vugq`j^)vVGbMWo6*6f!jH8N0&FXTfC zaWsZ4nsiBOjYo1)TI#CCJy5qiLX8RjFEfonWb=zEju3V;ndxTU7sWJCskz0M3G)qjExhD zdnb*^m#4X*Ae@)W&)jm)w9`X?8wao+9@wI>0(1mA3+tx}lqhu|3dEpTW3z&#?ycg= zBy{*uFPkw1>!4%6U8w&pCy1Ua_C@-~AW?-n4mRotyby|{3KW1M!9QTD93={YFKV~e z6M@)MQ(LOYbS+Uq1#>TGvl#(>lWY-yFZJk)^2-6YU`Kh$?OwgAwXp>#R-hDzyZX6o zflaP+MqOj0NnesGMn$uz(sGecPL%fAdYG_iMqL_+H~`L&0cOgUTu$fpLl@qBv-BPL z;=a#9{bv6FyTUnBf8D`q?qlf?!;}lTaAgd|+EnhZXC6)$YJGCMBt`eO1wgaa3=kf;3k+|)W; zwv87veoF#$uH_wEH1Mg*eIzj4&Y5}WOXQRW@h^#lK{u;~1iSIUz)}p1!$7MA!0&aF z^{qMoh_%?v9ia-o{%`DcHvb95yN_wl)amM;9OCQ`s8#xMmJB@0Jl}?1W9#TZZ8}N% zR-3Q2Ures7+uVd~cAu?1OZy$`2jL%}W7Bt%mabzD>3dX3gKFblbO_X*FvnAB=`qME zkwS4ky?{2E^!K4GKFo){@)dOWw7EgN)+cR%p8(pZ!javsBJ8M_VheP8#ABGq=eNRv z4WKl@Clguk=!dw5o!Lsx%um0|cd6%XkFT>W#%DbeQQ!2D2#OAqtLB+6DXF$Qmu9C6 zWeBD~e)R>WZ{M=ubjwA#s&0t6ce%bhVipAV*&F9I)3No#3@cA+g3eYvHIY7#Co^kv z(wsmb0gmG>UViL@8_0Gk*w2FfKJj=sJKvpOL|$iMYaq{!+Z3zo5ZpkE>`G6yl?!;7 zJ7SLHOugUDt!oiT-uq9sA$fY0#guW@HI-cw2m%GCDO%q>iPmASB+e#SH*GwT1D#vZ zL}W~BgQC|GFf>xi+lhX) z{w1r*qmXCm+91=+f7YWYQGuy%cA=A;-RPJ4nYTj zdt;q>CyuH)^2}@@{iKvm1;=aRB=tXQ0-q2DvHFUX-Fi8+cMIU#Xo@c5Z|QxPqz9F6 z>maErh&z<`cdHeK_5RE*2gY9jfyA@Cn8$1eQ5FODeK46OWxRajIks)WuLxT zysV-Hd`W0_r+`slYWM~10i|9ASM0H|SDK}#fC-?806aHqybmDu+9S$_r7-B&mtgP1 zPX|am7 zIgoo*^a|W-Xo;2Q7F-7o5a9Fq|74iGN^&VmEV z@wvYHmt1e;+z>O=JT9h1Gjc%~e~rj>%N)*_K}5oBCq2hB($a zKt@bqABCs3hn$_vG)caIZlWB|PLpK)aZ|_`E0llu5uQn)s5#(#YloU~A$AJwEx0uw z_ppXjBd9{c@{bDf8)~>^`TuT92Z+mNgg!J>!7%Vk_i?SxNkUZLQTClcPR~R}8))vX zXUT^*iZACpG+CSCZ>R^C0BIl64YRlB+Y`Gmu@3v#(%5+q1)EDs!GGCLL_d;sS4kZG zhfKcRgKtY0Z0%Q!>$S_St}`%P1Y_&g3-Lw~L)91qD0rIADpi=-f+k87uA{^z@-KgR z4E@XZy{JR`Lc)@6Jn_P0cF7<)LP@NJz_fxRy5-g<+xW$WknH72i_Y1{BUSq0a*{b` zjry0~(;*~D-J4#D+4oB?7#fI0 zGC51g8Ssx(2#IR^K!*ZEzn&Af^TquDdCS>>Sp<-x5d-ja zI8_ubpT_|Y{VbD|03Kj%M|HqK<2C0S$|}YVVprgz3~_?N0PAZ$G)Jl@3`q;~YbW-b zMcfzuf1H3WNbLu``%0~fx2O>?+^voYF!#X9XBvw?lhLR90yWC=u&(;8);x7ismm_H zAp~&BuW0d}K&JsP5)^1*Y|cg5P1~fK!Mnl^Q&-%o!S?`kM?g$ZMI|89U8}4CRDgh} z0laRO)P5RN#QmS#|2SI=haJx-0w6lI-U7vw;~GCd9>(nZJ>lkTllZGcjHm+8b&mvZ zru_g(Q@D{q5uKboD1>(AA5w{;dcuE@kT><|Ht@!{L;@na94myr8ifF|3Po%{3uQTw zum#8BSxI`T9$Kb^!(;KW=lyW>hb zfi|KF^p*vukspzt`~UhwIXWk7@?*qvLdc_BIaZ;ADKuNr4L6s~Cu%bP8>G}!{Eewd zhPfN*vnq`3h|Ywq!$CY>6GB=fvghO<_Ug6di!Eh#7!<)>gxrys@1OHiwq#UgEd#>E z9CN#W()6aq;M$=6f(FOu?F zu}-@u&W1S#Mnegsx}bH7e&eUp9ZkzFy(cAsEegpuo6nZo3t3yw;H40!{P9?9$hw)C zS=V*^^Ovums=$TQUj>@Y{)!%T@W|I=63W%)Yb&EuCIxX=LO_-_LvULec80Yy$d({|I#Jd8moaHoFzYoE2sZ&V{SppvOE-jV+h|YXLrHLkt{PlT(4*vDnR@(ElR$F7p;# z2kmqIE6esK`X~l}LkS!h>QH4)^srypY&L!j_VNC^7l4ZmqTTGT`{J(lXkjNx)7n3_Pa31Mpv~VJ>xtZ}QV8e$ zK9hTte_yeJjc^-7Rm3bsIbia_z>E#TvSQ(TmnX^UuE=Bx` z_Lwl1F=+NmSdtapVt53rhD7a%0>2a`eOQhI0MMgrbG>Nqip++q&IADv7PQDd!z*ci!gh@&4T#;(xnag zg+bSqxwG#(KT|oS?fUD`6Fju&F@|nbWFq?Sk}4cB%P0iqv;Ec~CP$U&Jv|SUIAa~A zSs7Y>W2IF#ZSfzLmS3dn#dC){ET4Mh`+u4HUHLQEn9YAtv>=vNwZjai7u37H>TjO3 zqh-`zZD-xM^(~XW*ffcBfX959PxQCQ(|UfFmBGudXFv zgIZ8gu_JlurLcTm8J(;$hXcEDugnpvOD%Q*n38{STNA#&J`2fF$S^T|2GhLh%AIH( zQ4+2i`9>br`L#}21Go;=Ww)1Vo^ENFfqmYawCUbjaBQ$Pkq&ZeE87zhALrbQ+lsh+ zkuf+fY<4L8bmIwxPy+=!k{J;cb&~oG7Hy$lYo||S^PVSl0Z<5VJ13<9X7_Kl2D>)0 zgl{T;LSy>wAY7EuESqO$YPa1c^bCMaK32SiQ23@Qam$o>+MN&9$-} zCEo7<`uaL2sP{Nk>Q&NA0VkL9Mp14zb>@AS0Mpr=&9gPFX{h^?RXFjp^{F_4T=3fB z0jrNiP4WkQsX*$(+u`>XvGb!dA=G`b2cLDNf;w@f)E{;|)b8Ur4}ITp;DnjDo)ZiA z=43}g~PHz?$qncoI-D=T+mIvNtfm9bxBc+C!MS!()88;f<_8A1G011yLWJ(JYK(Oa+t>LHF6!m=lrk?h1#gCfCf2Mk6L%M-qW9a-o zhno|N<7Y6Bs@I|pfb2Wqj8&Spucycn;;R5e`pxkcuA-g)ngh#LtTAgBg)+`hmJ(%N zj-Ok5;{T8a1eTHzHSpE~t6x~w8c6=>@ z)2{+Tpyq0?oUldnZ8t5|%46d!vXO`!{Hy&eW+^%96v5qE#ZQh?7dtbQjp++DaW-(6 zkwr`&vZO%0^bYSb-h0Y1N|SjucGFxtWS=z9SUaZS#(E; z#=5e*o*-w+nwkDlZ1s=|zjeEV9TR;1he-zz8bp1w*$cOA*k9m^Nj$150A!OlZq{bZ zNX(`S^0_Ry+?DA2f+x}Wv-9;|^>~7Us-o^S9DkfxdfE-5kbI<~D&jtCH$-KXDZOzA z^IL2x7y?QGWq2?tq7X`lsGgz{a40*TQqUsXWJco8h#e#9H0R2_Rk;NYY$^zMp2*L447d}mtU+>~ zCigLloYa$ZJZ(Jew6k{b#C-)0Dm%Z)$;z3Up!#_)0T=FpkfRPBke9N`_NoyJ+u+Z; zIId_3zD_({<_{BnT0sf4li0rQuhVpCJ}yUfuXrx|oj9o=k+w^YQ>K+Iz5hV3lAvj} z|NB{YRntOwu=^`56ubZT(4ie4v?zBAOECU&TZj@x-2RF6PV3QxNMOEtn)(tx#rjQv z?@gB*E?Yu*LS^sI#K|hqsL0v%Ba$Fj>hmhI(=H^H-?ve>FjkDBx-Fd{?oT7G$vf=_ zLx)>Sm);!LaPDklyK|J%&VxeLM75>fs-E+zc;;tS`PEUK0+D+l6aqt2+kSUEAI+ z3UhoI^wwz%$;lDk!(AsWt4>)~6!;T)F_nLRW}fx$27NZxM?#IZFg{n;S-_E&T)y_L z;BNrpw`o8o?{qq{^dLaB{LG&C^(~Y~CCf_4RgnIrhB+7eEVRNQs<>DH_#}?%9f<(` zwdkP-U|#u`W}cM>jB6IzDYD!^fCJETxQ3jwrfyo=fc7G)pliu&DzUh022Xynz%hQ&g4iN5c<3zVxA`gz_(7G(n1$#k&O^HmKY z9>fWUMT`q3#sXFw^06RDs)eEADZBF-=31p0qm>%ysUWeU_F-^^SoZv5;-U=2EZTeS zxnVPtr3>cAvExs_`fb7k?I9<3q9s6|4-i_hHl^?dzv}b0iVwX!bPE5uVfrzUB)AEN z-YdsM9PQNY)AVEJ}?|Lk=X)joIi5;?M*>!W;r$XJ(_-=k92KkkkzlHczH+?)yC zUT5xKWNZ{_>W9;B{vrX>^yO=AuAF(eZ2o2i5Y)e73CIZ6q3IKk?pRHFKD^*LyM6Ma zJAL5;YQcou^Y-4C(=^22Y1p=*r<8y#nR+WVYM?bZYT7e@)Mpa&xsH#+*m*826vX9_ zA28(q@&bvA=kkp*rlIFwB!cH?@i){ob6~HBImK=>$F)SXo*Q2dUbkqMbHu}J%Cba+ zS1)H3Jkxngmx5k)uhG3{F8rxvi6Q_Kd4qcB9?H}GWn9`eco>CbIPuyf7<797L=N`a z4_(29+v2rAcA);Jjob|I09(9!#BL)>Pir=Ejh?2-H(XcMY{=+k7)L)s3Zo#O4{76e zJ`i0qP$n;<6tpW6b(g?6%EuhfK$|0W9oML=Bl5}2m>-jW+8k`A>ef5ht-VWMIpY7T(APc~EHAHL zrHj)S!=rk0yuVuDm*~>o^@a^g{78UFc|YvK=Dc_^k`ij9lY$4^dxr>~aJG|t&Fn(`>5?X20Ds&HLIAiKGnsFlv87dB zgSS|=ChU8GLp+wiep7e7MAgqQCeL_q6tJv67ot*cwW?q9*gPLE_;WNkr)g8|I=_Y8 zlFv^LKp%_t+|}m){CJk-eC{?f(!9v7~6#`+uKtxOZRb0 z5+obq70j)@Rxt7JM^7S_PmE*@?%w^nxJig+%cMoH8evlzd3m`QZ^LjdPnC~~CvKc8 z`?KBo9XbU-gK!cPDbI60m6Xl7*P6KUO@UJ%<%VLGnC zthsJaV|!onsbt~Bo^ed9BObKd)t$A@g5JJxr%@=T2qxYgQot|f7E`6z@D2pJat9WcBLW%MLn>+&Z+7L1=ZZTV=5fz0)6u1d$Y zaFka=cGY$3l|92YVIj1gvlC6ne;xA9TZZt>$`Mo%=1?M2ZOTvpKrjA#UXo9+3e*;*kHab*ed_I^2&~rXQjLePNo$i!+POl|h@v z`iAkF0E~9|z}@V^?`DQZhdhf#e`h?$b{~UOg5ye%Jt7she)qOkRbY6Ij{l?YfWkHE(FXF~pqgTgr;&Qu#&M)g%GrObO+xdgDgx zCfRT4j(gR{3K7wC_QyTUpX`nN-t4K3){*@Z#0lMl)^OO7>_`p zwIFj#_IJ5h4Qn~%_V(FUEYx;uD_gHA=Uh(b?l}P|j+&_FjMPC2;3yj?a*!#NE-Vvyh$u z=q&h`dF=6jnkIVnRwo?oXvTL?9h_>u``FtNQJkHf<5un8&ON@FW!e4NpnT<_u>c^ z(jei1Y<97@cqr5sXD9yo3c7YEmLS z>~2LH&AHZlnI`=;eU-tjKvE}Pr51)?PntQinRm%t=eXZb8XeQbbSuHZ4q4@eo*Oj! zFfHiS)$w2H=Y;$Ubnmd5*5$ls%;mmAPDHox8yoqtSs~`>I_mK%Xh0K6%ff;Al_Pp) zGylT}ZFRz5QjGz%*TbT#lBTs+k3*7gcQyx1mJ*r{3*%lm7q34!y3%xGJav(MlQNDu zm>guQDHM{vmEnVP4zAXegp}@IkDX4m$732wbG3F8a%K&3njMzn;Z623f;$}r2fv4p zVotQ1Freh~^o#1Cu2ns!Dp;{REk1~p+?`A%B)<9*3u#1^?ie6pALe6O`B`?ZjLQ2d zZ@CN}+bhJE`An_&Ou3Fy=r7I;#Xce~Y|->7H0h$<^1TlbxOq4k$a9|tIm2`#J@ABf z5uvCWv3a*9RC!UG`LN4xPD2<0;A^hLP#H9H#TBWT^&Fl87sr}T9^gAb}nqnIZ=A@pxW zCNZ`Gdr`lFGufbTeOvLKPgg)k84S2ulYXA0;%k4WJ34EhyOXb(=DU3yRw`hL%d_KaeA3NghK0FB7MiecOOsK5oSuL(O{D0jxA zK`?uYH=wI-Mse1OJvA!)rVu^ z3XTCb8YV}WXk4-86F6d=wVY~T6nM?7bEfx4-l$F}WXK5wc}iwa|7v%BC9HT3G?9;kq*z>W1c$Q!yh9{+(> z7*EfVAn05vkoj5GXBW!Z9SXvziU8!VLVeww5io}I_qfrAO-z-{YqUzQihM8S*vxF9FXg|Uv=YzeWo`V;*F zukJO(yzfV{LGswnsjjsdyN`1oD<>XI8X7<1hrOr1jojL{GNdPeHa_p~e-~3(yFZU1 zKAvF>{+^w^rdt*YT<+kAq3B$&Z==Nh>(bY@Hiz$Va=0Atl?5?5xw)SZ`v?V)A}53&8x)ANAlFw>$EW zmn0{);;COZGpTNP+UJ@#OKO?^XWmozjx8*+2a9s(rw=o$|2+)OxmNCsB^Q1L<$QOb zOEWxrg`gJs1I2=;J}Ui-1A8txG|KGg|y$gx^LXKf~U*jh%gx8yb4-@)opo7>+t?gQa`13c1L z3IfmvC3^WU<7KO4tSAzdCtkoQXQRnaiU>!53wuPXRc<9nw$P@n{u)yhsr|?2-*Oim zVcGwTxDEdxYnaNL?)A&Bb9>@E_+uVoae;j{*5cD_c^*$A44F(p`YrR1N>if~y)Qy_ zsb5^|9hOrvmyg1MXa49sx^tfk?dj<(-D3x8Y{y`e+e`sG`)EMIZB3T0QoqrKD7>n%G9 zmsj*(t5!%E$VLc9WQ*E4hZPg{!8DKr3|5olO*lcVqhKQ)^qWrU)iDIa+XLTcLMrV3kvvZvt^ao(CBweS+_8Ea zrx24@obw+)akLY=eW9VhP(`|L@Rg_YVL#i4vc7UJ-(zpVGW(ai;D!gz68w!8th?Wy zp|fq>PY5jvMq)q3_P>pg+s18gtOb)%xBK3z6RqV(QSnOntTrPZ)z3?Twfo4}_$r6m z*C6_vDN-K6h~t^=J38?vB{v_|u+KIz#gZRh5y^DcEuQxrV4q#oRKX4CEBW&1X$1{! zLde!$#cw&}EkEGzd!YXs$Q=6A>hlLXwB{4JgLbJfYj&gug6_wOF8Su!Tw4fL@U>Q} zqrvLWgkF-n-5){J_u}&&U~z$G6463F&7;e?ur85x<-h9kYT|i$yE)EJjCo`cIcsqq zYNBBoj&A=p;U3F-3t*_Ar~XJX+@jqgOOmoVf7qni@ujiAL`$#QJY%P*c;DH!pXZRb zk=z@xse3awPo8QB@r}d+oueM3@@WU%ig*UGjf_S3`JH>KgY!gjamLgMakG;j`2&CW z){uW##`b2}Ae)`?!FB%CdAo~afLRt5Z)`btL6}gcd0cEv(r&E<;}Gj}1MR&N!`*X< zG)sSJId`A;Sw8b0XAMeV9=Y*71e7wVQ?I`wFBfEP!40G1(?Iu}T%aJiRD7s|5l&5cHR}?>KN{xCi5&0#QKfB#qj-9EVuL!tvmyNJ# z-d*)oDDsXC`XI|UT5)%B9}XnxN*phAyxRqWGA(dmJF&~&V*r~%M|<)w?wdBv%RL-3 z)44;NxSxI`%pWv#O)x)=S0XuvQ2t*Fz``Yq@NfFSu%|pFV^^b4d6$0RiblRA*d(1m zC36Y0V?gq5k#_nrnva6C#IZTzeV-5-9qJ}Qp#T))1x}P$tWd3dK zE&BPOB((wEs5eV3EZpDFAMM+wv&q>PtZz8QNwuXQN|K>$;a7N;NQ7T;--08zk{@dP zuU(xe^JRQudX9BCo^X#`{AmN8FU}&*&Tj>0YUb8G3|F`ows{x}8&0Y5ReDc`@9%%+ zb2Ol{&BGzSdq{~F3uyph zi-+vwvx!@T`TE6^Yeee_l{#{G&KCw&f;uUt^@pkF?dOZ`M*|@u_o=|!;EiJsE6m{H zYj&?Ln^qKC`B4vL4gL~B=MNRGH{#5zB096ej{c6P_m_rI=j1C*cT>aXp?!57WlYX+ z#m!C+#__oTz6WeOy4yp&%nX}V6Zmad!x)U(g~G>!qZKg;6W;6O+lErpcq%I_4sG~= zaC`YbhsQ|zE^bST?+vydQ73&{Um)b~o-7y73I2@W$v4S3IYlv!pDEwC4#Uq_UeG*M zU9~vcy)0?;7Y}ecU#9UnqxUq*e0ZMDRGBc6n9Rrar;t z51ORJ%4L8AKUa5nXH1qXP%i|0%fVN*8q%2YrQQG^mx=p9W`K8rWiPi;iuNj{E&yUy z)8*g3YYyD{NS0h;-;*$|jE3nX)@z+aqS<#C*nnHDgwpDl?-1-VR1-P~(ni}`E|au8p;#_hMPJ5&z!I8Id8 zv^rI854%XO9B0&kif-58l!@1JB2^ zu9{qT_l%(~&~CNX%kZmx0(O6@mFsd*##oNYYa2}oKkIe8G_m)znqc^rU^9C+$KDS1 z3>$rmw4cc3F?#pAvS1~XupysH$l)BgtW$Gcre&dq?Y9ZIL*&ZQFw!JRbf#*_^lnqf z)2%9JsKja23GdOTY4_V$MvLemYgJS^pXR!>KCQ{OJy-S#?lrsUXT)5mZ2w;pPV$QF z6~ULr0TZq0a$N7eV_Xb=a*ptBm>O$G273}g<7&+1D;qs~ztisUiuDP$=(M1+lnUe! z$ZUp}N5LzslvVyEE~Vx#2(Fv_&_0StB(R81!c(!ASZ|FvSvf_;@#T8V+W#^H3wuZd zdD8kbgNzq$)gnQj{Eo*CuM4}f%x7gF3|ci3rOA6|*JF4Sa?RD;OEa&@osbeWI{032 zU9~4tD}zGoZ?m4?_8S_U><$?RPO7!bRWudu>zlLc6)*G}mie4B31y-HE_l|0eM*i| zp#aEZI$yN)T|5wjqtOF4s<*M5WucEFN@m&m+FWu#Dv*N?8r}33wZLg^$IaD7M|Yyj zU~~NO?xmj1=ly!9d%aZ>e0&xsY!^6)ZoibxGdA)q)ZGA5Z&2@eb@+Lz+M&hiEdcNV zny)Hh4T>|%u5U9mou5|b4Jw*G%1c?1G^dF%n|Szyk^mmWFw13u!_RUxBo^7pvViThV+`5GBF!` z=2dd#&^X{M+&2(^$hnRbmu4BW_rPj!-7WpwGfriuj+U%D%`*0v4P04TMDQg^6#B&W zLeDsCEgpj{q~LnH$Z99D1wQE6I+2G(umkvs=5cHBz0-;xBwoh?dGz zoKzsdFG1<{Re>)@V*UBOS_3RKaAr8GivR%?1$VKoQ1vmN`mVcO7ZcDQKDG7fcDQNQ zw%+=RihiWK309K`b#70vTY0)3a~sS0_Ts^V&avCc{A6_`oi^b3 z!uX-QD|cV58}FrY{sM&!`m@itQj1JO8vbyds|?u1{y4%j6KppX2ifqK;#EV6IS)sMH|vp{&undj(GvQKIMV2pq$kcPIU)IrAn5B>XZ`MiWazU4HeR1$k@qMG5Iq2`P^n zDU9ZC{j=VK)LW{y$}5p?-#q8h+UMr@=aa$Yq9Xol7Y2|wu@R`q$KG>&F8V$J z{w3pSSw7A5Q#Do@HbAAw!KPF5L_Yh98(ulL$-GKJP$1=N^c2>EGynl4|Hw{T?F&9@ za~?2fpSQFAO!bh_d&Ibq-o1J^EGq`3&9w~mxkmdwVlNoD>H+|)wMU)R?TjVwP+GVG zH(eB(!=k%(08NBUw#2`Vh~+FdF8VYF0twpjcaB8ARkvN_6Zs_EG#Fc$eCPQ<)rMR8 zB_5ROmpX4R%bP`k&II6UxG;XLDrjA8Rv8BFMu7`Q-G8e&V_$(^-koHDRJMe`E)O)7 z_4GT$I<-lc;#x#r$@c`7t|*scW2HE`<{mqrW59H_7I*9}r_H4E^1w#QiOKnz1ba#% zd}$>uczV#WQFCdmZ;|?gVmy-de~k=Ry_8w5*2HW`@goI)!8>OAsTh*v7#dvYXs9}7 z)YODOTO^snmzKDGj@eUE7g4BS#B;T*E~Sj=nEfWp?O%=(h|Nw)d|B_EfmoKSu1W`d zK`-pE?y|lY7{ds%6&IA6Hd@A9v(4N1B|UBa=VEI`S7aL2AiPVu;n|pTKtNB@* z;&V?)BR{I0(E0sp2iQo4L|U;r_Rh*rfG`47SNobg>EiX%Fk4DGP-Bpqx)c@BFL!lJ?o&Ixm)!DXaRGCDMZ>Uhdi%821VLI=mgWHP7=Z6pd7QI5Z z>imwXMU>!;2G=w5Z9WTpfb72;_M zuxJ&E6KdY%uj#;CZR~OGc~}2t!Jm$j%zv$02A${Fb+x76*djs3#zJ&foDavbkMT=+8i54?<^ClPR;ib*2+OU*d zm5-D4Dvw)Lznnhlw16%S0KoFU7+HIeF8+Ls9SM7X2HFy> zD(jRz-YO_KdhP^~2$1i=0hdvrrJWao*>tM&L?x3aSN8zzP?Ow z&C3vLxo?*UN`*imoXT%0yD^#Qq|iSB+Oxif4ar(v(UDL@a*F|gT1uzTSt(*^XteVg zS}#5rmI1p2nwBYutYe4#vrXFrHfISvgXCFjEeE1Ea&WJeOF<%k`L5~c6Yxrt12@kB z_fqpA?D1{~yU<&hP+zr-6hDVC<8dYzm-P(<>)*@}aQUP2hK;Y-QmjbRG7S)0U?>?nA#bux zMhwpt9S6e8kFxEPaGOo*LVluci!DY3*(TWdIA=Abh-W5U4u3&h#I}I1@`Qei%76E- zI`f!*J-di6uy~gI)Gt1!*;1YRlAaOeDj~Rsw;#hJd+&G^5P$h??oRDURO0cq=y9$y zoPs0-Dw4#Owfa|j8tz*a5@+tca9^sAg)fS+OMG`3LXHjVANu}vNfL!zEfOwt;J5n8e$r611)Fx*b0OSX^3vz;q ztS1iq!6r7)jP3c|1lSPM-kH4ZqpW{;msJw;9%eb0DT|7{@A)D~7w| zG&9#NPq`WgF5j=))S$yy-pi9UQUA!~Gn2^uIVFWUCAFSNmm2MR4d4iH6m}rxoZ|*H zoH92tHU^`lX3>7WUl_*r%W>DA?x=1D-s)2ps@1~5AuqAz?R^;YjBR}wEj9%QU$Oj& z*Vg={Y3U2^9FT(7MO{+}PI98AYIe*0OI6+*Z1AY@oE=jj>dKA-{1=-~NwMz6LW7ny ztk95vM$3uptcqf)RDx`{t$I)*o9+?t-PJa&qUMfazSFTAEU&$J=X4vasZt(P^LMD;ZoqwQOulP^IP^rFTqoR(N|I8@h#nLsugz2n8*v6Zao)>+O0z8xt(?rnr2PYjj|UGN)f0QU24ue`$Jn{eOkTQ*B=@Bv_JaxR5}=O>*K`@4Th2KTG0sAQW*p zik?N67>c$+aI=d$E{j@<1ozs49FO6SJ?GHoB%{as(>JGqREPDHdP!^x)*+$8OlbY9f= zwzk)8ro&HqFJZ3(N^ry>?ldyh=)Pu!4r9o;Hxl`tk_9J|PSEAOZQ=vR$+RxWXvA za}*TVo5Pg69CLDTs~y@-DHF^WK24{l7goJV6`*+^R_#6p*mu0>BfbF68*<&3C!A zf_qYnx;6`_A*4dJzK$>>e2?@c;uA%%=kq-8;e-b``=3UwJA))Hym!w+&^Msaw(xbIu0b6bV($ax4q#Qo1QA7SYwJ(i`)JV&@wZ1J;N`|HkOQj+S zmFlGXp>(UuNPU+_toHmrFW?9A#!aL~Xm^^eolQVdKWr6McB~s+!FAhX#+QC5P5qks z=B!97$sp^Wp{RW!1Km96)kUDNf@Q!YU|EnK zEN`BE{_nK^je``PVKbWbiFRo3rpxWKLZqs_x z5CF7}I2eM@oZ?b(+{3|Two*`mjKhp`s1nr+dCBtn9l|>LD>i@LT01y+qo?TpB;y4| z?(#iu(lw_HqFW%4oT#uc<=c%VaU`xYS>WJyOvOfgo!`yhF3g#i-!M%#%bqFIdk=)3 zAWfemGeqzsMsS}x6`XXhsE>Qorys}XQ97U#AtF1M{~HCg(qa+AiIY@|c2kA?0qLA{ zz_;V|1yW7FWJK6nT9@8Y;wMNCW0dlRB?9~%f zGVzS#VAz{>pQAfIA|u5b2w}SltQ69VxXI#GyqGUwr2BCT9?rCRNPSXw1q<5zYymvb z*!`=q9xu0*lDn0)&~xI)HnoJbwXtNok^GbR&H zy?vK(hRBk?>~YdmSV@AtYlevTSuL8f1m{?KMVGeZWI_*f56BI5>y6KyH`v{A%G@wQ zH8s}@rO#HnMn(maq!DGf2tm|Z65^on)~9G91|O6HXEaWn!IXhh@$=4tIly5#xb`pJ z+U~sKJYkG@*jmtbW9f#ms;^lWNzQf6F~(w|`#zQbJK8lJu#0j!E#7GjgfS@H3~j7t z+KGSxR-$gO86ykej8GQJM0NpdGCvG z7@4DaY6!lRcwkvoUtNZkX-WvGW`ir88h}zveAb@nDI``cWE7ki4rXMN6Lo!dIMx zbq8QY!LA9Eh~HxXO?N;l2Ud2xUk!`c>GD@B*>v-Gn(vmS_%${>EoTy;QrD+PIgCv> z4^?g3<^H=1!Jl1tbDiga+b<=|flzifdK3+JgOcs%5!q3l&4vzd6~M#-?*fJHm@Aih zF5*&9%Wzi)fO6-zsXqDhWuX!~P=YYbT`C8vrmF_6Bpf_`_NRvq3y%HV=fHXc{$@bt zP#os9b+C!bSzOcB{9P62m?ncf0!KrSCSqR4!}@OR+@z+P$l+$iUo=xvRA&|+dOZAo zNKF%KQ%jV+K#a(litt`3Q@AfGS4>2YV4CLC02L)p< z3I~$`Yj!;_KZ=WuWKorLZ}=xAd^2^9F#%dyu9phe#-s(@e&khc|Nb%ZWgXvy3^j%D z5o}fcxv!5*%@C$2k2n(S%BsS7PqA_g*AuNL@>Rsj;geQSh01%?MJjF}PP%87_|Ye1 zeJr?Oz99<}+~OgHNcOH?#$v#e=iWlz)gksfj37FX2J!5udg`JrF&G%X9@!XK=k2`? zn)Yzy5qqP5&smtpptY>Ej_>YvnqKi2p|*%N*yws1g)^An(ubKb)sbYk;o_X z`^M4n^nm6_OVWG{vvM0LuZ%6*g<6b8-=j&PJ0G(peFl~z{riO7NM3ip5NEvhvqcKi z91X>8mYGG5p8y7oiI*?_=dw^O0Zul3EA4TPrZ80>KyL?+qGj&I1N#>(GOPeshCu4q z+JC%!1`cr}1uX~M+XqMA>dt>X073X3nxgYZi3of7Y-?VFKgX0#_^n5JKhT61FLL(? zyde7f=>_^Z3(T^Md_chgNNsDtz{Sc*b{P|*&pexePJ_1W$GVSPk*69DI+g!o%13}_ zZ9H?h3z8-WRxyW>)=@jQ(k5EA1o4QJn;c?ovmw5wWk{scP}-M`B8&p~-`lIynLdjREkFR0 zk$db3uv%UMF4(-Pde-#1qj}aD9>%OO9~4Pt%}{~%U~QCdm2r};O!sLkdhlXJi_vnW z72D-y8~aeKnV%TF`WeC)KED!T$%oX^@R3SRVq5n%Jh-{t}hZu8MlTzY~;T^n|L7RAB9umk*Y*$Vzn4T(g_C~ChzW>et$dU;t5Tj zn9}kRt5NNYW_ii;$8kGL$VbZje-@yqDR);tDCHW;{(6s2%v#ukRR#I{xqCZHi#vVn zNvu^cN_F^AAVhwC96 z`uEWlJRa@B+`BDUBXIciDdKN8wTRer7CeadmG$R@cJXNG+fL5Jm*+e$vmX=JJ>2ir z&>1X1uh9eAAF?3AGhd%`Bf86_FafWs_~p!1PN8>_Q3ek!Q^%T<-9w^m!G|(n+fv;W z{c&CKY3*unFL#}+!bDJW*?3LWgLJQ@pb=Zg@#)4D&>_z_a&cBhu_{VnrZN_3i?;vh zU4HJK!d=2DGSF0KxftlILP4cuJUQ_ zYWidw5iQ{pZTx=J4nj-bP&#W~?9fOvPF4*96)w6r_oLD4XmV>H1^E zjC!zuvsE_hg0<@RXn{WW1;_U&|ASq;tBO?Kvwu%DZa>IfBm)+=rl;~IP84Tp>)?D0 z1cAAL6FmhV$O@Dg*a-gq?k>Ggg$KybSm!4Irtgh0jMwp>HJh1jJe2^D-pTTb+dfV7ey8gDgvOviXgmv>8D+*n>9_&EC6*aC`lId%9 z*S(!1&P4N`I?50w-nEVLRV~-Jt%yrU#Zi)m-I0{~IR4r-j#kIEK#hKw^i{s< zX6hKb_rl`uJjLGr+%Brwajho^9`hb?D{`-hAac%|V-(lCrHXiEkN;la6eb-bN43y( zD2-EY8BOF0qY#hC@-KA5jr?uoIYqo2p&TAHd1W4#SSpI9$#{xhJ$HTY@PQ6Vm@x43 z(d9qvU@9hRKa#yDMmJh{zUxRwekAgNUy4PSF*r^+PS{cX@sNVo%kpmP(A>UnZQSA6{gcJ9)0rdZF>c}w4Q zcKvr^P)jt}KsWX5Ex!~4h+AEa@qA@p{Q>0o;fnm49Ir)T=ps@p{ZWkKI>8QXYbwOv zbE6rw3#$%^+pf=6f7y9beM)k|z0EhRgS^q)OL7CR66Ba(ynntlesiVLYI}rgFN5z) z_3+8>Y~{h}__Ut*N|}bWxxR2-;WZVvYlqT>E8mu|;$7ihDR=oFQT}c|MY>Li%e@^; zqE>ib0I(+A7!NnHX12^gv6GejJ4SkaeEMt+>Mi9U z2_{Kf;dWX_HW-2-J)M!h!??E7q-I?^!g!|mmv4U`kV!q}{u{gZ?B3JY9kGMJJI_!x ze{y>fSdXG+0_MbX9mX|&&Z;*$*Up{ATof=c1+e-pa`LO!6k-NjHscT3XIs9I)u8<+ z`(vH&k$cJ>Ti}B?NJ1qO5j)Y3;27xU#7Yt@LEW*cd zmT`3+4-emh>q-_y6Au5?)vaW_Jv)BYyGZT@E^+{3ffE7xAbltJW&9}k^QGJH&-OH$ z-%Imk${*>a#{aO48jLLltL3JH&{4AqpuX%8bW?dD$}V|R;@(+e!;;~f<6F+i$*-~l zxWqa}2ulY@2XGFsh=mJswSumrCqzgOxCYOCRly#-vS9XFJ%2I!_xI>mBkt-7Ycca+ z-VbQ}84m1!j7)X%#IQ`>1t+ja-D_S+{D(%g%s@c&489RG(Z@lc&);k=*ploft%;2= zIOpQy!UBC0T}GTz?ZH|mrY7qqmsv4WF~{5tYNLClM^x=ax{Mq@D6TIr?hOij*Cke) zxp?DCtyX;>qXq8oU_tu}{b4n>#t%!wQEu{{FG}Afcx-3CAx_BEr=)6qO<+&!8YPWB>c*%|lC0 zHvtPdQQDPB@VHyUJ9^c3#}lEuJRfU=MI={uDPLS&O`2Fg#!mkxe)dz`hINc) z>a%x5c~kNopgo#5r|QI5D+h`A=4J|R80Z98-0Rre~>QuqFd zQ>&cfD57=vu={q#%!(0M$6jqQ(N&|z!C&(IvD^~@FLO<(XM4Oq#Ir7F9)J71F+4}S z6@3i*8>B}678F*9J14T_Ec@i$Nf}R(;ncYW4UR_B*$X@8Z(nEAN%xanfPH-{zww9o z6kml2SWrE+zU_NL77-@Jw5jdzs2!8Aw9c+uGlk|e#Ygz7DIPbM1;|fx&TvR9F9A0& zzAe=XTrr|4{i!9GTU^|n19hb?(wJQ{)6d9zNCZEGf)a`BPF!;(fd}rWBjoLZs;R-& zYnM)vV{X$ntV>s@*4+*9@;%r7_{p^&#TEe0oC1&Vu7*cpfzr zdeNi=tZO7m{eXSt0C?&ryKrE z$|0j*mj6XX5Em8om%VaukeAr@R%CFb$HtuWs9=z$aY0kihQHLWr*l5HpE|mruVL+Yug zjP2QeF~DUQ879Nl(9TJ_er#XHqBj4>WGTb=Dst_gB?)_}d8O;bFwKDS8`sBs zVzNGK+gIxhn52r46omX=?dSd1Ex*etfUp*#5*WM%xE`4!}-Dj z;A_K}4E9RqNmB{1MI%h6mC8ZVEvquodfS?oUhVs2tmp(f8?CS77ziB?XJYhh3l$M) zAGq(Y+iVU|BG-&xr^mVLzyI*cvDXCmXz|N?cjbOCGqh2Tozo2VM7)*k63nv*)I7}@ zLbP_3?*Acw6v&^WYWKl9o<|soLgHMq7_{$V9}J<+7}y=)titAURaRf^`0knTB^&k zE($Ojn2XiTXnG&WGPV3(XS?n^%^DAaycxt+CuUN6J_p)RZ<7bfP%up5PXE;1|lT<1DvU{x_4)>g^bLp5R z0zw=EdsofA0i>vnAmVHU=$L>URYSmpBtbUA0cOC={zK~HAXRPHrw>W3#G4va{{})w zE>xQX{x}EFg6_SzgE|}SbjL}wrZg>P=t{c+4P2w!gL>1&*;hm?{Or(vM$@BoPT4t^ zWho>Gd{!9z5zh>}Wr(&m%`zi$Eg-4oW@+M&4vr?{R;`jmJqZ@U8M9;vyM#F7<&*1Z zAt=#$kK`*`GD?dZKT1n48dC!b_gmVuo27ibpN+GIyReWwQ0(EdgQsocY@~m*tQDafOrJ?oPPpr%!xP?4-CHgY6Q-LP5&Sojy)!g?qC<2MLn++ z;Zo%)@Oa89zBU){`6%71GfQ7UoJnw1uSGUHA>Wrj^r=hh%t2sg6nq6Ce+f!R^2Zg( zA|;Z_4p5D~>u1Bb&#Dw*_nQ!C*F({imb}?`eGx&x(J^z#%p^J)t=u(J|0iIl>2|tZ zZ$Azi*dg;`GE86gDvEPIKEC96q~(S8po?gbf7Hq`UiO}gDcn<(SlmU!U;BOQTS9KW zo?~CvvGZ7^Hrr-!;Y!D;r)Wh7B3M3t*dbfUu87yJa3BNNcnL;DH_}mQ^^HdI<~n zK0MbrhnK|tU>a?9)ytw+u>!|$1Q~s#cqk=4M0vb=crkMGcL5qa?Tkk9^xk88#gsjK zzx}#oyj0*nO<(JzD@xA6nNV4b!rQSLn2!`qzx8%XNS>#7jFv#3=&KYwEI4+Tb>KzJ z(zH2jAb;bp#!fhd`v+i4=A+spA?eD*T0;Nm8n4 zdk#~s1&^=Y&4#iOuaKF&aS6pDU*8L` z`eIbRcWD$)P}n|it7d6QSrqo7b%D(TPLhHblm`^?j_^kiFn80isSIKjHePYdA8Yao zEPmw5OYR3prKE>ZLP?=CszTq)}ZI)UXA3H z#`n;a=z;&5ER`RFoLf!iN)<`ywP=kW+17_ded-Kq`i)ZC_pv_I`_<&# z8sA_|HN~>hs1D|(nCRYEa^AUP?&sxo6qjCL5X=#8t0TulhrZ>|o@~?g@9vXkW;P8) zxG~{IreCoZ+g9Zf&l+W$8^KldM>qz*_xIW~pktGpzjv71K(k!l<}97}wI(lr#bY#x zd)eY@^Y>C_cKCgU+rLpfeYFA0$iLAd{^flY=q|+dQIJgHtZ0br3zqB9qaUbn=))(8 zi2Fxz9<>*C+sR9Tha2;JkplCA26nFIY=;H*Fub9Kg)mGH{kx((_;!9W)Gp2< zhOF*&`komvx+_Kf>j<~G>&t%KbnU^38xej6)h=kCSi?D)e+k!8lRhA=hFL-ynAe$} zsBq_0nVjvPkIu*vjin2i(%+Nk%47gJ@NdUp%^SCGJl6{@A{$NfE6JAz89ONWNHm(quslrh-oyO z|F4?5&vxU9oMtS8wz>?ySdmDfXIPJVc+jTBd3_1R|6k+%H~*`rJUc!AcYl@KKHsEbi0sUZlBnsSfr!jS` zBgf13q)Rar6}s>{oqGDgCkPD>6%RA{vi>!a4HDfOe4D$fWwR zG#akV$$s05o4GH+dK?Ma_jQ{Tpho#8oL>E8Uyplfv17$2$cb9o_N5dH7vo;IhmSh8 zpF=#*gcN+yG$-~<>GDN^q{gCGQFaf|kZFQFnw!Vu z+By3X?D7uXxQlvbASjQ>N~8Kf2;NVJ6af|3Ya%GjBkB4b(SN;VCpSh}2fTj& zmWNx3XpTQ0p0qv7^t*AJW)_c(BCv}X=IYoZE19&$9YMrXrT#1CnD~9KfV1lOwOvrB zxu2WxD2jd_`xws|LSSTR&DSwX%C&0NBW#=AoZ)2HH!uPoaq*<5@^1fes9j)sR~-6E z@Hg(Pad>wN424+o-sjbo@WimVU85d%+3%%`r#nJREQSr5vY+aL8Xbt8AE}O0Xu99& zd%Xhgy&!JdZ12UF%QGqh9eqD&Cjy`FqAyckfYOQsXq&1G(^<2Z>KE0H^!a%wLA zfT$KOIDVyeitMni9kxUYy3nFh&!(H>DFrbybyj5r3W@-1Sc=BQqP?=ixg^`d1u4bUM=^(~+Hcay8yAEEI2K#S9h`o6n+TD(lYq!d#be>{i1>`OPeziJVYePL%x_2}rc9vm3P@$)iTBHy^#8+me_NDg2U zn+~XC+xKmH$wCSzvLT^tY2nNeR%z6DKJ@ zpPY-~jV3KiXv2baU#>#n`=&qF$RhBh(p{sM(X9o6u|_NfEBa8!Le|Ens1J3br9F!9 zh~Vqfvysu5cgXz7J>ujGqR%>cEE&TR@0we2=c5P;G;ckl=dyIcSmXN?qo6xhlP`z*8r;A(^3t5|{exy2T`CcY(F-`= z%`~-c+7ABu^cZ$IAoFDZ`7aR&47j1tP@>Fv719x$G-^!b)4qCj2%oNEwewV~bXVF- zhZtECJ8+>)0)gEHz5=IqDqzH3)Y4rYTWRUEdnx{|Qsd-X93Y`s)LplBf@kyqn&@*} z+g+^IxrUVPIqL^Bt0@G;4uam;=Y)ne7jI@14K|t`HfGSR_g9ZPMK6PU%MOJnSr0k| z=M882Dl6S>wJP7Btug&a`ueeyFHqY#BZ0dPs} zF?IBHOhl)-*PzlP#DR!RsZVah%LFy{1tT?p%Rd)sI6Yn*t%o5}hM%ORe+}qwP&9t3 z5%Z@X-Tx$zfVLrNCZ)u=voga<>W|^@#$>*odR5-iR{*nwc$r@Bt*J5YS&{abWB%8u zaBM|f11-w7C3RPSh;DRxLz-cY{EvvR2z98-&Bbkuy=-LVe5uO=>5(8D>nepH`3G6t zd0*kINBN_*R6WWw*E}ojD&A1cHR7JV{L=Q3ee{*ozoCUR^z#W6Ntr@v6YlxxpGt~? znCuI6G*06PhCuq#ey(*;TVC7nukO-S`a0WqpXp;jHob-^KXJV|sG%qc4o+wvv-`Lw zb9>c8Pws=4C9@aFt-hjK*@Hoh$CO@$r$R_{%U zZ_0N~6E4(qW_W?D9rzOghG4<97*+xs_m@U>j;sKGK?y7&eLXp&f_B#^Jmx=;G|+Hp zB}&238i}CLJsZK07|ibT%?vcT>U^Ci5?6&tBdD`6m~bb^ZfY?!4TK|)OpW%5@h?)) zGM0*p(31&ax+ z!L8)u+QG7z8t2*LqXMY`gb@kupSS%kQ>{V4^&33@Pl6s2x|tdwMVR}e%N`o|m$=Au z^F8H!!q5Dj9-$%O-5}->esrat!0*>d?doG45ngfV&*&d$kV@nKUKT6C^CFNp|AncB z&MD|h3%K=u7{h~AQzd!GJx>TI?!q`fT5??GjnhkSNa+tvebVS+nYE(U3m^s(tH@}6 zhCTPs`u4A19JY9{?1zHFj2MGE{F2_MmYl8d$)6?7p);+t@@1z6_IG+Y!#Wn$`t>29 zAlBg0Nx-HEA*x|gDeJyTo-J9 zEyzt1zBf^P_I??+TM2Nf*<0AoR$;0Mt=BO;Qwu0f2ByKZ98rx{>fvio8~(%5w*+b% zv8Fg`HB)g14sl-s+1;aNmG1Eb=>S~Dkw+A+ zb%My~9>ru@|M@k3zf?GJnC;DR&rbI(_VQjT_uPF*ftdS$qz2XZzJWH{T93{F@+Mj} z)o9roo&Y!2THM@w?aD~_t0Mlku>Q}gMYQ57nR1esqg>cvV#Qte1`0=78~+)mB$5ML z&WF{D*fI1o)JEPd;{9^n&pztfSt`JKtQpd`DwL0X77GxO)4OBC<;1w9=)pV1p0 zj1L)$j;JhKA8bYZV{~c9`G>{RxL@K=vQMCTuM5BAS^Q(pa4=&wO=y}^@$=|XQtHp| zS@Y-_ z66!Ii-_bCmtV89{6EGT4NLY$52l34x8axg@@_jLq+7=A|F)5t+X*N?yyrjj5oII1* zDC=WNNM4M`tK<2I*B=>T{P{W5L`2w&+y<#kickL5n!26&VwUvo(d}N#`{AaWNg>%``(ACItVR% zx&L9!+o?fpDR9SzGFWcV#H@VO2kkOwqNqbMvJyn>j~59xO=I@;2ZIlwJgfG>9bF}# zMAz&O>&gvT?>*NqojZ%#`~|cj?tf<+erFdbS(C5giRRRu|7&Nci$kk!vii||e616? z2HDj6Twu*QiOB-&CoZnt>oNciy6(LU?arUmZ&6c~7z=Xn0R5|ZTsb}UzdkTZbf2t( zPv%p34e(%7Jyx1pkDk6m22+~G*LsD&u2Q8T#S!jrIG5y0lNrlaZW&6`fxc2@8`**zFFuIp6LK>%KU#B2%9;@ zZZe|h!<8$?5qMU1I2#KGf*VdpB9fobQj;*i$! zu?B2JhUVmMLyW!i5#CoGZ>ljMOS%+OQc<}>AEe+g-1M<2G+|Yg)u(Pov%@5nVt=># zEe*M*?Q6GySy-xEZA3i&6lLtdCz_QtHQg_(|VF z;qlvk;i1F@MUG-VQ8Fv=XONR+#qEVj`?l}*l;qfuLVmB0xmNz;aI~Cc@_VqdgZ1x}K0m0C+I$5q^UaVP?n< z_gw!{6(8!$>-GnL zba0%E>#13}_O(ap)*bLT(X)sUU+Hmn)4u{_U!R1szdd>mCWDYoJ};k3QK; z!}4JI*ZUokc1?7aZwsZ(1%_J)tBhPFfJ{HcybpK_R3&_sLgsFYs^R zp~k%CwDf&8%yS6~Kq-HzExx&gf%@^uUz}!-nZ{Qon=^)|J%lCq*bI_;7512qSB#scXyA`jnW{UN=kRb zhzUrJkdDyN1zR2|DAm!&dv?b*sNPG3P9Z6wq+%zEU z-bEcyxwP5PHoDF+T>kQfH?wYq-3;G77ChPiL~Krq7^UGM?%>y~4CPwVYy6WGQ0?aJ zr;E7HF+fzJ!Km)CVQT7yf#!}_)|jtTqEifJ_3>Vn4ScpLHb(u@#ZClTYNTN;c2MY5 zv)|A5E4qauRa+h*JW8Oax(Wgopuu&3^wFO*i!WNhsk-ElzYHvYg=Dd!KM<+*M%0LR zh96vomOVbLb~@q+eXfY>0pD%ovkg*EN^`bK3d;LAie3@qH~@4-uGi=nk+vh8=~a56 z(;e5=6R^`&s{Z?A>)boPE)sfOTxOC;FA>UAu-XjB3JS(9IFJHPy}<4dhNGo=8VBjd z!JB_utB^4~muujFUvYu()1yo5C5XAEmYt*6zwFL|!93*6mkMP3tc-v`*O#aIodt2|qfu;s-6(zNg^!AwG z&98fE&7_{r!{Li_jeU6>73!1}%sDpX&y()ij z?L%_y_jmmEy5|ZAB80u+x$WHa`)1P9oZr)YIBzx>u-n{#@EvvB86RfpX^v|Ls0emX zrAxz&LAa&~4IAXna43kqJKogk?UAp}QtumRVtp00?NxHniSeMaHFQw0i`U3B;@eu_ z2Vd$5Z*A-{x9y3tU9mM^b-M6Z>xnxf zTy{DYrz+qhWSH3asFEt+Qb~Qkc*ZI5@iK6WKV}0zgi{*b3?-;kJ%-G2$?E5m6OU-o zL7!a~lAB`9=@m!Llhofj4Ft+>|0&H8zZHrN(6h?;p;)DMZo$V6ErmYCop03&e6zj- zA-pDMpz`jAd#lPQnk!oOwd0@XC+gJND%Z4;%KhRYEJt|BBsd&t1Pz31s7rKENj;&< zUpQS6jon{WFhfoa$w4iJj>MH1-LG6rk8&jJ<@1Z{ zNe<>&8FNwS;?VNGz>{<+&Ewpcr8Z-zbavc#@}M|d44DXboC3F%fk(yx<)_J2({+e7 zT2+A9^z3r@(D^esk`)8E!)m7^&AmU_t~Ps@jG(;#ukpQ@lfAdPD*>H4tg3><)U>B; z1h1xY=f`hcQgA4$&TRIPhq5a{Pj3Dz4UbMEfb;`g9Kh=U@qa8`A@Mp&=DExtnyYSc ztc-R%$FY@1nu>6K#w8I#Jwc^MmbTpKx)h4glRiGy&%jIQ*r*q4 zFs>=b1*XUT6f+Bv90j);AZHy$Ej_m zFS`qXoFvRWhO6$EJ%Iu)S;WxH%PO55wtGIkzixM#eeDmU43W()-lmq(&WRI_?+8YE z&aqJGKd9tVd}?9p>69qKWC$ceSjKPy|Gu&-14x5wF^*luV=?xTSrLRM`W-*SFQYYeGPUlbPe}d?}G2HJX=x+if#=F z_{Kj-uGjL#we$by7KT^|Dt{uJ`JNvCpZ)8j9F1em+wbRw5eN*q!bF~g%l4Cc7k-MI zaD2`%kSiCF^;R1kYjt4s?^X+HVgy~?*3VGsag-03H@=H-Ugrm22_J<&g#trW^Bfus z>0*|Tw%kT{q*2LVO>eMfoY6AYdzx32Zy)YgNS)}kk`nf$Bqg{e-Q^AVis=4}ZLA-8 zO3_a^d1|_5UA6ig9Z$_5)j@-g?&Q4`uAp==dNXj+!o<+6iMeXJ&4jX5;hyr-yatmQTM}Hl>F$; zVovh}GU~rgr+gMPAx4oMe8XX0^q&2zglEYs)NnGDl@$%NcfWkq21j!l30GpFGMjU6Bysgt%UPWEYY(g56z@T7k%*&8u$`U zCj!@U1%qls&rC3}Y##3y)Bj#dPmvSzz~ys~-k6NgzwKsQ;y1Z?o8~qcf)e%a$Nc1T z#VH$2gj;C}40bBmXlpzoTHpy&AG&0;ivIR$4hG+U zgkFQE$g@ya5#4QQXi#w5Jtf8p=bT;TE%+Ie<3}^#pLzxdY;NsSpmUywA)a3Jdvro} zp?(s$xh$&?@VqkV?7+u!mqDQh>d;x?$9mn<@101dwf{y}YiLj?sViwDG&RZO~K zpGyB|C%kk!bZoKyRL(D5|%f?d_ar{zks-X zcBVA54D(b2%UDKw_n*jld8V42Z$8^)n7|rktFmaqf7bWnW5a2v$L^xxVo2wH^-p2f zHPwK1)_N)Y2IT=YjP`8uOu$(Cpa&wHMUHBAJ2L?4xj##zcX7s^lR^`5LRGX8AoL4Rb{ z`SS(rp>+83C`;>-9zbb zBcF;$2Rp$Qr5iugsHB84Hwg)5#=LzVj$X%U>7Tvxjb|;Bje{EHaq5|VR@sx)`}u09 z)z%kO7B?Ml$Qvdou259UD~oC-D}Y5#tb7}yDSF&Bc~YC}8Lh2%;X6OwWWW}br{uYR zU(!aQZ+jjT1?5dgxGC>^8&#Bo8_bJ6aPK?{HC?tgO;LBXHVrUmX7n2ZOw^c`VX0f( z!P%T}_g|ijX;X)s*55r>T$~5h_K5ZTM_RjBa%tSNl#wt2()%<$nZ&4zE5r9s!3H&l zDaIgKLqxuDMckYhYnD4d*FhJPW{eSg5MXY0E&yI_y9uYhE*_!t2U|Zv9v+ z(P`T=zUwBoe#J?D1LYkhJoam}mC!BgwyQ_=}Qa4ldI>s@b&VzHC42-xB-LU!c8D>#V%yXJE6MFqsc%ZQq&{ zx)=oLCR_;@)ISjt&X9eYWA)W!6;{a7%IWAvXrOMR2D9lBy7N3R>*XF&I>$A?6BCHg zIR=n4M7eYWTi){1>Roz4{>frG$K0pTq%c6Q_8LP$hZ3BmYg-UkB#Ydde* zhk;W~uttyO$gQDS@un0Z19y3Y^IPeNjUK8}UTEZU8=(m{RgGq1Eh|kp#Wsh~FFTEd z)flhX^KOb7RHim8UUY_@=lSPrTNnwFB#CY}r@4{-WL=`q))~YSOl^F+i?FM$GbIBo#y-C5+q86?b9m5z& zgq=8{>UfPr{U1EY^2+JaZWKGmN;+1_OWE`Gpk0Uk7A~jJ80{|&>>O8lnqHs|n=jh| zE{gSc6PFWZiwuQK8RF3SH_`t{NU;Mzo_WD-b;@U$!u%Y!iURI zI-Te!TfiGYM}Hc<(0 zqGtwg1s-{cvZBjO6Tmc-f##VL-I`z-x`I=Gi?meWB@=aMTHtTA6Nz@JFp&eps>n|| zNy?F!4V(z&z#l@{AC}KER;89G4XT0U3L^!r4CIq5#2e2jyfVnP&#vqjm1W&vVLddp zo|xhfo-$9YWq2viLl)Zl1g=}amxOSmhMVK)o&ub!MBs;%iF!W4+OMe+vhm&IjFKaA z_q$uIol7{IOE6JTOK#HCm-#F3K(diS=4ujECbN=5FKzVh4QgB}H)&_9Jjbf=Z$9gN zCf~%ggVsIM`bdEZIu2xQU%s-8?iKUnboAe=!!t?AT#N5Z|GlJnoq4f|KYBJcyfBqT zg+^|B*~%zgB<||>`H3E9l=t25tkExZ>(8%c3AhrjT?l`6cBpSvYm}q@N9+#a_l%dx z;Kug^Kbi#+92;^fWnf*_Qt!?2@Vr|(dahbMjE!WwrI8l|(o|!1xNy&6zH_7L_q?7dT6rkcpnDRSaf($mZv1cPyEH zprj5!JZ5)vH%k_9b6VD@2RpV!T(6L~X~WU%L?jExk0E@fUBJ(f}36qW89CUnBK`585Fo$-~}36H+z)v$RFegWAz z-mLDX%kBVtIcOP_dzTI?v&GFi+u8`N)zPJ1j&g>W3FQ(13{B8JZF~9Lp@DoIotlKh zjAiwErT6IoOFBe8+VLytb$$-<{A`Nl{CVe*M1D5lEA&CC!7h$#Ji_!0-(y}Q?w)`( zqd9+uJWD33l?#IhOhf`$TP|9>_n^KD60054; zuKuX-7Lhcp8O7{uk;9K>4Ew>?-3i`kvi#1ou&Yda1hkB$cvEyw0mPCwWc{bu=Ra|v z97E|7T2{|b$vf0{>CVj<3^!iB7FlFYmCW%z^LV8C6E)KH##KqObuA>fg$#@TUs#VPI zb^EoNS+SWZ`*rok{CRm#Ex$?ly&-r4j@|P}L}F6;Y}nH+9JPjqtQLh>uRy^$JtRZq zgK%vhyz`t;waj4=31bpMG7Od&@i-PZ)~)>M^h~WnD8)4gM$C=X30ofo^K6>7T-(c@ z;u=ceO?lvMKT+ycH?K3M2{)P9=@%@=T$?8~{6NlkXu9?OWe^?#U3tAArCy<+2@Bzb zsisc{&HmVo_+MbKXM%kmOVrPd^HS(C5o9a@GrqD7D)PNS?<3_>8%V6=LAjJb3PF&h z@gzm-nO=N)n{6oPw39Uo8m3I`2hO=#{fsa#;bwkXgQQJi+}daQv(>UQ^s(0V6RUmB zgpK?P_mh*lrP>FFo;>z*zl+Ewgn#mY7v}&LI_B}36`aqb=6hj>u||T%TMj?R(ZGrb9PDa7DBa~f7QJ=W`U2&@I|78Wj0h;;g?pgt zE#b!a#vACjjRB zU>3HDIB)&XkfGy`t01G=+DRQvQZd}V23GwY%wX@wKAK~eb>#4oVXz%xKyyc-5Y0(C zRglf?Mc>7a!V&p)q%r_-Ln`O`$y98S46U0+#>+_?7q_;zy1Vl4JWitTh@dZ>S? z_m)`3L=rs`b0GSeEz(0nfl*@w=i9Yd$7_UYAK{j!B+qVn{b}|cX;9B^!`%^I zE>rS;5ALZI<-nIM`YZXh8_5nsspi|wNlIix<9&XP6rQ)6LH?C-B)i{+_NbqGw4#-* zOYSyL90Jb4o9@rqk{1RmpUJkav2LZVP8&RA0vhKmnrXI!KsUcG&obA4(AJ)Tc~Dno zmNr6&51Iz`CR@Jb!Yyl}E~3c#R=>*z)5i>n31#pMvzjYZkM*?BXKPq&HD#newsQU} zb;H@xgw4x#@$!qX1Q@Z#dW$B;2jKD>F{$Pozv{n^y@gcO1*&9@)lU1GqSQ`d);x({ z5PpT+#wfXk^W@Op@I}3Oe-Zzh!JBlKQ_;N5>gXF4Jtm^MhM~6lUv!VAZ;w0uk}@OL zC;G?Pke}eaxR6D%0huv4YE~XK=>>l;nq*jz_7iV1y$Yd^BDt*zJ{D;t`SdcG(bmg2 zidST$>hyG=;qQ#pwi$zlc9EChHCcttCqk~S|IQw(-89^L<_svy6xsHr3vf2qyVQ(s zjy57xn9`14*O=#&jC5F4;f;3xoj0d4qEE8DRM^Ohi5)^PhkvN(nvaMTYb)myCEN6i z#<&*}zf}@TW_&dm_B#w;p~>58!W6gg&2h~Y!QoS5!Z}<)EpVuFfYOpHIOy(=>`o?a z%MQ?w^-AM>YXh&cYP(#lo|_bKr7a9OeH|RiUXqdI3`ALadRXJghvy$qvSG@y;&Uxq z4A)2zhQ*lWkvw?#S(M{m1p@i4Vm@5frFC=d%Qy_IjbTH8rH2+@@`t~sk-7TlMuV=c za!#%MSiDAe{=#@1x}UX186+<4md4Z0tqVY`+M;?rJ$KoG{$|N!W_=2(maEO7GPID> zqr3Fr=ZECA-5P0~Mn`|#wDtpxPw#E<>D1Lz!4vm8>7^&&o~3i-MzV&DrF_@2k*9Z}0b~_?e(uNKax#m^p8%_iEzBTetj_yyn#KS3iZ*HI!nAdgL*pxyNzKJfntq z)fH-Y<4`h4QDn1U!Qe@7fyiBjXLVsclsG$ZUWoo+Mw%iism8I2OM&% zW6t-OerFap1-TBarI%OY5w?3pqi+y${F!^yK5dlVXyS*k)<6B`1>?iC<>1up0zkSL z1ypp2iI&%ApQoRm34g`wxrI_O{|Nga+wC%FhD}*Du|+!4_>_wyH8BwX{y*dmDi7m4 zhcj?54Ws~v1i(aJ#L+PUf$+X0>lnTlhHm<2j0$AYe^EM$Zw5A+L+!!^Kc0BHyWc7A z3|eEHY>hlS>P+%UD-^szIgI_pTM(opai&2!_@m(OgH&L-#aw@?sO@tIQC*C67OZ*G*M zA_DFLH}-D>2Eby(MgiHsGgZW>;&{hUL{Q;F`=Z=VCvV2q{q^8&YZ*%i`1AB@Th!M` z-}AtQ+7AV)rx*6bN9ExG#9iEfJ{Pm|?1)L3_ zF#pRrHVIAa!IVrHCG`#9S>cv1cnbj`%7){K(b&=_biNLWe@oFF^=-tuXH)eF$$>=n zK;&5IuQo#`dllFhk!_7B{qg9u<+9p{^?E7y))gRz6;HoZZ(EEMFbURwN`JPB8SDs0 zh4Zuml|{*d1goq~pr_I_A#tUuhP5HCb07+E(`P`EE!g?X#DEa%IXv0-f+@BhdXEDb zigmhW)z0z}SMXZ!?xRO+N|=$DwX|l++y(tRb{y#N*r0*IOydENqVSy!B-z~(1*+tF z@t&ImR^7?>4%gR#kdQ1@vygRqJ>XU}ca{F(mljcn9%0s~4M9o-D4hX9XEW!L zKz%$Pz^7>86Z1(Msmq(+5MXe?X~$|YuWZwq3%62O2)YMlecGG#Z^5zCYdrnh`;mO*iA2R zmk(sub)^#KBBw=s{Llw~x259JwnS64WDKx9PqR=YrGNYu74u7Z4#SnaPP0`#4O1>$ zxkfa-X;f{bf;6ffl~=xsJ57d+&PP&3LPW<5=n^F@hF;tKP}6zZYEe{eSU8d~S-p0bp9goe>L&iwmo5vW%F_kb; z=6AyvOrOs&hxGQdjVRuzs1T_mHy?T1mA=|%zieHgkspqTRST)?$sguuWec3>PBZSv{)T88JQVZ$C`0Rj714C5FX}H+EvXjQ{f*?^I0*jR z8~u?tco%7zfn1*{{YQPZC<>8f46ki97l$3*`b#&-mttsi|NXEPhte+|-si?aMe6%h z3sl~}SH>;xU`3e0g`CbYqie7>CKrLFstlrm6D<>2Wff{sVBOSpy#4M%_w3zVOl>rC zZ4B-BCE5gxj|PfG#V@r!ed|kN8wTbsq>{u|tpg#<)E$G>H>3E;Y!DOU8Am99yVw;0 zAda4{i%>^vx?1P#Q4jd(#Wc+A^DBZQ}9I5V+F+m||sXs{ta( z!Uln=^b8NM#YCh6ZP|8?bUOi`dPQ)a)`eI_n&l0EKLwkCe2!`1fY` z4FN544)ud(Y{NZSWW(prGSw#`>RFV7N0!@+cS2P~ZaM>{O znlIW&+6iMDQfQU2wUjKXkcX$i7UYfLmd9@A4dl12Lg^+76(>QNr{d}Y{kNq>uHSFR zM~yn0yt%c+cjF=~^FQl91~2+&m@cPC_8btuay&wSSG%%AI5_S@k6NnGMr7%yUh@b| zI(O#akjBe%#l8x+K|voReLz)7A`gA($#{wq`Wpl51!K5Bnq0a;%g?=vnz1gClWxRe zqp9O3jPRQQgL;dhrTg45c3qpfJCnT5Ym-F1GlN%WkKJ#XIc?|E+*brknvG=1k$g71 zaO)wpuNEH8cVjnkdMz$-qazFU1Jkx6rjQV|+F?varts%no(VS2^INYtGtjN-$chw;;X(01C2FzC zDs@4+gfSMGLg^ILaWIv|J}>DyK7z>Wk#883aeYI9esP)IeQFN%>u}E>^lS3A*yWi& z(A&s&F?=PtCf=3d=uH1K2ToYqiYscSw53I^Kpw`^Af@@;A?0g?HAB0|np+EXq^;ME#ultYO9XMQ?899>JKhg*}pe*;xJW^2!hRUUPz>DfrHdXz&}l!x1X=Fp{ zKPcli>u?6r3%iiBPtp4JLS$VgL6Eu{R9agzWT}z$6Zn~zYY?;^vcLmpXZ4O=6 zeqlXM*X@GK_)wl!-U8~Px3AfEee05jaA=OUJ*)Otd|#310;7cgwnIzt92P=~=qTS> z$Wj|-XEfSXb9;D3v*z!zt6X(mWh-!;C?VYuH2mliK858hSgE8kSsBAal=xTt<7V_b z2T+Uo^5GP#4`jm!VzErCKkKP~FIHpYd3BV$>JDqj-^eYyOwS^GdQ%Nzm8=HYm9*v6 zinc`!>etxd36CcbrO!)JgI4H3oRt)Ej>kD%Ymr>YCPHtCd4W_7&I2(uf16SZhR~Pc z-l4W8vrb3Jf&L%uRjt`=2G?_orYWP#_(>`U03EdM)3T{RwSrMWaj=LMi?@7hA3jHE zDJYWG(Fr@!^*yNin>ftZA3t4xegX{TchQhbex=_*RIlNuHfJmjhvWEr@nh= z0$BS?R*bihyyG~~MkHnG?#4xed)Z=h?@08+_(jFnlxnu_hbZ!W_^{*w&Hjm{ zZb-VKGCV-m+uQqGEo1Q4LteWeq;zQ7{dxM{m=axWnhLzD2zi*97S*kp?jGO=!o?pb zEMc6xJxPh*yGks7()BzW8Rl_->LFVTh|>wEBfIxx2x|vuD{L%Vh*|zQhj*IP{xa zKw=Heh)uEFvZd}6$K9?7r68se3)iui@M@NR624!p-RK3-Y_LL^A!D7xJ5_`mGZY zMWxo#B&hS6&FG-h8Kqmj1y#^$8KsmWzC9zckrd^Nf9}hE)ahOm>zEX3S^&@3iS%TAaI*H9+91vkTY=_iTta) z>B|hcXUS^BkH4lvHh=5y3(n)p)-~3Y`zy=d2F~Xj$|KHGdi)X76Ufhg&x31lUuS8X z?dxs(eE*>ywPoXf#9--yX2G-GG~yzb~L2 zUgw-(P6Q&5f*gioh*)vmW5&%I+>RR>}sjhL=PR6a+^ zm3|YY1pcF@vjWuli8>;pu3walSBbRR}ON4;e*4Xr22TJ827az&U zoj)7Y*X)i}WY1ap%o+ z5|9N*k#ph-oeyPy;XscMPy z1?o&)Avr2(csF$G);BO#=RHb;m}Em0nbCOqm{;WWpG$McKleUEfsI@DB0P&rjLW;d zww?aR6^Zo_^xB z$^y(F(yhb!fp)&#gLOJxnqAl(Sh`j4DJbpixgB;_u^&9!Au7U>2Q9fYu_w5W4^w}N zmK)5PcoxU0Lo8YGS)vak2^Qv}Wr(Y=of!WEH)Pn`Qmh-faCB8^D=SFC z>Bl%ySM^Ymd>aHqM>na6%w?3cR=xS_y|o~uu1^$Sa+c~nB^H#G{e;k2vFsuYODe;g zBq#i&{5=%YUWI>aPu!!eqerLg1c=(eg_V=Ge{}fXi#ohoRL9j#_kot_?La%0+L$Uf zn96@cd1#t2>3fuCxLn664-UH0#~)0^9A5l))72=VdApy zlh!+lVoxyMjohmV=%(Oc$|!_w53|%@j#xGoH4_Tpp{Ec>zctp+^)W8PzaDk&BuKOdReXQ_gcMmu^WsC>)%sFt_`rW#ewhS#4XdPvZJn$%Ng zU~Owi4ES@WZTu$Xmyo_XytwhaS2PisoPX52-+^&%h{fi4g4OQF^g0O+Kgv(Dt$Nm| z!FHPeM$aH4x1`I}5qFjo_b+eMn{Z7gh7MMh?VbwXKTgG23mptLdNVV@F^ErxY=*9D zOx&6X+u9rL&)}{`_O8@z*$>~1_`PB z0T-R=s)twK`w>O!LucsnV>($ZNx{+DRyX2%CGbZj4S;mpx>5n_{(NTKaITSg&T!G> zH2h^byt#?CD@^^wNGtx~|b ztt2%q*!wb!uKPK+y}T`-r##gLF1*5BTMyY8g)+7_GMmzdTSL1l?3_k(wxTNihI3Y> z=3}kD8kdCaKFD7eST^lm{bS}zF!o&E&<@*-jx#HsR$tG~G0F*6vlyToZH zvry;pU z)0W&~2&8O6FDApmH*L`QSC(1xE@>&yH_wtD&jixMj2f=TiS~{5E}1W)9%0$Ux*^0O zpX=+6$6!A^YpEqAS|=6GwFdBv@NBT&q7i-e`Z$sLCy2A{NZ?x;9<|*}^{!G@jD0Rt z?}O%zvBOto#1QXXT?W=}{ekj4(|*C__isE#ZFMr~g;rPaTgLnM64@2%Wdh9^FN(^0 zVVQOB=x%Cne^d4j)I&k~b@B4^TyxZR#=NWAgulWjzig^|m-f}uYFxk7e(hvLnFJ$P zvLp;R!&FMVZ>n1qLgeR_Ca+~koNR-?{$V-xN}(Mc(#s0;;pXvVQ$K@T1ehe0{rzMg znltBK9;D35l|7W7M@z?8T;h3Kf78u`@vTSs@-zF89wl=w;}n=`H|556Tue5o9G-^p z0#@GY2}|-E3C$RY6As;uzWmy6pXlPJ*fQ7s@*T&oU|8XEsCm$@a)5|VeAwSIWS?r% zRYG>(;;qrO(3JP&)S~)2T{$gDO<;RGbyt#gtvTPE-X(}6p7cYmCp@}4tbcOQW=nyP zLUR)7mtoh@&aCMgU3yiEG_8kDRADbR!HwOvaN&UGT})-uXkYZTGq;s`K@yq30N8lI zaMiRCESG5}SiD^l zPHj1+|N9W$jdsIjw^ltQuv)c+EKexcq`G$IT};oO>OS4C`v=!?A(zg-k7pQ-XNaEY z!_qk`#eiMFLz}0(X@e!v^=9o3GjTkD=bEU?$YiT)YxGe{&i?8s)LL8fAVyc0=Vlm6 z)|G4OKK|O0o!!#6XW7lodEtE@t#7?vQR*)8NFq){vW#2=C z2mydiTbH?OBIg+Xbx-%ZN0(o8CsJx`cF+Jjt5>U$f+?SxylI_DxR6#gm*<3*Gw+CR*L8@rEP_;mtj--y8jh;imB8EMlct7KK?hB(?0fqr!BO z|F-j3KCr;&B`zqKIMNc5_q?~wG8T_l)wfiPoaO_(FJ}$WZgJq}Q{P$-i<}}9b zF*K#v!5;0^=8|9QQ!g4W%xIY6F(DN7U~iW4Q|hE~7trzP`8)t^?nRFz<~AA*dZ zavzND^gG}-!6jSmNkyr13$le?&TA~^9Tdt4jRFbX=n|x*bw{e2UvP)Q_<_Wu%1oeA zF!yW*s!&7QoRT-OK0;?N)`*7`_~)~dyj>n8m`5^STd>1@ zhjx9Z)v3xaORe7$ue) zTDj8ZfDan2DCLiub(-jWWE@=#n=_P501W9Jr@q1(u?|wpusT3p*1h&$M;`ebP#X>} zuUe>)fI)7w;Qjz1qWE2P?Fln8EaEJ zLs$7Ru5*+*(TP?#`;T$b=E$pW0F(PX z2U6Ie?Txw(n-&Wl*yq7IF&Fzhf_h*A;6{5^^{=LlBLFt^FXoTRD);T~DVvV6ma?5W z1iu{pHlo!fA*n$Dor)&M;L)9TItu*&%mbk9Cq!NQnY#Jt4kcT@4t=;!|0K0#mPlTG z`*f)sba}@pDgDC2XtXfBJM!FxJ{Z|o@x)8)wCvKf7Urp*b5C2wWgl9OeoLJ)uEi8AhV{Un8wxo2nE zJN!qoPmQ|EthsSMOkcc?%yx#Jv?&|h&9_VnPcEIVw)|+a3<9lRedp4J!}&Vvf9dOK z`tS7l+J$50wVosT4Cy#We;>jP6PpQb`)g38J8njVBYf#b-h>OXqHU;yznYVi^QO#b z@lhm}bTJd*FX%ttB4weejXOZjS6&%k>7wr92_WMz>S3W}ddH|r-XyQm5K~J$;ZONQ^&<>VXDA5)B-`dwGlpza9|0+oAf7 zFI=NO#Cm3R!G1wPEci4M%8-d|za_EBbH^68pIp@J@AH(edY|6CYPQVSf%_<{E&Jd- zxo1y&UYu&>Li*%IGV+@2;jF1VM5_DWCk$frhIAF5IVEF3e4#~;ZdJv~;p4<9b)-1V zT~5Ghz;!4gsQ|TsY-UiBCPmWgh`r#}iuRSU)JL<|&%b()8y)jf84Qc@&vSDd_XF*- zQYEovsY=4W>gCH-Tu&BAWX)G!Ai{IKh97-=SLx|UeW}yUldz$a)AYs{Dce$oN_X=A zg42r9Jh7uNI*yikcbk=E*!)dk;gW6J0VcEzl+=4`Grq=mU48AolGTlZFpXouXO>6i zBVd-fq0XH@X94QH=mWu{5c>Q4hb!sr@d2?P*7O*47J1}ckv2X1zoZG!rx456T7zcY z2taOXclYkCp9HrWp~o2eTbaPhIjOmTiyhKU^P;_0$4?F09E+Y z*|)jF++-dQzPbm7()#fF3PeDC@X-Ah4H8HX3pIAZFiJq3w zpj}*4dwCWGbnGv%P2I0bDMc=z0`mE%M_Q?Ox{{;G$(rXydgl&78b46Ok~z*C_K9v5 zW_4a@Uk3%Z>V0v9c0NXZ(r&B=&f+i`a6tSeY3#vn;b3#uvg-mZi1UCV-kH~+F`MhO zcQyc(Qs*)^qtdYoL~T4e?xg4(_Cxml%Uu*!I1XF-W$yAo3@BPYZccD*Gj+@VK%(n< zOm!UC4~A!zKj#z8^K^7{8dfc~_(K+Y%7jU<;T~`&v5xet8ofCok!M61YuV>D;w{#F z;kITrstbp;+vC8P2G`Y)GYmGwEngf1jA!qZX)uOo+4|xgMSE2isg-# z``7qdsz9DC?pb(PeOcyYTYT?8q~KtG8Ce`pEB}uLrYw!1ZfM#xh_b4ZW9@Mc|tg4j;T_=(e;6?FctuDaCVf;P{PHHm|FT)uI!cA(2$!NIQJRy8ylvNu|2SkhD3Iy#MWp7Mh*IP0xeo@A_f$(uPjeNsLye~AB0`MuXL?;wj|%}ot$>RH|({73J_@{?G||L3pGq}5DGx&&h{$wPjYzmx)G>WbBHg?yf&;K8yku;Pz23(drRp)7SV_Q zM}0B^S4a`x_tg2PT_fV2^6h(ei7N}s{PB)HL0)e$F{@xoYy3)QGO{H&D7Vt-1JAGe z-usPa<9neLGL{}o44wKBa~Asn$IW7RnCf{b;Sj!AZt5kyhvf3}B6{F++(!_PcSed! zIbArXAX6UecJDRJg28*rTbTE4w_tVGJike1gw?WeYB{k(3C7AWA6w@)>%^VYUh(pw zrht~7xxvVHV?BQx`7lsrJ!%y(8HLN?a>o)89)^3ESna3v$zE5}893Ih{$lxDUMd_t zC3HEgMs>s9I!hYUF-JDnKk@62RM7t`XGHPmL5N(?XfZ!7T@BeaJf%Qv8`k+dRZ7z? ztObfpQJl9aN&Za<2Hsbolb4?uM4pU%Fy;`AI_Wryc3urziv-I*uZ5<&dD!5`It)fv zQrR?8oDfp!%yMS7^60K`W^^rf+kTm4MPK%T{UrGByt%x7&3pSYcGX{6&&1oX6Kr;p zaJUpz#p5;F*hO8hUV8!pRxb*gC7IcyUj04yp@iN8EbBC1eJ>^ zc~Zcd*VhuFtCdCR)t1`wYq6SfDpyf;#&3-k(!I~@|9E-BX!QS z?Rm98&#&*!;^IrMv$EyZm5KQ(TfCYBW>=y5*x!jdp0KO2@EC800w)DmCv}~r}HoC5!LV%ee=|jY)#_hH5NEzPOLe>UhQB?XQ}xH(wL}j z@}!0pt;@XnIOO%^F~F9NVvK~|{_B9tyv>(|Nrh*-5{M<0h#Bp7@I$`KSmFWgm6z4R z$G$I2f93vpz-#Fa8N1g%{rj0?yGdEiaM6B~@cUBZ(DLm5Crmxy=?C4-lYZ&(eRy*q z{A3Qyv_I(JcqfSE2{z(Tgx80t&K0hO!!NJfKV-s;jG#jhFuk4aR>4tiY;bbHd?ll9 zt&dTUIAK~N`yu7}t5_6<>*Ku-b?->N23s1xMElB&lJ)|Xv+t4Zs;o^)w?mqV;jWzd z-5dJ2vn=KR&e#S8FBM7itLkC3-ApP9;7#U<{YIGQe&=Df#!~q?PYqsW(8SlT9@0Nt z$z%lC6%q@q9L!K((#?4XB+BNIPW<;C>&+vItcMC8ux~ZV54y>E&Cp=UDV~b)2P%G$ z`H%XKgznmVB?5stvs7u8qKe*WPlMOLO&#H%*y3$u<>791Ckw6bSIbH)D{w2o>UWmB%q4p)gIUyN&zcq_W|HI- zrz*I!aB0Dum52ED;q;{3?o^&qufCDdQGcVVsyu#27m6_Vm8IdJG}-+)1Gw}>{;xfZ|7;XnAMKiQKT{s^ z6<2bYQDLAjan$|1`^Wf~R14h!^AdloI-E|B6*EWpkOtn7zG^OCBBVrN!d(k)yiKWG zjM4u;rmiwB%KzE!(%nc(H%NCbNP~1qD@aIpvrBg)AW{<2-60Etq;%&Z-QBg9-#_lX z^Zt2rJ~PkM`JOSUFWIsGhG^8T!20{iWq9YoOL*{7mEyC5@VvPr)Q*B`spbuAmHKXT zo4B(6JKL9ECFc21HwBJ(uaUH$V&~(B^X>F6Pb#gy%?Y2_m<+#6I!#4lH4Uhn28}Y! z_p2J$zpvDmS^e?&{m=StgZ|rfmzFWAc#eL#(eXCB;K-{J4%~=0FGrHZuWiqNoi3{JpRVtr)zu{Hnzk?Iae|CoBJD^Tf??=)Mg5^71UdBT(-fBCd z=%02k#IU=U$e#MFnfche{H+<2dh7-BS&;dYW6SZ`6Mdc zDz-5tjaRg6d$wD4UVhLps)Z)h*(-$9{KohL(GC#moU4uJGIjIxm%kT1h%DZ2^Wz3F zG8dmcB2Zt@D=i1@nw=b%rhd1rG{A)Dk;lC>j4B%(jV*D@iY8@X_mBk+^PTU(L+oTJ z5{*bI)BUHNz7w0Rm4EDnsKd`R@9P&Lyn6e15Xp8n#Ly&aZF^cA^PTxj+Y`MAl@hoG zJuT)7Di9C%X6FhMmX?=vm`WS{AM?K9Zci z--bAvT`DWif7uknNZ7cEOZB2YcgXtF<5D5a3%TMw{<9l(EPtHjGN9xB>eY&ICJO?c zOM+Xl1KLF$hQ&AOMdly1DQ%IzEXhu^If-^m2$j72J+p+?AH|ECRr$VWA4;rb*xOGE zWlcw=*SDPjlpsRJ>{ZvtKFO0AI%=IzsVwBU2$qj;N&9>rJQj zd7#IM6~(!OgMpdyY`dU3^0NG5f8SNcyIG=fOwa4d8(G?oo1j~<&`aud!_1(rp%{cB zZOOaSouyNfeLB1wNBuX2)w*d1`hK%sBZ)bYF(B0*_66cDu?`$Z0QNcoB;qX|QcFv9 zbBel(6MCXD{GI&e6v%DyCK#AJ7Q2z{fjDqyUB3Q zRTBGx;R{24s5eEi+z2_*@SV@#E_=|PC0eX``b^KxKFxTy72@4b{L>E;|B`tvLEZOm`rR( z;x-PdXq4ljp)b|Yy4^URIgTM(E(vY0S6&(|7v9SoZf8@w;6CGhu7*-^JI=X=HFlrZ zOEkL|@4Um)c*}`v(ocvpmNcZu+kVF}Z;mwQIrh}MGlVhiwP{%qzLDAI#=lqIAG)?< zFL_69=_t7(K+r^m{O$};@-@DkSmRY&eJ2wgwJvjeZXm-bK`>kb{FOQxqS7ot| z`%FH3;N8GC$1NYbJ=8iI{sT)#?!gE8{`u=C@gLtUq%Qwbb!54HfBjLi=B23ME-dW) z2rZQ{{UhE$d~o{tK47>0&z2YAeQ+!K6I~NMUMQEOe_vZLAXE0r*Y#Lofe{TjPnd;@Q3D!xD;&sjhf~pXq3Ii&N9% zWy{9znP_aWlC>m>YSYfOixImr@)xSbboY7H&wji9{)2SVa&NHr<_lY6jad8x-}2@-SgpXC3fX!1AP_25yT7Kk=3Jxwl~fCcE{7fl@iX)hU-5! zcH=v)%<3|$ZDxY9VQVupzM!(eIw+6MUUd$T*kN80 zdKvhUBJ&ZWth_y#So~lTGZ+8$qT|fTr9KVU(e!-lTE&X^54Zp9V=8_3`C7$-FOD*@m1}}8+bh>pTSU}v zoI==hrcQ56YoC~TpdWyOq%-bq56G&dqW!lRo{*m{EC=(rxBDnNJ+CIY-*wR8nM`cos1#XCwAR}~)8dB-2nuizoEy1dJo{fhS8 zf;#em?i%t(zm}7T)}dhQ-XLA9O(5O2dv%-@Z}~6a8K_PesPbM6Gj@Q)@`)JT+Zg8V z*Z|?nJKO!T5+Y+py$((x@) z#lp&P?Fw?$at?=i%Pxoqc2C`{kgBUB~&^pQ=%o4|mZR9_u`O^dq(wHF{hJ#*v z>S2L=AAW^8Ng2PY62y?-ICHm1Z82FDdTpquw%1VfX-##9w-V}SVYryHiVB-l%i-uQ z{>76(xc5!{*k#A_*PVzovjf34Rh-u6aVx`v8jCPJ*qvY&_lI9|VaK6o#r8b$E;vrG z^?0e@&DAdbT(-DQF3)c~;Ub?sVw1UyNnh^|zTrKOAVbn{CRk>1de=>#>)9WYV|055xaU1KJ%A;>}i#^`Nk zw~AUj<>iA?+un!Kv*zN$FUC7Rxzb2>8$Y)ahrO(gj|DQ^O;vBRkfXkBBC5y6sn=}U zWLty%an%ZR!?b>#kWdLcjKJFO;^6Dx5;Y7W0fB7qa^@7a&zag@z$6*@(j?l6>m98) zB>9`|sv_lpGP6hYQ6<-N6jcgwGr!bAS&K2&B0o;B^{mM3%SNhKwy$qTmLk7F&5a$4 zc#$3DF@`L5`|0)Fjj3XQ@T#}ELtd@N%Bg5HC>z7L&*#*x{&xU&yqXzNofIwsqZpQ9Uy{lCyN0m#yeJN zIa9FY8$-rIXGKnrvs*88TR%1s``pv4^5Cl}*G?LM1`GfM3I9kr(d5S4VF_@Bg`yL7 zZg{WHyO-6dGImG12b5n%WZW|*A&+L^_Ky4b`;EC=G%tXk6%1}}&Kz;E8;Hk!*)Tn+ zQk^L+7mR$eShlx?6t;y!!Pu}vD33OG<_vO^C^OU4`IdbO^womEYOkn00&nqm)Sx?q8FR;hMYG$Zcz(@GJ ztVM(J`OAh2NpmE>c@eEa9+KS~;*X49D!An}hzCpD*Mm-iGGaq7{(A9WC{r&Y&z~kw zFrPO~jB;Cb0@Ep2elp?PWJ!3UGonxMiAHl_G59QqopbH9S>UVfXPC}^%$F@yF&%(r zh_exzJ}W*qq9eX@8lsnB-Vr&EZ0r_SV((HkXE-}LLBvLJfMz20tDjv^PjUuA1gh_& z&&rw!5-pVF+)%=_!X3GkgHfzN&+T^1VvrY1?YNb(`(-P|9H2=)1N{L}P{sk0pqE2x z-A?LCISJB!pW~97F3D3b%!#^3rjcTTENj=F^uOGLJy-M^!v6)pf z68{b&K=I{C=T_v}Pix+N7TyB+O&`YJt(biXyb<#T7y7`#&i;?>l+Z^%gC|Ke1RTkh zkC}PWbJ!(A8^L6PhTT{F3Z7=8HlAf}ahKjY<)>v{=%+*9vwpaAZ>dEkwDIM|VHid9 zi#qdC3k5|Oupc~>L>>|)Qyqk=SYnPl5%@;k`TnYwDOo{P*b!E z7I{ysNbGn;cVhP5E3fOhbN=b3KSny!U}heNpt-Ak1hj01S8xvV0MLGHGST{){Gj7y zjnJl}B!N=f6}d4?sA}ggDZN#_l}sea?$K72Q=ZG3>X1^HyjcDU4t-4D&kyBPG|x0A z!ss2vr@v>%@9IVSoV@@orlW=5N=_DE^GjZeAH9z74C(O1v zT5FVY>eQDEmVa|i;=URp;y=I;fKTxYi#%kHW8maBAKgtXXb*YXircQ zQ@@s(>B%Eu#h1iK5tk#l)R~HZywMgNYCY&v)xm&Tw4!X&_%M^%^cJdpzrn6Wzw?qj zyBQmhDpO(aHB1E1=uSWO=d7Qt*4a@zM;bvi>rB4j!P$(fi+&R2PF6i*lRq8Mbqu`= z3jP6QhNBcCyr4GIJV+}6jg zH@rA)J8n`!qF!$#`_-0f9#)ik+&I8HD}vp?=8U>f8KEeuZJHhzCwNSk*o)}hlz$_# zeVfkF(4X2PqHOp)SqpJUi^*PWc{4Ui(Yp%I%k zfBW#&Ae=$nWq1kHxAoNkCNbUg0Tsd`vd^2Q%$Pjt7(mCN$Uh^;z(A@t!Vo)}PxfOV zA>pglCTTqui^(@5tSmlkvbUZNYL6;hGHrec3^0L^^bzGkuPLRddmDI}-t7-uq6ti5 zf0B{+Zyd-I4NIL(FS)ZO18du{Dol4|3GDd2j?0UkhI8(-c zY?crnlm_%2IW)Y&{5t;WHs+_Zzfy#{Yt6d3FDQFauQ*GtyOpY3JHwo_uq_{6htm=6 zGC174d$kFY&gkKC5>v}K9S-5)`&*q9DvCWdQ_t<;ic-Nc-K%Gej0x<(2nG~sSPh=1 zK!?_6oa!H@;Au}!@K6VwhM|Ie(7p%ekgqXg;iqi_Fnr*ena}Vo3H6^P=bw#|hgv*t zEY%{S8FE$DVFo`AtmoXKZkKdE?;hkDfW3;?kvmoP_u1QhrGAV0tCr`!v{pAF!I7Ky zZZh$lfIwL$(p{q6;CzBk05iktZ$F8kVX+VJ9BFt3M)#&Vuc$%H7jllb(~1;^kibpzgb(6VmGe_C7IIW%?N}QsiGyHLJpoZ%qLBD)aCKD@7BFP1=!4I zJ=*z4o|4hOL#TvJG!PuBBmeG*iuR&~FG4>2)`a7DUXT6lIG#G>zxu1q8YlRJ`MC@O z7Iup&1Yw)HFYc8}qWao^;6_-6PRQhMorLuWjodvoBhgM24w`<(6D8c_eE%e9K{Z}Zul)it=I{Y1}^lJDq3Et_6$heQjOG;-_Z9&u5 z(edcdNARe5?^joY8g4c7{_=!hf_S4`XLo_w_-P-iQ2$JeD|J`hb!bsdaQp>H61d;&7e@t9fd-2^z?0T!yl=$LVxm!I1bUaqeoDD`z>NYL$7f(VLS-UZcBmz_;kfgm{} zhK4@lE6qv+y8i49Ja&LDJH+(c>k8!2KB*UmTXJLXgX^1sy`1E2Noc3+DOb`HW_WbL z86f|g>>hbgDT5&Bax49S_+x6)I1B0rqprw-Ik)fVnt(*YF`FvKaHdDe#n+KK%!{;G z*HjJWy031XWFNp<11j5R_!ii?+i(#pDn_MI5#xRHyRB6(X)4aN%F~*b0)+9qUkbT+ z2Y%6!hsB5~<2m@px@Ez0~gOrO$T4ZGocy0QoOihvTa4lH9F+`qrggA+2ww zC=~ygnZPoNZ@u*DFykXaxU`)crgpxd0EhI$Z=|+b9#C&z*%Nb48h#IDf8jL-*e?WH z*@SVI4Tb1>Z@9d%nGh8kQMNA!4S_x4OtS3tFtp)NTcG8KAUb-oQSYpo|2Yc+sx`Y8fwW>pxif z3nf+GbXf4W!7m7<+&DjVOB&tC2SHT$pTE)outgWwnPmV@<3sBmbcZd-^3e6|v1e>5 z@MCVh-wia6Lfwd!?P#KMp9APVQ|E6x--%;6r!(=eHcL-d4=Vc22D?-TmS zP7D8n5uCRl{p0??pcB<3N%!M05`w04Q6;Ib;Xw za6CS^IEV$8&`}%4eDF$#LEta6%Fk%7#A+GTl zX4}-EV!-|c_+uH~X{5PTE#fo2d1^gSxk)!}9((Vbp1AXcwx5C}aB5}w+{cK3H=of19Nvo zIs*SL+R!!Fy(lxI6m?`=kiO|%hPVjy@58#!*;=1cA1Snh%6Jy@RNwT%A{imC;=pK+ zBq%JT%uj6P&`I;h`te6~2JnA^`^7W0Pd$tg{ol8&p<;>p#8$AUR$no})4@SFsAFxT^>=}mfr7bje3)U>5y~an*_xhk8l9S@Q;AxDk zOyY$=lh`u)n&$qo0HZwI&7o6{)y383s36zPcaEox|duaJK$3=bc|hO0Kk`z zsg`noXTKx~FjsyqK!#N920S}((SIfkNvR^Sx~uvaj=Hq^gNc0N()fAGqeT*%bQfh5 z&AlD4S<9i7s!FdlDW&|ZVs$^3bpC>$M0u+Go}aIoIP)fyWk5K@)KCwPlA%`$1))LG z5YQ_g=@f7O`){w##u`pTryMJ6%jt*Q1(!^i=k*+CB3xPZxraH57~~Gz6?mSQHj9FT zWncJh4bPi9fqJ*^(Fk5L&41(B3xDJg%E_CrO@vW+$nzQgVV6AdJ)%1h;fSW%P$UXH zPlKgH?~mkTz^e!9dN3SXQ28s`adaNkm+fc}$lc#0d5C4+#SW8F57Nh2@MNT?4!QTUFGj(v%O4Ha$ACRIMF1S zAS_>FLWD`?Uzz!zgu?yTI!fhWnAzYa9s_^*G&98;$r=gxBwdRa*;^qshlQp%=0_ESMD8h z%x4?QlE>Z^z06_@WXH)9a#dKI8LJ7OANxZQ%_1n`(dd=8;fzF;NLuMp8S(-R8A51x zLP(f^k<#?2!oU9Nb7F%)ji`KY1K};rk7POLQ;sBn{L(w&ws_~DN3SXb3*osIk({^7 z7vTxmH$>VAZrL2|f#x%(NUVubS}!WjU#uy@6tU~y!1EK{AvgS8|GAW}g9a#JexkM;7>xaY zF)D<7Lkjh4nEVK=Kv(;PS@j$Rhdf9IDv%+GWt!i_OwE+t!HF{Ry=D*$c0-lrH#+-L~GuO20Sqic?e%xB1f z0uL!&!;Tp5d1gZ`WFnL4ZqaiXTCb&M^{vBlj}QQKM2dSKqcVzjbY1y0eha&`Hiq9w z+%@YVVDttapxTB??gdjwH!FOwKjh#m9aJE}qAfj*>4e)#pSu;E?1;V%d^f1W01p@) z%A;Xr`z^Q|uYyeS4wSzTfvA1Airb6qM!DtJoN#*oa2otMtiFsIC$-37aqYtCu5fbyv9#v(s%h(htS{k;)W=A` z^hh{qkQ99!*>Wz-ON}%U+RQkpv1b6R^|$yzYqEm}%TT*7xCh~(P;)?fAEoREX@Wqq z4^XklJ!sUBH}T^$ut1-3gnF$5$usf?Tpsn@OrKO&B@aq&VP(!1TSb=nc!2kaD?D?* z1DjGOea%0X{4hvh{(t#`3$Vg_iN0?l^y7+}5M?tMa%;A~n{7fdx9Cz1qUNusWdhOY zDYNfy;l6(!qOQ4vKdZQ7>IKJ8R0ARqt?YiE{X9`DnWT$3m8|L84L?jpdX4o8@rN=I zv{W-VPu91;q7YF?ITT>A=$U+SC$N*+$jE-G(jt4W;H*`}j1pciXz0h>FAFpyli`Tq zuvv>T9>N#?D%14w#v~n`^W}N46w4=@1AkOdHdP`Gq=n{o;;j1@^8|W#z(?*8HNwE- ze7DSpNspaEF%ciGB9A?Tza8nJZZYAbUKW;KfP$wkxD?96!J~boG+ZZgFi*O_3mi$Q z;aB5)`KIZGjXsd1S$44}{dV}|Lo3z&6GfbssO*D2)9r0S5}1(*{(5k%@wv4RYKab# zxz3e2WUhq&yNdqD56$?Mg}JbyfdUj9Za z&x>?}if5dMBY4=E4F4Hu{8mpW{J!>glP2Mjrt)951bK9*rnJh5ZgU52hrDVkW6fX2 zi<_*++>7*+Xx}~KK{oMcwekZ3FUooxZ)H;?TOX|S;RR#>(L9R*zNw8Yx;=v;%`Zf; zPvW2298&vV>K+R~VE_+?V=68$Nl~yft^6adtd>Se;%>tD)!SWx`&X3`FJNaAtR=qbHi0?}MGVNC-ZFSe6l`1d28urb1fW*s;0VE#i4nqG`8z z$G7Is%HFLg=qM09>Utq?Ej^qj5N^{T$YKKIi;a?mCq2e2fEnNuo6wT0F$xZ{OEpgv z#p?FZL4^PBb_BMTmu`_r;_sIeFc9-dkWwpvFWa)qZWNiCuFwxGwdCV3YuPYqyW#UG zXx~`^+{ykR_#2P1Co)=vt4FyHyiXG|=nTik(x~hG_^92RvE69eE9doR6RYt%HGuQI zNZEjdby&bGW#{S7LK;bCB=2AKFX~sw9SK_$XsDZhvCq_qtUo9l+-egN)V-Fc=zXvK z=c4#gKx^RrP|{+l0y0H%BxmREY0op)J8q?;t%?cO%|q--haD~|KS)I;q?nJQS{Tk) z-(%h$c8D@QwIX+=BzD}Caw*#1&Z=DJJE5?3?DTDLP}=tJyuLo=H$?D=+$`1vi?I8i z+)y`Nl?UfSt3WX)aF#puM?w_Kz}VaLkHAzmbF38NuK(v_45O90+UtaKl~KRDNGf_4F>d zHOSOA_CgRM3*t|TO+Wu6Q4b4T?G>v_s$>S#U1&H?Yx&+?5XL<1^x*Cfkiup@P2Vb1 zQGfUNzT-3Y*9CUw$`~9XsRzsL$&^aCiC?{xLV4n|A?5(2mcGRU5jWpGkZ+pp>}aQo z_Aw?)y$Akp-KKggx0ZY@wa^l9ne&G?j}R&LJz}8@MD~e7UF@X=LteaA`@8r(yHCyw z!NCB0a#MQ)COJtU7*Llcq#g-Ti03#9K$eG#YUzX7hvAVm|bfGhwg4SNwxG&pC=|TjQcZOT=bDk;X~u7Xq4hv z@plna-=Cstlvo$;tNmQl{@&&qWNn@VwR{VeNzGs0;RG)10E+t9ZBJC7#Kkw81O;x` zepN{-W2EeFgNrlzGIr}zqPjWWk1HfSw%nSZ)J9n3J{fin2tszLCreI-2X!8*HT&{B z7`AjSJ~jP>R}!$p!0DK zhtzDg@1F!(ww|E8SoJN@u*cpJQdL)_=Kgl$r5wIW5~S!8og}Z{ZvbCx9@=L?M2~bz zkvz<84E9c)=9mPLDG=OrZzIPvmr@{SkxLcrSQ1=kBe(yp;v{)JK-ijWTn^L=Sfa~PUwsCuwBtnvcZTJ-~mJpA?B)4N3d1rGdI$?qy+)oMx zp{I5_&gW!_a08xJBEDt}#nG)f9Ov(Ro4_~!=WVgSBs|!w%kcFKEZb%J=t zbx32Hi?6$SX}~;OlM-}Ks?(Kg;y5#{>j{Pc1`{2x)=`dM?QZ~6kmOof(ON*F&beTt zgZlHkFWF)j@tf0iHklt-39ph_!G=CQsN_9GIH?&LSm)^m0CC;(DMk?jB>HzoN)3TU zVP?(lTE9r|;PY*X*p=f8P2gRrhz-Ph%aApNe^&CT^4cF%!?d~)KwoX{b5r0l-HeHA ztas`H&rEmOtJIjKWAOj;GjD=U&bu4+$D5cx8$t*qb5*THAV=(P)o}vq%}9!~{XFC_ zbUw_R8$Ccb-3QN`B^wxFBMLPVE>)I+?;I0Ln3zLHk+xdf>|0FEg=F!M=E$69X!7qi zw%k&s;#``u5Vyr^+ykPF(vDj#KX4~IDnE0bNf)GaL6idCM{PO2K|D4r-4stWI^FT{ zNF9rK1??)&S7}XPbbX5napd~d0MgBEO(yOlRWk-E6PqxSY#BM5l7}c0Z9wRYbPW!hEh4!Xz^9Emz#Q3pl=6OU22AqGIAL2%t`A6d;1Q?0ce zz5{Cd8`d2a+jov+&Z7=_siTqKd~GpriCQrMJkFis%x&hfTMtSnWilv}ubIS10IxUs znYQnGk-Jc-T~i9nlKT_|7MZ&ri}INJEzE<(Yr4O}PO<9I)w$3}Q%=Qoxiv(+j_(`3 zr}UjXTInoc&}3Ysn?h7&H~jVS_55sw6Qr*^=(pj9qaqY#9kv{XWD|$XuK>z_%c&W- z3~TT-=(4BVL}#93a5bIorI>V86Luy?l4&4(h45t>srP{Y&6Kh&m=n8Pb~{}77sa+a zTwl-3Ug5>2cT3PdnQ@$VEi&HzU7RcKr3?>!{wf0zXUUt50yphJhwJ$n~*-*&Y0VESs%Rw)JoLW zjfnR1GTT}fp1EpIu2I|$OvM&+w2hBwd`3a}$42e09@kh@(1lXv!Ea=9{s2V_?&Rg; zh#=aI#P=fOWC0Wt_ax&Uw)lcyIK>p6Ek4De067GHX)8X>d)%ER3v|!_WJK5Cd?(Xn zuLyptN%OJS*V>#_(_VqP59cIe8y;#QkNXWOh}XQ!?f)z38z*A-quKXHnZ4Z|YTo?c zJJ5Op;e#8lH&VOrNgAHZob4fQA&<>U|5_*idt82Xs>}Zl&&Z9F%`ACv>NVn3g)A9?AFUW zTOD z@wN0LeptQfxSIW7C-UuhjsoU;J-7JvZXORX>~M9*g{O(l^idVRxDC1aAr|&CyL|72 z-${(PxJ$y5Q_KC1a^FD4-fveIXduzq*>(Lfy&|>jXCU;N!rF;6r8w{5+vJj<)1lZ| z`!s`$&MEJ)PPLN8Dp&!)q(AJ(6d~bARW#33%?3wZ3tdhLq0dzTh$enJ zhm>Au-E&o^J+^xqB|KL5$%tGiP=FEtW7IfVbO*8XPg74vFxHAVpJQ0*#X{!Pr@oFX z+KrW8B4ZVTon|7vL-nD-o5v{bILVkmb9h_Z=Sr~%f6n$ z4#(wy2vsA^XbA9Y=SDK&t%E}ZLOKCpC^5xYSi;FI+r*LqUBNmzeb9V&T3$a ze065@(SB(DKpdjcRBRdKgu4Qvlzl}hLOTdyXjD_MQWQ1&2vGC z@Be1|83%yx>ALcocATp)Ugu;s^O4K?xi||Eg2vE1FLMu-dq%!ih*84>{|rcc4End> za*fg+*!A8LEl5Rl!N{I=mUwZp#7?xIja?$O#qtw43bwHeJ}w_-o3EX-M3WeV z#xgI>ZL;ro%*xPo2+b8zrcZf{oxwLhNswj$NK#l?sL1O5yCH>0$>;=YXAC&EQM^h? z@Zsjrx*KZ8V7t!|*Bx+-6?=I2qu16m6LlA>J6|-`f6TvTJZA5t|4eMgwBWk&X*6mF zIu-j2z)OtoxzgOBae4aLQv(nX zyDaYtY&jf{$@a&W1&AF|!@15z*})LBaKvKQ4aEGkeYljrZc?;sg5MMj!|{@`!v@_?YG<=qsM6kl&GOh*#0k#?>J z4j1Nz9|R6)HF}g1+ajxvghKD#R$11gku$%U#%)|h_>a83?jNHCzaV%5D=>g`Y`BYN zg6V(aUe+6!Dh9hLJ)2Kmtw#>+ z7lTjPlHRTL@r)M-NggW4ehx(#PqXuO8&Gze9Fyj%?@ZSq!dw_BFm&_aMsaLy!W7m4 z-9#AJ__E`{L%wM2i>8UK#pguDn^;gnguT&!BBwpN&On|dq zq0mL2fa$z*iEGOjk7NAn?ZWYM!3CxOKa3!OQs`>46`H|@Kl_vsUcmcxN;;akJj*kZ zh`NrMK*RT@5+njb=SSgDNyo${k|0r^Jj!hAX>sS*y)F65-JbsawL zcC$}!<+yx8R^sluv)WotLEl8<2VT^c0ou?YOCRtrTv%)NKXZAX%zq<8bh(z{=p&MW zGQtuqs#gHf>8P~N-=HE#L9^%FNDPf(+9O*+OxP*xNS4D(3|pcAZYp59YC&qimCWgH zJkTBHS1$Vkw24)47Rilz+F>pYeC4w0WFq0J>p#{W>qdB*J=M>2e{l6Bt#m|mLTZO} z$NWtQba-Rl+E}aeF3=xJit}-**rBKOU4YeQytH)gM*C;1UEdweI0Vd35Iw{!UW7{> zfF5IB$e9~8I-XNFjqr39`Y67QZ^b22Vx%g1w%U2%pRNX#K8PZOuufYojIjD!sm~@? zq31}c{!psd^z^)*9|z|as{z#lJ$z^2>BvRB!Qt0N9Ig2WyhIPtFIdvE(_W5ZpJ>+R zd231qHNHi4^Y}akRBhS(g#Tt)Tk1u04W?3Im*>-r3;UhVXTZmO9~$ru9ZFbmzQEbP&x#>Bn-8j-1A83(If0DyKmgujGtocD(WE>HyA<3)jbF|y*h^+M2^eQh z=OSiBgp7qQ&&;JrCBJ^#b(9E~zD7m)mr3Rce;Gf?x|UFfzeoYN#`F9&4(ahJ8OzVK zWFD+DI<%18P@Vl4z~*N65JCJ2WQR7sGsB0{aUc)Cti%$wMG{8#@l1%mV&$!4L1(#0 zwz!#_KcPbOzB8Z#nxnQDElhJaH{b)!t?(FUs&vpRgo|dz`m1~PzBh|Ft1qn3pSG=7 zE@E0Ws$cwRbg^ugl-$}yZS=8wj?qB7A_v@R6c<#hiyWptC=Q`Yv>g1=Z~I~tm%>nh z#p1+!xAVyMC{bgwd@UPU$aLlztG%(?dZRaSE%sgc zv^PdH9WdmzLaq{B*;7mjSRuZ|`m@(gy+!BwD(tC8_m{RM=>3-F`W><^qj~e23*bEA z-k!#B>LczG{68-C6l3~P3RsQ@5wyyk4kS~!w`rMl`+7M>)jw(*0MDk|JY%ab;dzvP zt>Wil#N%>)8;t@QQxj7?7>&FNpNqADC?^=9_$1o?%ydCE2V1$2d z(LkE-{)7y1kIQlcT58dau8?Qt{=7C_^mP6evhpt2u}xrQOw%@ZF>qgxL)B4p5Myc5V8qi4oQ+;poz z5#M1aNQ^8mCZ>mEl^8?fuL?UNGVer`?khy|tU)X(%eVFkb|=b9#e729{WPcd!H8F) z#L~fI7pPoLRJ4Eg7kOO?P(<{V?RL>3y^*fL;_+T``qyC$lXO-Luu#Zy{VLv1zHOp;f6%$8L&I zNGZ+QV^hQ;E$^`A$q^OoG`wH6py38hgY9P8mjAZZm#`%b>lQ+kS_ z6|qid?sD$t3yX`-id?GwH$Xya6eeq-Ox+{EkRd2(S}W;?vxwXX$uS2smBN z(HP|&-&l$Z28Xm~*RL@?(^A)QRORrG?@L)~9yT2V$6x;Vo_3aIH?&MJ66>y zok2~nI(i`k#&1ngs!VPwi|snX|KV4;}-K95e{!l%KPpLmeC zm#F{nDR*SiDGpc)CKtKmXnce+KG>p6za|0ro^5(He+E@U<5tXU^%8TR#zv9z?o+!P z(`R^X_Kc+N_oAj5{scvJyNfA<~zyhSpkQZ z)7IHbKz!D}FpQ1?qVrJ1G*hRM-!Tn6hT5}Om_OGNtEzxU`RM&$Vp#g$U)-lGIyLW& z)%(Ke;rni|hp{6P+~ooBdksti)nBMG?+7xmk$|{dbNaD>)PhRIFciQLTWcEP8s+-x zp4h;D0~VFE`QWTs_UBPO!?RsZ;F8m~LpMFXoA-lhv<0Zylj`*Nj-VV&dcy(;>L;md zpFT*U-~C}t@|}v9Ca%5DJxojvB$0w_dy@R20HMXM|CLrjUtA{tpRQ-yw-B;<<|t^c z)@Qjq*S83O^s0s`j1%MSkg73T`}KBY#WUHXD6^^h>JKr_b~zc@uAFZA8Ac(2E#BinDM5u`h+=2Sr<#%Ratt@p}0@D*y2c+BMxDNECSm=ty#H?BwXp1O*8JWvR&>+R=e!-kJ|3@Kjd({UxI>NpC3mqZu5>~*k$r^sJ zL;~o!A{4%F&h*j*x)VHlyhy4Uh^#Js_Y;Rc*g)+pZ(n!+wk-L|Pl673SE;V6+!H~M zDEoReNAb%@D6Um#mPyT7W;%M=&96IO+11WD#LtW%^>tW?$8nBmn&Z;OaNq5RIADI3 zfox_q8UHNhPDdbIZE^~~+nRtK(S=*qERN7y!vBG{@2mfm?zg?n)2)GO(qC^I!}lwbv7c%s2$R|Ro<%pw*BXTT}=?j0hf@{UD`FsM%#N^ ztcn5(V1%2s507>YX2Ac`Oa(pQ77tu*eSEZt=xrtdujr>d7c7Et7GRk-pNIinT9VaK zjj=H0ht|UbG9-W6s^QkpPGBx^a~1sYVf@ooQXr8aA)@0J!Z;f+;*$X2%5$n}iq}8^ zw`>C#RQKU`Awk9t(OWg2yNsYG&J@AV@2iDY)upa}!e-DQKcWrq>@9F+UE3>@d|%{M znGd(3>uVeiA4VG;M|;$o3*xz!EZSF+;Q)^?H?6*~j8K5HtR>82k=b)s*vRDE!I4v@ z6d`eEQ?HPK0HG$TavnE{?VobzTHV0+u9-mWvO?(R#u5dH9rM!qTTItw66vc}+ix7{ z#g^3aKAMOAd}U|qCyCdtPrU?fA2~(eD-d$`1qqa7fWMjKFZn0_7b0Ag(WD2bPfJq7 z8`ao|vuJt!dr8kZ`D;4CT@J(muhm8xJcM7tyTf)MmQcgR{V|mg2;RySH}Ksuqc{i) zXcrBzL-Tfu1IVvd#{rNf#-IV}n-n zdf)1W#S~Mqjjk9^Hj3Mt5d7TAFbUChIjEg&Dd-CSa)X7UN7l8w!?SDtMbxiLg%rYp zJckEC>l#rM%o7(90t)Agwq8&UEPJw|=h63aXp(5DOu5cd0-@z0w#^Nk?5$tYosPtG z3NO|rnpeT!x@(0XaTh1uoW|-h`Lz9Y;Q}7XdO!lp=dyOZ06Vl@Q|7xmOJ11R)&OL< z7d$I=w5WPc`>urPbx}u@Xz%0nYd>Xl^J|q4L*K8dN&jWHfHk$P=VI|^yVd8kj%J=m zBOl!5 z@toO&y4oF)S_(X86zi~XxvL)JplP*o9??8vJOWWfGV6_z!atb$>1)z$U?r}}z;DEV zU-g=y!P}0$OCXEHXZ5AY!oq{?Di#blPK;94?EZ(Rul$SZ{klFgLkvBXfOLa2NY@b3 z(xoCHrF4iiGbkxYcdH+i++2hKUyzV_N{?X{YUOVc;p zo_$0 zOsr=3OUW{A1`B+7JNFTRW07R+Sa8DoljVA_ zWa2-Qg+<72wz0>Cm&sL$-pv;D1*k6~88Ws_`f=wwe9! z(^UcI$!NSuwY<+55R(%%co!`y=+pYxf2yOip!+#Ai1~1cj}Sa5$kc~4rX10@`Ocgd zhBHnFYJ1}shBTM}|8+@5&b8R7!@p*M?Pac29ZdxXYBp(=H+UUmuLvwyAX<-KTu4ow z7JWbZV#o63-Gco(TCvZ+3nXnf{6pYoER5g5VMKqB0iG;4~rvK-t=HH=rC0c@`|f-wGjwRpyr=iLQwzl*J~#p9Q!g? zF>MsTP^rA9M&+wMl)QQh9s`4Ci3$iz`3}y_iMwQkGfw(^3U*h%mPJ?-0%u_d=rHV) z@>9Cz^%)D={t$4Fn&TyIvgfgWI05jbxHgO3p7iKe%nNuaa1EpY`!fI8;o7Ih8_-MQ?AC|+KANe{owgG&L=VQC4r zoUL(hCL4m@Czk-0ne|4dBr0(5TTY><@kvBex@YjoSd`NQ_-F3(Y|(CUlTi!T_fnTH z_DH`)_aeE&9fqf}8r~1r>c@Jv?N--ulOk?eCux?)^ENv$|HIkcb%dAu{&&pQGZFiYbJ9&wJUH zmsiZr#=32&xljv6kC4VV7rR?y%9tc(CZrWPHh%p+J6^!>2jH7i11|F^=PbE%srtJ# zJY5%y#8`XlfqV37#2AlcjhA&2UXR%S4#l13w%vaKs*o#=JLLZ3%zo%s{fN!GFKF)(laVa)E}lf|$cysD7GLiwk{$ zF5d&FNLL)4hrUPln7PuvJNfTb1xUE1^I`(=&|4CiH_txo@5kRVYQ5;jFG%0X!5)5ZEbD!k44f9 z*($+Hx`0i(*Q+fLOSKue_N^0H^HLe%-#|lyL~h<6fcFjFSEX4<__;`auNJ)^&RM<^c>Jv*&&)Sg<4GXB!6a;3iaqkg4C2x%luGe(WA?R!=` zv=Ftec*K7(2`x|aCXw~XjVA=+m#b2}rG}rf%`kwHGGbdI$(yu zn#Rg7vn<)P_)LT0AO8>iST3VG>kE!*5)QN9A5YzN=L-x+N)9_SW%|_B(ST7K+rhyQ=VxUx9G^{s%M@7fS3-~;4cLcW9#c;r>WS80`bfleYr*>jQM>`cIdRP;-G9(q|24_r;X+Qgz_8V76xD z+0Sos-!Av^i%_7R^qXphJQM)kdx#1=ek>+aG4hO440vh`vV2W3udzxpZX)|LC0Jr5 zWd5}UZ!s=RYKe5AhWk9PSAHbH{d%VB_Wa&7|IO^y z0Gs&uve2ParuJ(o7s*cU4?7&Zgt!aG%m6Mx#pC>|d}&&`iV2@(psZC8|KE`8+LRUZ ztOEUWk6Wi%WX=x zqbKlM6PPs^@x)3Q4WvJ6EMTf#i#&uMEBM#@H5hKn>W+<0x$@yznh@pT95!jIgW;po zqDf24zniOM7m{;QzI;o3t=|lnd41+z8 zRFXLunrPveYb!xKw|QXx;(s07HDM|4tW=@1XqS@Ra}iq;|v6JtoXFhf$efg+oF}~LaA98=H&^oY^++mtVD^WXulIo~u0o?F-d&(~zx>i(&x`S)O3Ed(zpA zmYPK6M(l9%TT#~5h3*AI+^pQ+0&qO?uWtxSDtkVwaO}W_(Gs z2w}GD+}76&e)l0dM0BiE^oDocbV()^U2AArQ%O`BO6lf<4Q2z z5Z6JX269ZNm%)sGaTT!rs@)*(F$x+yHeLm)P8M?NVm_yyvsq7LPegGOOe8v;xKKF$ zjz2to{E<)IQT)A{j7_E-g4=-W@w#0s*P+0|5lPP>eY6=ips8!Flv;Yt+nr)9xcmam z^4858Au$yE6PAj1`G0*MHFX`Xn{*DwyrKESjH`MOe79<>L%y@&2MbzM6>iwA8C%R^ zw=bJ!JmoNOP2D0bUb?a)5Ckb}I>j$F_a|^&3Lr3XHS0WG6E*wgMHKGF<7+J`tD~Q% z5hdAZzAND7UeWF1jcsy9vK)M?R5vY;Eip`D_^HSQA#sBhdSuO{iXz*R6q>{os{ zKV_H)UgT;7#W%At{2~9NO{XqJl}af22^g7Ld#A>< zmilh}dvA-w zP8I$*6;$SMLN5h|3Ut@5NV*{|^sd2fd^(@*GerQKvJ8O-?lNaRV?NvQg@-3fjIBB{ zCyAXf7zhcac+rO~NN$HmaKJ!Lw;|X%7X4~7>Vf4DPB1b8k9_)(u2#s>uOxvycFb0T zz9~{*XTk(hWd&>+6qyH~p{tUq-AYO>(F*9B=4h%a(ve_eYUy?B8p`z*ifG77lpDjQzS^CqR+_*tHD2b<>>oCOv4eu zD~iNR1rG^u6``sP?t4aekEqP`s0aYPUck=r+x>izUWk#CLtk*Zby}!*N5cnfI1sZ&9JZsgAB zs%zSw<#^1L$`cuReJ@L(}c{u{X=QUAoFOp^tnt z-5$x0!eu*63t&*%NL?GS#aR$W(xk>q0;bxdPu))1fH{Jh_SsLfYp1*vPtD(6Nv09R za_=Wp7|u8IA%6b=uz{xHpMuiJjUmy$fz8~So}W0bvb-Z_c3l+maqQ;)@hgF0{Rs%# z{j%C~bLW!go?B;+X2TBtMh7WC6ao$bb;vkaw{)Sv^~o4p?vowbV>9E&my=K1aL z;(P2ixk-lR=+jU-X$cM_Pn5d?V`P8xy2gcuTt{<{ehxmNAZ7UK_qE3jH`_maE+_0) zA`bq1)>-+ibMsK{u!Hju^-iadfsQoYqgohOeE1+SJWAy4u|yavhrCq|nBy$1omjfL zJp@l$+$x6a5+oDAy!8TRN`Mzv(-?xhlX~Wbdn!U)j?){cnz{#viIVUI|e`{TRjolZZ)4i>%VRMfgBmN(2dD3pN;n}jaU ze#Ev_(=*U|IeG@VY+y}(kdWz}MGT4%c9)Gyj_H5#lmI)~cj;bD)~>Olg;YtWZ3%nm zA2ow}`=|6`na*dXXVgJ~zX1wtxWO%42E#)Ji@mS7BD=hnuHmO=HC-NNWVR7*#SVM< zuYZ*5&W^)W`yebej~G30konjEGO1mTVpV4b#x#m{08Hf)1Fw_!S9F@r3NL<5HND?C zmR|RLa#7n)5@NKP+qerqbToM1g*&~Dt}pQGvCWnH!J@fNlS<=u*#~v#C$&w$@61vso@PJik*#NOmbY~$^ zL*7!5`l>9`v+N08W-!9TvGaGVc(8r`*dRm&;4cZmqvwl)U}}o8QoG#RkIH_Vj4za6`t;gwQL8(toZd_i*wDP;=VmTSshs4`?LtJv`O+H$^QGi zM0C93>Z+QKu+Un>FDl=PPt0*L+rWA@$RK%M+@g#|Q~)V9S~QUQtYmTLP64YT=UfiV zmg6|m|Du@udzU-@Ulqdn>pIVWx!;&NCEAOa<5xtXdrY8KJ*m@cY= z(0v3*zV0l0HJ-AOvqJNsm?8GdBc!HFfR#4!au)7D96LBzmUA?=N+;4#r1gcM5Q`9e zg6WfFI>%xu%*HoW;}p*-XlKvlaNQL_YLVF#5-WeJ9&q)!If9cvM`O%J#RmK+90q=S zen1CXi-v)}ylUge!D1A0Z*kGM1TE`70&hOhi+uudf55i9Xbf&3$m<|#qy`g9>|t2* zVE~w^&rNBZV14xC^6!IJsg3I#F$}uQfZw}yskSJ#(%cDq%U6Wu`Vg_XV7c5%e6N+~ zmp72=<8z=*BNP~<^zRBD<=+H=k+3kxBhbhj@6*Ee4ZMi$JU55HA3rl|&4#lxt$55n zsnM~KAgQrz^rCC;_VIxc;|8@3+0l>lE1WfsZOMCX?G7pU0q4(Dg2Ee@O3Lm}fh=^|X zJ(OSByWFda64!J{OKk-5=z9!77u*3G*3Hp6GUy*g zubET@r8{~$VS<-wgdjPlUAx^|20F-FZH%hXKP|vgFe#M-z)i!2wGf6?)eG8ZWpBxT ztJmKpgp@_Him62@o*C~5BcnKIbJpV6SyFELN5&1ja>?-*SrmzV{r?@plNq%ostPhnL3Phpms2 zw--6+O{O8J*BJiZ(L0uwe}*KGif3wVXBSZz=)y@I$KwaPS_BdyJoh{=x*&)si`$N} zwYAtALNK_;NC{3zYISrV#B>m{%xcG2uBwbO9S3$3?=wlt@p%>-fv+?GA!EbNwhU7E zT9Yss1~Woe9(hTSsx#RFtwpf3vp3$q>|zWq@>XR)FC~qPv65UltkMH?_J6D|p8EH% zbYw2|;{sKb>nB2;I|7$~^0U7wVabMt>V*@$<_bBNZwSw0;uV3) zoMxYx?P!hQnt@+X}vdGt((&avB> zBV*BjG3`f(f6u1pDtpJ*g!a!>53H*jn|urQoWp_t{q%0`g(%GUH;&s)-`mg|@&@1K zUoZ(tMNrNSs=eHR64Bn$@my+4ys~HuJ>F3mPKP$>&52Y_TmO*1+1j zG6;~q^F4Kde!b(hnZR3hjus(T{lw&Z+US)%a+Q)O7unSug9c?utF+J>^Yfg7k6M#j zE!sWWE}o;?$M3h9bfqyIXp4jg5BQShN*;5U!z);X>V9qGY*x|yhr%x66ccX@=Jv%d zsVaxl6e2XXm;7$KuU|ctNnuxYsU7n+i}SKdn|83uKoJP{5i#lb%|c3jqW*Hjuzuo>3f}?qipylHrSowBf%* zqP`P?6`r|x$PY^@SPkE|mJd(9HGRct`fOw|?J7iT33fCn!P*_AjEr`Q*JmpSjP3bmkm-{5T^yr?e@(lPi`){ z`d{%FFr$fyl_Te`|9~WRvrfFZ|6062)30ryBfiDXw|oze`Hs->)r}mb{lq)QsZNo$ z@-+;)4}FbQuA>JTOyn>+;$pCFvg{yek=<}xUo&ZmZ*>)F`zEqc3rPcgaUcYMZUdHu zprN1GBsuiZ=xkOxI%}3#@kKv8M9`_l+8X2SL-a~_5Jb9GQ-#uw&eIh674#xH;urGI zW)*+Ow6x_N$+#I8S%sr`6FjDZG}lI6&q7-?KrJt;&tDMeuk%!ZDp~M`j@T{*7*_fW7(;UcJVvM>C(4)9)pB3yA;G0t4J6f(A1lje#Wa(dWG8;I5 zTf}b)^jsG?J6d~jF4PDdKIo~Wa`@%*2tBM=lf}m?>nLv7q%M0T;2Lj6^j}d5sQb-| zkt&+Isu@FBpm*4>qtZx8ms{M_B0=nLYW)6?|B?)YpgS#YaxZQ?5jY$!4d@t72^K9< zrn7^gc1a{ls{zaRSG;Wj?k)W~jgf0j4+zz!n-jdrCoElO>pMWgGdgT>!U8?cXGure zd+|iLS3iyTyW@&qbI%0@CVgPqL~=q+a;70b%oOXzdPI9>cfyR!NR}bi!`2^4Ezy7Q z49<>vz2;lnMGo3DhnK6x&0FMeP>85lnUf0T26kRRod_ne4u%>iN1G{v-{gS0HHzlK z03eVRE;|+Eh0GycZh}HaD*eE`sZc^K#gq80_w%?-&J58BdFpPP+hTBcHfGF>&~CiM zM_dQaMF?|l`Cs>dh8`<#nT2$>VE=nslXznd3O5!PgSpv}42G%@k;9lFOKdD?q z52}~7MDI^r?qHwKCj>peTAhaheQQ&6gyXpuVg%TI@>P?5|HPID%pLbH_ni}Kvbddf z_p}o+K<{mCPjkollpnzVgY0s+mANaTtl^^eVmB2{(%^Ays~eQ!`MXu`!MwQMQHcNM z3QIXF?t95Y3$4#hSJThx4*!rwW`CwDOb99tL*7K=rLtjw*1_IG6za*{?BE3<&?0>| zkoioFmH&Jo9xEQC%DrOY#9tj#i2DVX{tu(iZiSK$-}0m;ZpXV2yn>IAI7~x{v|V zI+h-?J8J6hGhcT}+;lO(xKbE&WN=X(i@`Z_4_^kukdiFiptwj(X+CesCNsl^CN*fN zh?wboG|(E0tAq5p{HIieu)>-DuO`#S+V+m~5PIT=eCPd}!dY>C-{$`ob=aTant9%w z1r&~Im-ga>I}Lp+;wz})V7i4-bO98ZsuT)6)EU92hb=%(pJw0&o-gl8%vyffMlDycMDH!^Yg}AESSiH zcGdV($SF-O7wPNBO>bgkVKI&h%e4gA%W!@7_rL+^D_USz*Y~wCc7#bCti6Qp9@*wm zwc2pogy!GeBcFpJTqG@NI3q5dx%5W&HdHqx9OC{W7x#VIr+2JKZds6y&~j*MZmXkf zV}!*uE`~_%s--*OZr^DhutXMC`9%=!2FIC18+`AW(*>9G-|qh!MfR1Gr^Kw`>j4+a z+r2d+Mu6V}Re9ybEA{J7=#k@#b7IR6ST%kv?>~O-Z>#?OG5p1Z!!X2iZ_Y>|@2CTo zDu(x=GvPvNyltRJN$fQ-a=M4sluzazT`~+f>hI*sHM!^Oz7T!>>xp==`3WpZb>U~f z{{hcljUvtomjAV%c(N$gAKF(#o;isb3N^`|@pYJK{)WbIK5DLY-Fpp75SNXg(+=|L zPJ`S8?f*s?;n3968^#p}DdC`_b zC$q2rLSARO^&M9zG2I)Gyx%Gjop(Wu$>6AfjfDq9!UPKJ3BTe$12Nxr7-b($ekDEZ5U7s9A{>`JJ%b z8LO^4Zyf{oawvYpLBAAJwN!Rz0Bf2V^uz#Mes`6RjnG`1v{i~<#u^cq&^(wlgxuSx zd+yszE#i|o^d>}u&x5Q*g{E{{% zdGomALOS2Oh)~ruE0}*<8Xj2&N5g+(i$6HsT_eq<@rYDwjCy<%Ss^1H;&jzwEf=#c z-(9^n$1)z?jipLVUYLf)V~}Q%*pJ6Yfm(4JPEeT=f2}#Ti&)^kf~nI%sf1wVr2Gqm zvnz=DED41UBEyj7HuOhiI3w30!goDz{e53=E>zxR4L+k#oXrv%AKcUXBe&S0AUo2V ztO1wd;+nkl<|KqxUSjoNk`!PbAx!0?R)v9Mrd!u~fH6|(U0~p%m83cNoA_cI;xrP& zAgyJYN<;9_hEMJY)q0XDQutrmoeBBwHGyiZzmI&}J~4Kl=6qDw3MGi#8{7OzmCFq8G;{U1IYw=kuq_`ZW*k_E58~9 z4PzJ-@tbQs-9280MJ}ke>lu*2D}o}DU54~Q*hkJ#WQYP&o*}WRL2s&;9%qqGT@jFn zI#GxwzG^Fd;^$X++lfWETj!|pc9G?q*e7GI0>DcYVl4_8L4iiv#e0bQVr`en_$wT? z^eV(E#aMCwcQ`0F<1n5g=(+Q|1|ar|e+S2mBUq|1C^`gjeTs-eoStH6MN}nVcOyI> z36JSu!QaFTEyVnxU{pQ&U~9^~0s39FrsL#pT29;&Lsn0MoYOnHe}BKJ4)v;#?RMK} z_8u2ITpj=AUDQ;y5|>^@hNBU-@+-}j0U;yITdw{wC07lm9ybP`gUvT}b=BR2`-^Rd zSJ7&u1Eeuulvg8HdJ45(y@q!XFVmDYqgl|q8_lhV+ae5zWS=241Zz3MFrH}>vfQYF zmSC>dQvrGpmxWm;DFK`2#UK|-r2h~!jpdEXBC#|x`u1_P|w2oQG(z15?z3A%2TXd)S$kX^rg-5iz{461G z&Sg+=!~ixe=J)$__1a;Jlqb~$hZ6XJTH=Ac?`ng_#WJVXazpCu8d-iI-`)IU-qqZ- zd*x|~%T>@kzo+gjT+!qTS){G2I|2y^l5k0e!{&gM+QUx9AW%==v+%41M&i3|Nw+$H z*;)PMa9e4Axw_jGwPsV+9Ctytq{AN{w5x9f9Q6#pP5KP(0N|oQmC(X(@BF#gkU1YIgmX$PEB* zXyp|e>cdC}qy_1j7dvV1NGi{zoAV0+xznYU&m;kaLMnjglLBzIykj3T1(7P5cO8WJ z9y=2Cx_li}J%wrFCi=6}uc+6M5LQxk>b!^)WksIg!P$RiuwHH(5*c_^j%MIicYm0a z8FRe$kFk;t8rE~V9_Zt40O^5GI<}=qkU6gIJrFNG!tU%7IbT`hXe*QYp;lH z&}zc74USi})VOtx<^=Dwx_g%68&UB1fp_uS;R*HKyb858O{$~*dwWd=xu+M|?rdj_ z9`78C;6aqHq6R^Ezj>>;fRpN4i=RxC2FK!_F)b*^F;ejV?)eVe9>giygtFz;QSKiE z&vh+I-Zv~*dAf|gbcGS~8xyhLc$@v;a+98p^o?6uc{`S6IQKdT?zfCU7n!)vyC+{q z;cAm(o}+g?Xqp2IKV>cJFw9n$FurWOQvOhx^qj+t%=5bt$c6|k_^LHJ7^#cbWQ=Q? zL%|gA=mrCK!(u!Z*{2Ya-9ES|lR*wT*gK(J=@Y)xiNs?#&&3_!4w<-zdG6Ih* zny=V>vB~wgF)@#jJLk8VI%unu^&bE|G@ASgIg^;bzp_ty?Kyiwv6}fbaKbRF<4vL5 zdBlCP5lMDg%azc^+*+pqE_}dUd8~36i?V?{t}^C}Q;sO!@@(pud&-|ETPs!)c)ZBh zJ;gBg4aOeS6BL=FeyknrMBpaFBd>PfOsREJBK^j($NoE#>$>WJgjsNa*C^8 zB*=~tptF_B&g3PTY1mAEuh4RLU=-4Os3F2rx|H~yppq&FI`&BBR#lvO=x(s8Ty^`3 zbFAZ1V?4ceeGD^_b)%#(%{_|#Oon+GX`gfLI3TmOnR`)ShsLGbzB${zD!9Ol8yA20 zhaJ0$--P+5KLqLV{c=$aI{ibvPRujg2^?@3T3t&~I>xH|EgR|MI$yqWb6D|nKB>vUE)3@@>gl7-xaqHt zAOl{?qS0Bdb?LcZMgENtafZ5$Co$%u^3D`$bBd`q)*bshI*+?^AtDUh!QetCAflAcs_zt=6?EMl z%!ycMZnzwzjS#mUB4P>)r6FbbwOi9<8E!4JEG-Z~3EWdbS~me$8r#r56-w|j9>2e% zX6D1FP}EWw7~s-CPC{Y*fgUz%TzRt&F$vjijUVrK3l?wsyZ?E@-7flZ zlHo$sqnTr`_Bu1tsp08s&XPB@#HO=u;TjJpk=al8(@K0^WAjP#$XOISeib+}-1fYZ z`H}R7yky`G#ldy4Wb^kwA8V4-Wk=y=I9(tUIyM=}=y70gD+OnGUa(UaHk=lgS5Nyr z+r)9WCpk?2vi8YnGxuS6wuSt+fQ7 zfT$Y_S+mC|%&h0?(r}f0^0#BRx;#8`NeSE#?p0i!h8)`(pyFHpB56;32{C(J;Fr8o zGEBmRMFU##N=fr7aX;pLxDf+$=(C=B8~!PH(h~UbYCq25)5xheID)oq0$mW6k@*LZ z*RK@zUTL4OZTLzLNQxmmNQI>8WLF|B{9xkd#C7O)TFdtlbfDSlZ-d0r`=F-K{e}Z0 z7;>146`p-RIOauZ@8)2LuSZ(ti98c3kRhJ}NFafSgH7u(vUgx_M2PN5fx_&u3n@~t zpnQ!?B_}b(e`wLZQ-@ZgfV^#o*{BASCt;s9vKct(*<4dL)%ABLlw#WYOycvUqEDeDu%NdE!2KRmKe!oBnAmP|}*>8;2qJesmn#tCj9C zPBe!CkPUrzhzxzyE{ zS≪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

    jh=h7xoK0rQ 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%0Asbzn?CM?&%V~qx>Dc6S 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

    ~G~dqhsUG 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%0DbA; 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 literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_128.png b/docs/assets/cgi/icon_square/icon_square_128.png new file mode 100755 index 0000000000000000000000000000000000000000..2600bae3bcb8f11cd6ab9496a70e082ff7f866b8 GIT binary patch literal 14641 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 literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_16.png b/docs/assets/cgi/icon_square/icon_square_16.png new file mode 100755 index 0000000000000000000000000000000000000000..e11979d52ea7384e525d9c0b14c2289edab5b92a GIT binary patch literal 1066 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%0A+!#%` ztq!>PjS!dbyimJf)Jw~p+i>wf7J%p#5Q%|gL0U+jDIdKKqvu}<1DF%}ocMQ%Zu%Z% z_qubTk67p5IOoFYJLL$P+V2ve^s779Q~Nn{Npq)*#7T|K8DQTW?J}lRMmp#DaaR&( zLDuhea5^noQ)`v94P~5u8+FZ*2evOz*u^Fz5D3$kbQ(HOG<@a+jGoKWObif@T1#OFnUcF~n)1@rGuxPE<#h;Y1^Gpj511_P$1WNb?A&rUH~ zaeeQAH;#{Z>+}SpG^fi&FbFOdJUlmq9( zp~<;^_no_hD(^#g^Z0v&T?8W85ac}&(VCw zld}_ExORi*ru#I`Qd!Hg4u-p>tr-;s*Ctac+pt#*c|1R()Ee(S*CzYin(k38mlUEf zN+Sq2CQ}wokR~3@&zaX1H>UgCJ3Hou$qmfs_pm;M^t!m-!TKQjTkC>PKl!1f-N_R? z9`O>^dKxDTKKQ}anB-hHSDpGK!S1+j`YwIkz56}7TsB9_1YKs+N91$0F^4|l&_`gp z3}jP1+Xzxub?w^k`XDQ#`U;6+eULH(;Tj}F(Vc5~2l_g`35n>G?>^gYt6N`J^iHrz zhwrQZX`e~Z72k451Sz}U5`l;?N(t`gOHTG`=5@`Z*%?Ae?4gxnZ!ly!7%(YHPG;wv zESDS%$5LvTbA(O#? zlf{g^au|$*%HTb`dGv@6-MEc)j>G#0=({735X@8C5Y*Hf^$5#_J)lkejcW(QOX~t1 z?B7BcqszzmDzNR@KB5krzg@S!oPqSsr*1oau!%VINsWEab(bUeNk4sk*c97B|LdzY z9Vi_p3X#N@u2CW9j$J*d3s3uzH@#@qme)Q;nEBOl`XgRX_hido(<$pZ=jocy+G9gq zZo~D|ziwZ&$4PJrDii+Dp#RQ@d9>8up6+qBTrjtRC|Kt>trkp&Aq^OSQJSOq4C_4$ z+i+tvL5Wb?hQr|)CBm|)d3JipurNGY1Uo}v46hwMVpdf=J3U}&YYs;f2Bx5~mg}Pl zXNv{r%O$g>;^51-scnefOJV0e>bJqJVD_Gd);!IzQ4<^>rT6QU-Eu9zuworcH;TkT-`<-w!u|Y?`_Z*__T%aT?qPH)2VVJ zo$4vGP4wwI-vk+Fzwn`b=R=6U7@C;^FJdB!w!eLYkq1$jtW4i?ebI}37S?X$*S6rM zbgiLXYN4GV8k7e2mkQL64%jOPVQKGZ%(y6d{@P7$9~`1YaWtRNco#!@gVo+T&*5;y z+y=e>)?|-)Q{$XxZ!qMnUU0fx;Jo8#F~d2}!7w1a^NwjT;AnnMZ5yn&6vnVOp0IEY z({jl0tmO8$pQmxKY(jvLRzl@MMj)jG<)CUD)`e_HRpT&9s5}gAzKHj7@w;6HIVZeI z1ZO_pP0#hd@5_dJ-6+3J-!&e$a~9uEyt+v-w?QB7Y?EMe$me*b8uV4Evdm2Djda}# zeH3kzp8OOYZaJ~*LGHDlZn&QJF^`h!e#C9zoZWi5_qtJL-4M@i-3TOwLQCZXk$$BT20 z>lxZ8DrXs%hG9{#KOAF>1_gVg0q1T>Bc9`C!QN=dQ8Q=hEuw^Fy`Yu`uLY|;v#%3 zoSvcQnQln$WqmcBvg!MGnTBCaa(F7ZD)L{I`+M-V&jzqb1dk7`xkJ)5+qu8#`K&{U zLIUd_V(kSys7Rq){A0tPihhl_`U#soo1VAvS31|yv#G3PWmCC2t50=~yM0ixJe~5X z4WHF*!>=-+ln;i%eWCeTgEoprIo_N-;R82sp^e6B&)xYEN)#w~`Q~j7reiGdXmQS| zt!Tu75?;D~n@9CIUV#%&BcA=qi1A>+_5B0x&qK!G%3HJ+?#_opz#7I&nLBt%t@PXC1kc~n2+NM9}`^P;!FIky< zY}0xAmfh#K&8Pg^^_}N^cb$9Px)UKtOdyvSt|}GuxedFJ0!!c`A?S%*6y0Z!?=!+B zqeya(<(gz6nR756QEJ0kwPZRNa$O%#H#M)FJmi|M zvEnI==IzBXHzpzcczLfzo=m8(pJ5b8jAtlySS{$|C$QFI1oY@8YBIj?&h5B$$6m5A z$|X9*`dmw8y5BRev&)RSBK)R#)a^^&UJm&@&F^<{B1B4M>U1`_75MnDDJrJC~9gy=Z-no77sf9HrcgwJ%28LhR$)* zsUt}jO{d;&8`8O#x8XX^bnDVRWwUEad8F`&^5_x-5C86f(`La@Jp-fIACD=@f?-)Q za}~2@$&JZ`bGzil>(7A}yz(qu!`wDF@f_PFy3|;K$5mKo@Zzm!IBMoRs?N}ZlBKVC zdw#^XkKUpf6x`Z7Kt&()sa^2Q{vl2r`{N0NqTuk4Z-ENYfL0MgnwyYBL=+fJ2q{*Y z(JL>nk3-)$>1@2{8ME}8zFplZ(;e${y|1qBbKUQnm&>KW?wm=-blP#*d44aI^{h*q z8K?H*+snO><7O(;l?1wOzzqZ>(IB%{I0j?n>jExt>B~YIeSeu&)X8;e<+H$z{CL%I ztvoh8BiXIo;FqrJ_}x`3lMQ4`P&0ip?p3k6mdg3q4MT{5&)+A#UmQ6d%cA0O?1N1s z>j+6S@@MAa96=Y*{IFnOQOIi+ZG_WihI4T4mKdWLji=l_dYi-Pl#}|DS}eEr z_Ic~o#}6151*f*6KAlmP24#i2XOB1-jab@-Tl)t*njbS63^`gZ z*&7V864H7fE$3W!nqg7k=TLt4kmUy+hs8qHqm^%6n;{(<6xW`|E1}&uBaT};K0c1I zUyUbz?enioad2_m(>1>u(+l6K3;8$fAX_ivP4gr5GkX_0(-sxqrw@GmuaS_r)5oy^ z(yF~A9k+4)-S_W8A5lns_OVl`zZpI|58e^I0I7c0sW3gi(=bgI@`BNK-T3Vvm z?^~TvC;dr0C?5rcL_JqgX6?18mZU*XRj&Dho0Tv@x@Y4xvTLkgv-x|bGD!`J*KFpt zNypvE+qRqflNHMEDFX4;#57_C%71TyDnda0-f+TjP%tzFOBxo{IoHM$&fO9xjvIUX z6s2J@9xxpaXvA{d%sFo6T%Yc7Fdj2!K~ZQ%gMzZuj0YtPUvp#skSEnS#lWDnVpJ9^ zea(1Ka(8}2p%oQ1XLb?d5x#R1-x%u3Q+XdhSy*RYTMueT`OGux@7p(K`S|vYS3aQr z;-$6tiar%JzwbNOb4c@q?7Qc4$gj$@B7Vc#oNT8vv~$!shie>8SzK+yF*b0PB+Yn8 z`zZhcNAmaYsvtR@^%0w{@lv>m#}ek+`eL9+>DJ-^)cQkPb&Si9A675wSRuL($tHD< z*hwu&&u{Xp`gH=*KA*1X?3nDH?kS%elIZlKTPK}sWY>MqU#oQa&3(qBB&Y%6dN= zQ49=&vc!}cs~p2Y!CU7K8IMZN?UG7r4kvq<(%`h`&f!g*0xO=upuh}<9OwxL-*^ry zG3&7iUWIS!1V(p0uzAeXhOy~8?~ZXY^0}r%dh znBjT~kXrTC(bSI0xv=kpwb(imf~3|sR+18|>|_b2`tZ(gxSm6op(kj9)pp$m5F2k* zfK4uhKBLH{^EJboUm<=Ra7~!#I8W`N?jVlqehgSq< zpgrR&T3JTJl2JLpD$l4K@TfZIKC)>503ZNKL_t*Hq?zHwb9eTLS}dn_ z&beJOYtFIaC?8BwX9Gk@n8!A(^Dl;nsOk9PyXN02|Lq%R&*nMRH>R6J{VtMhYHt&4 z>+?1)>^%EO1U9vi`k#+gpWEBh#qg$YLQ1=B z<1*apuNjJebx7?j)41OMVRgcpUs9-$FF+N-#Edyz&e$7`7?_f?dclq9KBrYkA#S~A zq)goD$5N_-rL7p4601D>qY-Cz!MR&d=pZT3z}{fYlV#X%ln1W3Ck zu};i3;a%=I$%$4{Di8O@dJ^)-+eUjD3zh{k zJj2EG#oF9P6)%B)9+5zi+U=?6Cun`+@LoVSHYgwCbgkQNU;9_Nmp}l#55_L*F~8_p zo_w3?bj|`#H9KX$o*)R;Tb{T%OJCzPXfae+DnF-kj^UtT;KR0HMueJ%rGpyJKnX*M z8F{rwSz&2mPfe+vXCW3XB~R>2mQ&Y&D=|tlQWNIiy9on8DTdFzjGlgE<2b&X zZ?b8;>A8N@_WSC5w;+W1?VA~{dKJuTXnl?w!N_Xnk4ItiSZ$F-kZvnYBmxn!G9r*w z9H1-JbkB2_b8Bq&*lr9DT?rss3cUu(9^}s(?7P;bQf=i4;iFDOh!sbBT;=e^DPn5W z;0Ai~_o4h4m_twnS{I;8L_(;SQVK1bbdwcaHE`-GuxZWErdW)3bFX{uyhFz^EC0b| zVRQkMC`3sJ7J3KZyu(@>5`lV;cbnT4rMMx2*80*m-|aWK%D8<$pT88M@l3av=fPPV zHGcVBs^j0FKKTrTTW8qw5zCXXiL3WMEI8DuKPq?yGWYpfCa$ZfKRL%wc$sW^c18Hi zg@=k*UuFQ*igs)0Uh$vx-(>jHDsZnP`UjmqB0nBiTbAYt0|R>e^Vs3v!Ko3-l^9Q; zN@Btv3Z-%%<+iu~!2u;27Y#!CDW>a%ot&StO|$~dR^(40QBQj}ZT`iDj2o`aO%1*V zr2`rbKFr|O%joGRnBV(H=;C#zw>4+?hTy{nuhbHD;&Pstf1PdypWfy-G2Wgormm%L zbukjqMF}7S+vf`GBLqF22*e{8HgweaA%>>vli-iu`v+9=A@E^Ai5PIn z-U*F$VO#b@1X2q=+5*U$>Nc4nx=!&3af!U`SUg?Ru>s`JHj@X-tlj9mdk^xr+`p~I zw}eET$NB|&=#cT>q>>K;9*sep5?zkbdJKAqr~)M^n?|x=ynm2?2Y-RU<*YWd#I?Am zP%2yN6OH8IBArQ`W=KCf)T-LnJ1JDS}SM(~zA+b{9 znqchP1hzFIWbc;~lMV$+#y{J!&ZLG#c^WKM%_s(UsP!*^cZd||VuUI8Da;hnBhV!Z zA;hI7W#)bA7TCf6|3Se@yAJw)YmpO0+Id$gKu;Jv^PjT3_unwNUUBwz32||db*q3l zn+OQ3yZ!R>?r#L!v3fQ(GUlwyY4_*g1&CI{WUtG`zjmELx3R9DG@iHNktzg6XhQwA z2CEimBiP}85(tK9bTLF1Q*=2+iU~-8QUxod-y#1F-aBxa|KNhhDnc%Dt1HjF6h50o z_(TYZIFqCTO)>r$&E#+37QccF!B>F9=s~Y82)Y-Hf$N>^HO}jd`NGA3z>7vel_9#z zVzt{|&@TaO@cP-LgZQ9FIPI~vVq|Vp$xWPf=)$1Q5N*aNJpvg7ogWn*PH~Gn*ugu5 zgzz4JCFF-bPg}ThIWf?Y+ehTcchwRL-k_And4t*eB-Po!q%fY@Jd`&kY{6E>R@c@> ztaoU2oI2da!){hqCMyr zaNdJbC|#oT09}j_T?A`?THGutEzynu?BMCa;UdxpNFg{jqEx!y1wmRwm*Cm&sNS-j z5D2Vtq!H9u7g}6#T1_$j2s2kgX@j>bW)9LWL=m+{^u+nQUEN>=ycDcQsSWu6OrL2m zw+bi3?^2c~_%d7QiM;SQv;xda;g%@0D$r(_iTye9rwVt5za3m1T+8cM=x?#vb&GB9 zWH+fpf{8Qr^CGVagDt)X!AVcU&AP~j>O($ zo3IO0T4dyeRr1>{oWJcwE5UcX0ECD|6A8@<5^}4R!JrjE@_d0k4SMZ2_#*`qr2teD z;vDpg?eIa!iYYDf>kRp0WlHr`yn`KF0EdqpJ|JmGi)V4zV1SNGMt|QzxG?C-i)HF0Scq;0M z_zlTLm+;QV=OcOW4(CGB2!}^`L5rbKB_lm#WX6pAkWz*?S--kGQC9f~EbFfL95=NF7nj$Ymdb6zu}#};<@SKQf%e$1(+CsP zpq2u(9nTQ{f?;K60NlZLNXTzn>ZO zF(4Q*h%xMq_PKNGC0=>vgN(~5u1RtJnsZY!(G$kSggrB5zzDBACPNUlhRsO|$P8(s z)lt+s@V)FZoUFo9^7Une^To0fEa! z`klzXgUg{S_SY@9uc=vtbSQ2~L&I0T_Z5EmOP}J&`F#X~@q!FI^JkV&DqQK2VX#sA zMqml@NKmJWhs%fj?w@{^Kls+?_~^?Y=OQT za>zdTAM@DPYG(H>pZn8ar>d9y_?@5Td>M9g*2JJ8(J|6aA-Jo_w#jZN>*h17VuWp< z*xw-nt-Y^L;8v?;38}gD>bfqxSgMr(u9A=@Hk^`(^h+o^Vz7gE4(*E57~E4+;iA}I zxh1ckzQO>$AatG$E3BMO!kgB%DoPYWW+nv zOA0%*EMKem!goH$crf9^H-DJ(Vy z{jG2C`>*~sB_&Vp%z+SU-Xgy?C@&~y5K*Y8q34!))rn_UwL)8kSdEB68iiOvNmwDs z9fKcwo>a$t`Q8_?&ar6boLA>8?1I`gZGYV+B9Itx7hn3^d7BdTW{g;8(tGZSGkV}(2DR%q9t5Htbl}-f$oDsO~uz= z{}MAdqpoXa&78TN(fEqA^w?iA`F+prJZ8??s0wTWYc~*yL>$bzdcINtB9gbbo+G{A zLU}Ss#)|e_ZFp{{0qo!@A&JpR_pfOp_YdK|zy9^#;_dS{(WRmq*1-T~G|DJM1S8=$ zFj>_idka0%Y9bCnJrJZ8v{mRx1cEtBD8>qZ!*OmGeCxrVUuSAP!}PRtIpr+KD%qAdaIFK`3SRoW0K2t4 zJ9tWP*}s|^OM|D5>-!p>%uo32H-CkalA~vWPhLc!RX|pw6h_k0^SETktxfZ-uoBHB zhU|xJ+azu}>kYVD3MUQUzWWWlcQm$P*(_PwCDyIvmtHmC66;_x#M>n1Cw)JU!Px>D z;`Zbn2_nQBWC;rrVs<*TQLNYm+lbvRFlYx)2|4!7OaDe8KGo76ON zQS=WR$t1zQJzUS>c?sl1K-Q zPHTc)U>o9hVsUaXl3j0CLk#{efALckl$_q0uSNPv`imQ0hI}`&Hq~A7R9%4( zw5l-xg0d@x!le+|5j0qy%pT)>AX9Z+W4$G18|XCz_DID=zV;@?;@BRQ`YL#7fyTGH zkmq$=8k>-!`L+rGeG!B`@7XrCEvwKDb`YV@@Yl4uf6Z@x<9B)M^n0iR7Q-4uP%+r2 zh@!t{-QtxHzMG%37u*W*B`fC&pKe2w#m|iu9?Q}4aR}#iF6^`T4qRV#3aIlLj$ew1 z5wd`wwzw9IzWAzZ=NDrA{ngsr#Lc_Q{vADmMLXC*5A+-UmT~D%!{h3N&wk@G!RPI1{tiVs85&J;S;LplS(3Ai4#ekBM&vLBd$f&%s|3{BsH9 z@p2kgdjHed;&QrU)7plfb`tp9ESe$p#&$Pw@;oP5f+l7|u{#xD2Umc8!(W4=qH#6< z@{7OBV!6PLYwBqD(?+9|MzjGNBKKDHfzJ|etVsZu_2mMQ@TfYQz#?(UPprPN_xkxd#4PPl8-%b) zs>wVCYbz>!r;J`Nzk?m*&^7$|HoxDpHT?b3AO3T+gX5dC*6=4vBTAuKpT9w zE8=3ET;%Ur7Js#+?HfSV;EL=#wjk#YgW<;B^?>-1An8bqE_v}#N6&R_dNc5?UM!Co z%^vt5nU8bb z;&@5DJ9ytg*YHQ1%DbX*4WId=&u}_F!wnn4S{y-Zg)$ng6~=~qJP_1^3&?v_xj#kb zB@e)wB;j#|kLzawMoH{9r=XSQ+5P8HO3XygKUpyxbqFqaVede|WfE1Ss<`**MoeTKgz`d8GvcKilk_}=d^63yeAvmmW% zK^YYde~PegR^b2bdKwoI_$fsG7U5Mt_ix>M-hoKaj-NObnw#TiC<$AL4fKG49#E*V zRROly12zW8X2!ua$vho*)$jx=FC)EWGP`Cdv(~-tz(pOO6s457dl&f==T9Nnv6D(d zp1>XKAct*+zlJ5wSw8azpGG;)>5Vy72*y68DLRI~maVytgq&PoSC9e{(u=#u>%!7l zg7|Mj5)JP_?h2*XeC)-aL_|@RC8J_Up#m{TO2Adxc9wp|yPmqB-zL|EHts)pMOfR0 zBg7kI54I{mLh!uuT-?vYDD!k+T?*wA@!44echC>r9Dm91=Lu`+fB(CGz;~W}gHj16 zqhNm5S|LUuYlc5rH=z@__=R=$PsxN<(>aL$xme)w_?bgB!u82@UOM;y8bzrF4E2bC z9-u`>xnu+N$?a}{(jA_fKbDwqUtmC&Agsmx%G$f6lU4;-FV+LOe3CEeRjgcwT?)0e z!X513qT#P<5x=IQW>zowS6}-KBTDYyj1hlvL)k)NQqQ>5SLJ-VPwc;xb2HgU7y}dOo?X$xroXaJJoDk&C zx9P$B#e2kj06zLOb4lRR7~pP$n-G0R0Cuni{Mx@{_^VsP->?4BuW+_F!VMjD83Ju` z!=I8BXU*68t?AQGA^x|>pBesCEz$KCbzTEVF#X9Ri?W8>d(ZNhZvH4nX@s0=mL$9+nT=!8ea2>zo z4&L9;<^H+huc6`I={YkgP3q zTD6~VJsxZTNdkaN#iOmA>N{^Tg0x!*w1WtFhTpE?&k@$p|I(K}h4zkd_ZU4ddpBDP4H$9~-_@UcB!tLR+6v{9jj2RCm zf%~hR`#;^}-?(TlZLpi$+B!CbwpgD#%{TW*hQEca`IWE!5(5ezUk{?cQ3@3;{-zLcx<#!d`X>oC zsR8X=R#EhSin;CGYTbX}{NQzBVjlH63@G@qS3bc&hLpNsQcRhadkl*aMa%u=a_*lQ z0b3%}C01SLntR{UySZ2{{iS9z?_IAApnd7ShBraqJM~Eb7aG{X`yJLr|CHJ@Z0_Nm zrv06)}*9 z9$g!54sP+$>pzB8ieWioT1MSp2AG!nKP5+voxflMNE?DSTukeuvvm?t z;GZk51dNb}%WRaC`=soIREpK^!6tj(Tn*@eO4!+T->*T_mR`X&YT6+JdA&Oo;Qb4I zTYI%jePf2-&wTlp(AIIjzofy3wf!a-{fsgw6|8z;zDm&f)rK%ycXfd_MGht%*+-Y3 z$G1y<9UghZGmw&3u78AQN6%xFW>k)ulzWWLm_o|d?|)_PpCQFoa*-qdCNEu|1kjDk zSvLa8DpTlXux&JR>az)UF#+#u=rjB|Y-{-YZtJB)qfO@g zR{fL7smzU+ie9yUZ9xyUgnD$e{NrKfkYiz@M||StpFQ_EZ zsT6NtJHvZWN`l@mLZKDnLdfqbW_Bg^CzYUe1199Zq7;x84uxZn(}6VQaW}jULkd26 z=cgE{F@-ja&6uePhQCsk6cq2s?RP2kA@(NN!i3vp0jnVdALXaMy~);KlP=YL&a9X7 zd9j@#@O=rn;m`IM{(j@@pXJHoA=<#aY!DT=f1yL74@rJ3{6_8%7w7O*qW?nwR{eB3 z2K(r=lTV2FD=Nc{!A(AP>*FiK-(ZhPIbkTnHu1;P2K`)sZMig_cH`a-^u_Bz9ajQo zlyg(Z)_(gSHw3Pg^E(UR`xGu__^t3U&tF5sgZUFa_ubDj#BldUH2jG|nXuAN5q9Iv zDr})|MQmTWOFEdMe1Euq4!L`; z3o0vlC-_%s&C3S1~({03G zF#N>~|D8;hYa%*aNOuz~_m}lCJJt^CQ_xQ!1?nCQe94Q~KE$2T%fawhj2V{`_RN$* zN=#(U?}qzdSvcPGKAW!R?uE5A(wP^a?Zg(B!Bas(cD+;k-_Ou*_*-r5^(SwCgFk)i z%M38w+q<~6mySz)@!+HNP+G}O_v#w!b zYkuvIe;Ff+hc`}x5l;!C6+@*EW#ZOeaxsK0gA@eh)w)c*gvN0~9Y53KVy%;7+mN?xi>ccZx%RkX(NE{qFa?=N~v{&fd@LHEXSzjYO(( zhLtD=o1i5^v)?b)1g^a%86bdM6Kl+^qxTi)>~#vlRh2)e&V*>8hNSokzx0os2i5MW zgzypy`UG+-`$wAT7v7<}tS@uf!k+Tn5o83K(0&RLexIDp1xM112ev-?OhHjecbg|( z-g?1@sVZQsTn8AJ{0onCPf5NZ#`_+tfZ-wcr#IQ3qahJno&0d0g`4lW)bZb6!*9^H zWw}tUsZdr#!fXROj@n&d?5-@cR4xq?CmWb}4=uO9R84|t-@ky*@l>;1eW7_57_P(3ni;`s5vu9;u zKW`ruyas>d-SqjLo58l*Ir8N}@$JCg>I?kBI|$1ZG(hjn-A&EaNDDBN)Lb^7Ry zDAVH>K&Z-ix$TX=je*}eD&UwhGzSz7ahHOTJ#(?@xcwer_h^4@oonG2rlCwg^XWG5 zmYPGag$yFUl+Jl~8_59}{tzR~NHI7ed8ERxi!S}NHHFH@*K;n+A9R+tSwnF7O9&lR z!qv-6ZnhkItP_y!Q=q1X6KG@$eV@oZtO@j|ml5uBm+!QPIp^*!AYjPy^XKQyISqHL zZW`wSf@5ZgRS8+x*Y5Ma@GYQz;_^(NjrTjPKfOSF5PkWz8Ab7Z(Z2L#z9^P@$Qb`S z;y=Ni3(?XCLy5iyQ+QJ@YQ3r5*QV;zZ75&i!DT>GGLpH@vp@|@aZO~`r-^7tZTr+& z=&@hGBTTSSH#aS7a|&%IqczlAf}Px6kKPRy90Jw~TV}f}@cWs8ZvgwdOcCcB`FhOK zO@soiufV!#&hOWqb#c<&@Og&3#rqZg$(vo_#jqM6m$2pc^?8!51|Kq4RC3Ox*{`cV zQv7~&X5KSC|7Yx{Xo#INOuSn2{&|1n$lLjE?|EYVBbDVUO0b4}$#HrhX_>9umYrQ= zO_f-|?KZ%F{C2)iGa1x@4{7o`Z!7LUzsNvxH&S4mavu8nF(0&R;!CZ zKe4CKrkxw=F#FL1^-j8i&&;sgaw*?SwD?3OHM%e~2B(r_TDXiHWRutR^!-(Z#Xau( z&h9VNAA%Q!2PwSYz@5-yaGVE=;m}OYqsPWB79Ld1_B$5pFlXfPp@kca%XCLe_GH2T zTApGtVxQpO`RH{YW8ax&zpaR!u0|I)8bWi)_dw$(1|b|`&4^eH41f$UPwySN)RbZ| zI=)VTX_Xvi7Y9aQ_YnUMCa2Ga-qC<*>92h_&wuHjLt19{Q%4NnY{AD#} zzsL-ce@NNv4zMFb9X`imfrellr^#p3;}GM_N1C{q%hc7yMl)GMhK}@#@*5&KWf? z!7qr_v+F?87Ml}PuDYW?%zEFvSiNG;hWP$SO_AbP5$DuovQ)!rd%wA=Gl~27DMTvt zkr=D@2aN!&M%FT)jtr{J@fj6JIgLF+5Dz;>Orj?@~`Xr4vrb-a)6S-$y z@b3xJ9iG=XgyGU7Dp-+M0SM0GP^&7D(**<{7yaGuGv+SD34Gip`n!!(J-&m z!NC!=_l^Ke$%>=)o{z(Rd`>9UI^_oT2zUz~*i$N));y2qp|}73rvQcn$w$!0&rsIU%aC@D%|zdl%c>_rPah(j3ekXrH(GRoY&Wp0 zCItP9bIj+-6kVXyPMw}e{vvq~TljE6h(=)x$c_jqC+5_8{zB$uy54$P{7j-Trr7Pg7iG*Vdvf#TT?&QN{ zJ^-lXO-HiV4ETfevbDySmr@nd8 zw&yv1@ToPBdtiTkL%e-g9={_TJb@QGFJoVgy~!ODDYr+~jryQ69olWt_re(u{Lli9 zX!dxT#>c}a6+qw|et&f2BD_CYt<^+ZM_<$fG{!x6A(u<(G1Xw1;G>As>HWf#?3bF7 z&(tJZ)Ld7ed((j46uaWLOeE@G#8)A`BX5%L$Cp&ICTMES5k>AOGTmgs9bN~7R-a&F z`H`x4x=iP_jJJzmv1%ja3CaU7ArY4>MsMyesLvJwgZHf3A6*j($edhGF=ow*|%t+#oN1P^zpTR{0RPEbVY|V$lz9 zD}P5*Hz$nfW`dGwRPGTIfWzhV=wN@|*?&0hLq)W`6!m9hde<9r1d3=ZICr1eN-vcw zTy)qiH<;&_zHR@5N(5ZMEr6l6wT$>6K6hK>g+bh(;8TkxrhN%iZdjjQym-%!L(3q| zN?83D@f27u_y>~lkIETogHwy<(u}xFkS?(vBZ$H4F6hK9Dc@O~mzVH55G}YMR}T92 z&imlvE*upsiruysfVLS#=BxMP{(6J)TAIT=tCL=PmY=5WYc$;VZ_x8TOYkp*NoUV3 zAVkj>raeOUX~0i=ckEGnH$UC48NVr@Z)iC~p%&QT30_Zb^Ou|!20F}xdOlgMvpNp= zp9{Dg^9*c(F%?B^KPX2crlc;#jRv7EmW>NBrB6?XbU%!53vmQp9UAVQ)cF~4JSx=M zcs<9%?9OE_z}M_&=PQ_qSAvPIuq5dx8?9=&l2@_BUm&;)6S3u3Lx2aPF2=*nY5(&y z4V}0#f%y&N2~;$*-{H zb^g5?A93y}P6ButD_LYW;U(njaU| z2*vHAb{ORC8_W7Wgow<;Az{h#4exw>_yh6#hf|b1v!sz+4<7J;kLm-{R%4k{(RLKag-nZ^>0cn9+o^jSEsV&B1;-X6QjlCO?CD`Jv`H^xHAySL)9di=*|-RHe!6Wk2*{??Fi) zDR({8UW|?9VNhQw^wvd>BO9e$??PU|<0!!!Z^0;;$uegit?FMcig(Tr(5C1DyJ$~I zo4~R6;2+%<1;v(Q{w#J2G3Rz}q@|oP0>Y;DIwFqWL-33MFKnd;GtX%7@x|B;o#(JB z4TL3SFwaDfgdctwS)ragIJVaMY8jN)MJ)ofV;(Wga(*7oGcpa;4Jg@S?Jgv0GA+JY097V(Z?c8p91PH8b zu^t^`V1i7ZH$`dr!n&hO{8;8GS=gQutsInv27h{rD!4k@EqI4B(LO*ES8!zVA&LxG zh4$-0s%We&IJ@wMy9!imL8Pq#KLm_PUM|zKFloSiS|!y3%wCfbOL^^amCl&&CS>J@ z)Y66$s8Jq4zDhE=$AzclvFq6!c|2I`$=R;#vOIbsSfb>g>OlN@t8Ig49Z8`vG=xA* z)rCMAKU!BH!upwwmT6RgU1jf)~Iv$3J61Plv3HtliV6C^C-$ ztBW&sFUS#Rz)N1>Ik`4QJSd`WP9kIvebBmIX!%$eA1euyKNE3eaVQsgST6D zD@oIn70y>^)p_+XrI(hvSke+%fc>&GqAXc6ZJgwHd*v@oZ<*0>>DK^w2$CGSHMp+G zPGhh$iwf4%saefFA7}dJ-?7{Xm+!0j%{Yykb zwAU*}dIbnxa;$-hoC_=Htn7#7yKsl8{4RWDJA8?UC;gyi*#C3X3haq=W^$iEGg7Z{ zX_AyFYD+ng`Hku~(-|t$*SNmv7X=DIP*L&l)SPcGbPdEtSZLL3o;!k#IHhOB0pMaH zSL2F*S*4AKhhK%>1H_gdS}y#Af~k>xU#AZSBf1JHK@(RF!1M2NJR`HyT0#JEpTO1blwwQXG>

    }FfAM5b;0=tuz^I)N!= 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 WI~wbg%^2YMk(XBaP$_8=@_zsf))uY+ literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_32.png b/docs/assets/cgi/icon_square/icon_square_32.png new file mode 100755 index 0000000000000000000000000000000000000000..24e9a609718148873ecd925265a16f3a8e589541 GIT binary patch literal 2011 zcmV<12PF83P)~n;^kfunj1PHeQAP1=ENjm9?}gn zFFYnZv643&-V|=pQ`(a7v2dp;2Nb?`^q9*}4kumyaM)C%5%4w%E6HCR`^9?7%m~ZP z31-p-{pUQ+P3q&>nQ8HDG6Gmc86}LuIKxB;(aWIN5MA`qEi)>__k71sk&{?mtU(cz zWs;=G($W-b4bPkDy@=nlI%uEVwoMS?BN; z)Kz(^3)tR)4%y$h_yq)TXSuOh7p(vQ010qNS#tmYE+YT{E+YYWr9XB600sX^L_t(o zg{7DKi(OY0$3JWBeeQFTiJ2swOeSfklY~$$1rt%xv>0g<+X|&vEJdXj!GA$Tp(;Xu z_NP`6LCFtVwS|xrX$46zgcfSGN==g(GtC$?H0@-1XD0XFv-k3cv+tdA=ha8n;oQ5= zK6|gncfIzJ*7>jh&N@>E3AwTkg=ow^J?H%ECO7gfL)98x%b7oPh34(^cmXh^xj8oX z?2a+*D>IFy=ZKGPXxFpy-m7;b%?c=v z#=g5S=ho%br>rrpNG=4x1XKYj#%OVCajPiVX2;h1M_33Qa*MRO9XfN7@YfN#0r=P$ zCRu;a01uWw3*@GrARq~V97H`@+liPK^F2~<0m7GzvFU%)nVqB6U1X%Wk?Yr$d{Nmj zac~LuvJWC)MrdEcETne=3cieph^M~qXK3YCP`}KB$rUlePoV*9RwaZ$HAb9ssA}?W zBoHu+Kmfqw$T}Cf@%pz>Ddqw_1#&=)Y~v2dwy*29o{%0n=Wxq>9th;*oP761-kdqh z#PA-zIC?MMkE4xS2n*8%x)Hofnm8Z?@G%q*ACY>(kDmQG&vk!Kz~F7AQgs~M_<5wq z-XK`Q_$K77ph)22jel{x^E?ql5WqM!eTqO{xFs))h2>6;D;a(mDy$D2J@*T`(m@eG zune}=w;@R#0B1b~a*7zZN#QYt-k;t%&OhWeK#56Qy;J9bonOY`z!KsCddXf;esit} z1Que89=*V9f6zldLZ*X}wCGeMI3oIlukSFZrKdNrDRnw&!C!cI(%0 z>Iw&x3$7&>$SO=f3`HVW!W1W4F93=lL=3}tWa2R@)NnWy9dNY@MGvt6Qm$S=#L{mO z$+`wuJ?o!6{|s&IP&Q}3JHR~~4&l`=&D!9w94Z#tO%lImPZ1wTz`qb zcVDImJO~IC^?YOUTex&-8T$a@AmrdhSn89JR6+yi=l;waS6^Xb+X40te*uT1LS;aS zK%mVcFT8a$F_1obM~3ca-0#9^fKAa{PEH=EHaO#ATnToah9{Klf1d`7cvA%iaR`y?0ljWcxzanWJ*e^sLLkq zoqURs%3eebkC6d&gxUO`96R@YPEY+B4|o(WLcq9h(|z1l-CYX2RVXk}{1tprF92prEZE#@XN%l1Fp{hm= zqnYkubsQP}Wj&_C&(PA?yV9gWQ6HC)qNxlkyLWtt@y&;MZ|)S0q5U+QpF@xjCLAalf#A}a&;x`B zPK7Y{UwmfMr8)-}jMvve%)Z;K#w$xlCltKGh` zMGh!4NXGJ3Q(D+>bVgXXh=hPs>C5z(6-Of#BH;1>t#gFb2PiRj<*~3LXnV)|KK0f} z0eA0REG^b9VTn!N+a&{xT#0diyvn!iiYO>Gxuthf01^#M&AZ`!&xdy|eU^YCLhNl? tEi2ag)?Pk0_#M7-pBQ+Hx@q*e{{su0PQrUKeBb~8002ovPDHLkV1oC?q&olr literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_512.png b/docs/assets/cgi/icon_square/icon_square_512.png new file mode 100755 index 0000000000000000000000000000000000000000..da117347a9b34cbe8a66856f79f58a9d1608df31 GIT binary patch literal 155209 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 XT#Dp2m>_e600000NkvXXu0mjfT8UJA literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/icon_square/icon_square_64.png b/docs/assets/cgi/icon_square/icon_square_64.png new file mode 100755 index 0000000000000000000000000000000000000000..d9d63e823d5e6a1f681e7d511ad0c202e685f293 GIT binary patch literal 5036 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 z%0A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

      jo|cDkI|#lCS%UW>kPdn(ILo+}Rc_4I6P-L}Tq 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�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%0Aa&Io1@(a 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 literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/logo/logo_256.png b/docs/assets/cgi/logo/logo_256.png new file mode 100755 index 0000000000000000000000000000000000000000..a01735a0d28ad29f2847887819497ca515b7e449 GIT binary patch literal 68818 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

      UVi8leNyWpE`}8y9>N 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

      L6y0K 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 z�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 bdg| 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 ASpWb4 literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/logo/logo_64.png b/docs/assets/cgi/logo/logo_64.png new file mode 100755 index 0000000000000000000000000000000000000000..47e028add9cabb7252469ab8c1b6db7a73a49240 GIT binary patch literal 7359 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 literal 0 HcmV?d00001 From 5ae828b1ebedfc8ac7e6428a77c2d676888f259a Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 00:09:54 +1000 Subject: [PATCH 230/548] Implement working set methods --- taskchampion/src/storage/sqlite.rs | 120 +++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 5 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index d5bcc3dd9..fac09e817 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -25,9 +25,10 @@ impl SqliteStorage { let con = Connection::open(db_file)?; let queries = vec![ - "CREATE TABLE IF NOT EXISTS tasks (uuid STRING PRIMARY KEY, data STRING);", "CREATE TABLE IF NOT EXISTS operations (id INTEGER PRIMARY KEY AUTOINCREMENT, data STRING);", "CREATE TABLE IF NOT EXISTS sync_meta (key STRING PRIMARY KEY, value STRING);", + "CREATE TABLE IF NOT EXISTS tasks (uuid STRING PRIMARY KEY, data STRING);", + "CREATE TABLE IF NOT EXISTS working_set (id INTEGER PRIMARY KEY, uuid STRING);", ]; for q in queries { con.execute(q, []).context("Creating table")?; @@ -47,6 +48,18 @@ impl<'t> Txn<'t> { .as_ref() .ok_or(SqliteError::TransactionAlreadyCommitted) } + + fn get_next_working_set_number(&self) -> anyhow::Result { + let t = self.get_txn()?; + let result: Option = t + .query_row("SELECT COALESCE(MAX(id), 0) FROM working_set", [], |r| { + r.get(0) + }) + .optional() + .context("Getting highest working set ID")?; + + Ok(result.unwrap_or(0) + 1) + } } impl Storage for SqliteStorage { @@ -214,19 +227,64 @@ impl<'t> StorageTxn for Txn<'t> { } fn get_working_set(&mut self) -> anyhow::Result>> { - todo!() + let t = self.get_txn()?; + + let mut q = t.prepare("SELECT id, uuid FROM working_set ORDER BY id ASC")?; + let rows = q + .query_map([], |r| { + let id: usize = r.get("id")?; + let uuid: Uuid = r.get("uuid")?; + Ok((id, uuid)) + }) + .context("Get working set query")?; + + let rows: Vec> = rows.collect(); + let mut res = Vec::with_capacity(rows.len()); + for _ in 0..self.get_next_working_set_number().context("HUh")? { + res.push(None); + } + for r in rows { + let (id, uuid) = r?; + res[id as usize] = Some(uuid); + } + + Ok(res) } fn add_to_working_set(&mut self, uuid: Uuid) -> anyhow::Result { - todo!() + let t = self.get_txn()?; + + let next_working_id = self.get_next_working_set_number()?; + + t.execute( + "INSERT INTO working_set (id, uuid) VALUES (?, ?)", + params![next_working_id, &uuid], + ) + .context("Create task query")?; + + Ok(next_working_id) } fn set_working_set_item(&mut self, index: usize, uuid: Option) -> anyhow::Result<()> { - todo!() + let t = self.get_txn()?; + match uuid { + // Add or override item + Some(uuid) => t.execute( + "INSERT OR REPLACE INTO working_set (id, uuid) VALUES (?, ?)", + params![index, &uuid], + ), + // Setting to None removes the row from database + None => t.execute("DELETE FROM working_set WHERE id = ?", [index]), + } + .context("Set working set item query")?; + Ok(()) } fn clear_working_set(&mut self) -> anyhow::Result<()> { - todo!() + let t = self.get_txn()?; + t.execute("DELETE FROM working_set", []) + .context("Clear working set query")?; + Ok(()) } fn commit(&mut self) -> anyhow::Result<()> { @@ -569,4 +627,56 @@ mod test { Ok(()) } + + #[test] + fn set_working_set_item() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1)?; + txn.add_to_working_set(uuid2)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, Some(uuid1), Some(uuid2)]); + } + + // Clear one item + dbg!(1); + { + let mut txn = storage.txn()?; + let ws = txn.set_working_set_item(1, None)?; + txn.commit()?; + } + + dbg!(2); + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, None, Some(uuid2)]); + } + + dbg!(3); + // Override item + { + let mut txn = storage.txn()?; + let ws = txn.set_working_set_item(2, Some(uuid1))?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, None, Some(uuid1)]); + } + + Ok(()) + } } From cf70ef49ede09ecbac06d9928e83dd4bd20f3df4 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 00:10:53 +1000 Subject: [PATCH 231/548] Minor tidying --- taskchampion/src/storage/sqlite.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index fac09e817..4f57ed3ab 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -100,7 +100,7 @@ impl<'t> StorageTxn for Txn<'t> { let data = TaskMap::default(); let data_str = serde_json::to_string(&data)?; t.execute( - "INSERT INTO TASKS (uuid, data) VALUES (?, ?)", + "INSERT INTO tasks (uuid, data) VALUES (?, ?)", params![&uuid, &data_str], ) .context("Create task query")?; @@ -214,11 +214,8 @@ impl<'t> StorageTxn for Txn<'t> { fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { let t = self.get_txn()?; - t.execute( - "DELETE FROM operations", - [], - ) - .context("Clear all existing operations")?; + t.execute("DELETE FROM operations", []) + .context("Clear all existing operations")?; for o in ops { self.add_operation(o)?; From e06e33bee499875c276689995a804da013179efe Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 00:20:13 +1000 Subject: [PATCH 232/548] Remove kv storage backend Now uses sqlite by default --- Cargo.lock | 30 +- taskchampion/Cargo.toml | 1 - taskchampion/src/storage/config.rs | 4 +- taskchampion/src/storage/kv.rs | 683 ----------------------------- taskchampion/src/storage/mod.rs | 3 +- 5 files changed, 7 insertions(+), 714 deletions(-) delete mode 100644 taskchampion/src/storage/kv.rs diff --git a/Cargo.lock b/Cargo.lock index bdd220039..8777c807b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1167,7 +1167,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" dependencies = [ - "lmdb-rkv 0.12.3", + "lmdb-rkv", "rmp-serde", "serde", "thiserror", @@ -1231,19 +1231,7 @@ dependencies = [ "bitflags", "byteorder", "libc", - "lmdb-rkv-sys 0.9.6", -] - -[[package]] -name = "lmdb-rkv" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447a296f7aca299cfbb50f4e4f3d49451549af655fb7215d7f8c0c3d64bad42b" -dependencies = [ - "bitflags", - "byteorder", - "libc", - "lmdb-rkv-sys 0.11.0", + "lmdb-rkv-sys", ] [[package]] @@ -1257,17 +1245,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "lmdb-rkv-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b27470ac25167b3afdfb6af8fcd3bc1be67de50ffbdaf4073378cfded6ae24a5" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "lock_api" version = "0.4.3" @@ -1921,7 +1898,9 @@ dependencies = [ "hashlink", "libsqlite3-sys", "memchr", + "serde_json", "smallvec", + "uuid", ] [[package]] @@ -2207,7 +2186,6 @@ dependencies = [ "anyhow", "chrono", "kv", - "lmdb-rkv 0.14.0", "log", "proptest", "rusqlite", diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index e5a1ab801..196c2a7d8 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -18,7 +18,6 @@ chrono = { version = "^0.4.10", features = ["serde"] } anyhow = "1.0" thiserror = "1.0" kv = {version = "^0.10.0", features = ["msgpack-value"]} -lmdb-rkv = {version = "^0.14.0"} ureq = "^2.1.0" log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } diff --git a/taskchampion/src/storage/config.rs b/taskchampion/src/storage/config.rs index 773baa035..a802e4a09 100644 --- a/taskchampion/src/storage/config.rs +++ b/taskchampion/src/storage/config.rs @@ -1,4 +1,4 @@ -use super::{InMemoryStorage, KvStorage, Storage}; +use super::{InMemoryStorage, SqliteStorage, Storage}; use std::path::PathBuf; /// The configuration required for a replica's storage. @@ -15,7 +15,7 @@ pub enum StorageConfig { impl StorageConfig { pub fn into_storage(self) -> anyhow::Result> { Ok(match self { - StorageConfig::OnDisk { taskdb_dir } => Box::new(KvStorage::new(taskdb_dir)?), + StorageConfig::OnDisk { taskdb_dir } => Box::new(SqliteStorage::new(taskdb_dir)?), StorageConfig::InMemory => Box::new(InMemoryStorage::new()), }) } diff --git a/taskchampion/src/storage/kv.rs b/taskchampion/src/storage/kv.rs deleted file mode 100644 index e65e31dfa..000000000 --- a/taskchampion/src/storage/kv.rs +++ /dev/null @@ -1,683 +0,0 @@ -use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; -use crate::utils::Key; -use kv::msgpack::Msgpack; -use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; -use std::path::Path; -use uuid::Uuid; - -/// KvStorage is an on-disk storage backend which uses LMDB via the `kv` crate. -pub struct KvStorage<'t> { - store: Store, - tasks_bucket: Bucket<'t, Key, ValueBuf>>, - numbers_bucket: Bucket<'t, Integer, ValueBuf>>, - uuids_bucket: Bucket<'t, Integer, ValueBuf>>, - operations_bucket: Bucket<'t, Integer, ValueBuf>>, - working_set_bucket: Bucket<'t, Integer, ValueBuf>>, -} - -const BASE_VERSION: u64 = 1; -const NEXT_OPERATION: u64 = 2; -const NEXT_WORKING_SET_INDEX: u64 = 3; - -impl<'t> KvStorage<'t> { - pub fn new>(directory: P) -> anyhow::Result> { - let mut config = Config::default(directory); - config.bucket("tasks", None); - config.bucket("numbers", None); - config.bucket("uuids", None); - config.bucket("operations", None); - config.bucket("working_set", None); - let store = Store::new(config)?; - - // tasks are stored indexed by uuid - let tasks_bucket = store.bucket::>>(Some("tasks"))?; - - // this bucket contains various u64s, indexed by constants above - let numbers_bucket = store.int_bucket::>>(Some("numbers"))?; - - // this bucket contains various Uuids, indexed by constants above - let uuids_bucket = store.int_bucket::>>(Some("uuids"))?; - - // this bucket contains operations, numbered consecutively; the NEXT_OPERATION number gives - // the index of the next operation to insert - let operations_bucket = - store.int_bucket::>>(Some("operations"))?; - - // this bucket contains operations, numbered consecutively; the NEXT_WORKING_SET_INDEX - // number gives the index of the next operation to insert - let working_set_bucket = - store.int_bucket::>>(Some("working_set"))?; - - Ok(KvStorage { - store, - tasks_bucket, - numbers_bucket, - uuids_bucket, - operations_bucket, - working_set_bucket, - }) - } -} - -impl<'t> Storage for KvStorage<'t> { - fn txn<'a>(&'a mut self) -> anyhow::Result> { - Ok(Box::new(Txn { - storage: self, - txn: Some(self.store.write_txn()?), - })) - } -} - -struct Txn<'t> { - storage: &'t KvStorage<'t>, - txn: Option>, -} - -impl<'t> Txn<'t> { - // get the underlying kv Txn - fn kvtxn(&mut self) -> &mut kv::Txn<'t> { - if let Some(ref mut txn) = self.txn { - txn - } else { - panic!("cannot use transaction after commit"); - } - } - - // Access to buckets - fn tasks_bucket(&self) -> &'t Bucket<'t, Key, ValueBuf>> { - &self.storage.tasks_bucket - } - fn numbers_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { - &self.storage.numbers_bucket - } - fn uuids_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { - &self.storage.uuids_bucket - } - fn operations_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { - &self.storage.operations_bucket - } - fn working_set_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { - &self.storage.working_set_bucket - } -} - -impl<'t> StorageTxn for Txn<'t> { - fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { - let bucket = self.tasks_bucket(); - let buf = match self.kvtxn().get(bucket, uuid.into()) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(None), - Err(e) => return Err(e.into()), - }; - let value = buf.inner()?.to_serde(); - Ok(Some(value)) - } - - fn create_task(&mut self, uuid: Uuid) -> anyhow::Result { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - match kvtxn.get(bucket, uuid.into()) { - Err(Error::NotFound) => { - kvtxn.set(bucket, uuid.into(), Msgpack::to_value_buf(TaskMap::new())?)?; - Ok(true) - } - Err(e) => Err(e.into()), - Ok(_) => Ok(false), - } - } - - fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> anyhow::Result<()> { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - kvtxn.set(bucket, uuid.into(), Msgpack::to_value_buf(task)?)?; - Ok(()) - } - - fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - match kvtxn.del(bucket, uuid.into()) { - Err(Error::NotFound) => Ok(false), - Err(e) => Err(e.into()), - Ok(_) => Ok(true), - } - } - - fn all_tasks(&mut self) -> anyhow::Result> { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - let all_tasks: Result, Error> = kvtxn - .read_cursor(bucket)? - .iter() - .map(|(k, v)| Ok((k.into(), v.inner()?.to_serde()))) - .collect(); - Ok(all_tasks?) - } - - fn all_task_uuids(&mut self) -> anyhow::Result> { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - Ok(kvtxn - .read_cursor(bucket)? - .iter() - .map(|(k, _)| k.into()) - .collect()) - } - - fn base_version(&mut self) -> anyhow::Result { - let bucket = self.uuids_bucket(); - let base_version = match self.kvtxn().get(bucket, BASE_VERSION.into()) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(DEFAULT_BASE_VERSION), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(base_version as VersionId) - } - - fn set_base_version(&mut self, version: VersionId) -> anyhow::Result<()> { - let uuids_bucket = self.uuids_bucket(); - let kvtxn = self.kvtxn(); - - kvtxn.set( - uuids_bucket, - BASE_VERSION.into(), - Msgpack::to_value_buf(version as Uuid)?, - )?; - Ok(()) - } - - fn operations(&mut self) -> anyhow::Result> { - let bucket = self.operations_bucket(); - let kvtxn = self.kvtxn(); - let all_ops: Result, Error> = kvtxn - .read_cursor(bucket)? - .iter() - .map(|(i, v)| Ok((i.into(), v.inner()?.to_serde()))) - .collect(); - let mut all_ops = all_ops?; - // sort by key.. - all_ops.sort_by(|a, b| a.0.cmp(&b.0)); - // and return the values.. - Ok(all_ops.iter().map(|(_, v)| v.clone()).collect()) - } - - fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { - let numbers_bucket = self.numbers_bucket(); - let operations_bucket = self.operations_bucket(); - let kvtxn = self.kvtxn(); - - let next_op = match kvtxn.get(numbers_bucket, NEXT_OPERATION.into()) { - Ok(buf) => buf.inner()?.to_serde(), - Err(Error::NotFound) => 0, - Err(e) => return Err(e.into()), - }; - - kvtxn.set( - operations_bucket, - next_op.into(), - Msgpack::to_value_buf(op)?, - )?; - kvtxn.set( - numbers_bucket, - NEXT_OPERATION.into(), - Msgpack::to_value_buf(next_op + 1)?, - )?; - Ok(()) - } - - fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { - let numbers_bucket = self.numbers_bucket(); - let operations_bucket = self.operations_bucket(); - let kvtxn = self.kvtxn(); - - kvtxn.clear_db(operations_bucket)?; - - let mut i = 0u64; - for op in ops { - kvtxn.set(operations_bucket, i.into(), Msgpack::to_value_buf(op)?)?; - i += 1; - } - - kvtxn.set( - numbers_bucket, - NEXT_OPERATION.into(), - Msgpack::to_value_buf(i)?, - )?; - - Ok(()) - } - - fn get_working_set(&mut self) -> anyhow::Result>> { - let working_set_bucket = self.working_set_bucket(); - let numbers_bucket = self.numbers_bucket(); - let kvtxn = self.kvtxn(); - - let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { - Ok(buf) => buf.inner()?.to_serde(), - Err(Error::NotFound) => 1, - Err(e) => return Err(e.into()), - }; - - let mut res = Vec::with_capacity(next_index as usize); - for _ in 0..next_index { - res.push(None) - } - - for (i, u) in kvtxn.read_cursor(working_set_bucket)?.iter() { - let i: u64 = i.into(); - res[i as usize] = Some(u.inner()?.to_serde()); - } - Ok(res) - } - - fn add_to_working_set(&mut self, uuid: Uuid) -> anyhow::Result { - let working_set_bucket = self.working_set_bucket(); - let numbers_bucket = self.numbers_bucket(); - let kvtxn = self.kvtxn(); - - let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { - Ok(buf) => buf.inner()?.to_serde(), - Err(Error::NotFound) => 1, - Err(e) => return Err(e.into()), - }; - - kvtxn.set( - working_set_bucket, - next_index.into(), - Msgpack::to_value_buf(uuid)?, - )?; - kvtxn.set( - numbers_bucket, - NEXT_WORKING_SET_INDEX.into(), - Msgpack::to_value_buf(next_index + 1)?, - )?; - Ok(next_index as usize) - } - - fn set_working_set_item(&mut self, index: usize, uuid: Option) -> anyhow::Result<()> { - let working_set_bucket = self.working_set_bucket(); - let numbers_bucket = self.numbers_bucket(); - let kvtxn = self.kvtxn(); - let index = index as u64; - - let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { - Ok(buf) => buf.inner()?.to_serde(), - Err(Error::NotFound) => 1, - Err(e) => return Err(e.into()), - }; - - if index >= next_index { - anyhow::bail!("Index {} is not in the working set", index); - } - - if let Some(uuid) = uuid { - kvtxn.set( - working_set_bucket, - index.into(), - Msgpack::to_value_buf(uuid)?, - )?; - } else { - kvtxn.del(working_set_bucket, index.into())?; - } - - Ok(()) - } - - fn clear_working_set(&mut self) -> anyhow::Result<()> { - let working_set_bucket = self.working_set_bucket(); - let numbers_bucket = self.numbers_bucket(); - let kvtxn = self.kvtxn(); - - kvtxn.clear_db(working_set_bucket)?; - kvtxn.set( - numbers_bucket, - NEXT_WORKING_SET_INDEX.into(), - Msgpack::to_value_buf(1)?, - )?; - - Ok(()) - } - - fn commit(&mut self) -> anyhow::Result<()> { - if let Some(kvtxn) = self.txn.take() { - kvtxn.commit()?; - } else { - panic!("transaction already committed"); - } - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::storage::taskmap_with; - use tempfile::TempDir; - - #[test] - fn test_create() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid)?); - txn.commit()?; - } - { - let mut txn = storage.txn()?; - let task = txn.get_task(uuid)?; - assert_eq!(task, Some(taskmap_with(vec![]))); - } - Ok(()) - } - - #[test] - fn test_create_exists() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid)?); - txn.commit()?; - } - { - let mut txn = storage.txn()?; - assert!(!txn.create_task(uuid)?); - txn.commit()?; - } - Ok(()) - } - - #[test] - fn test_get_missing() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - let task = txn.get_task(uuid)?; - assert_eq!(task, None); - } - Ok(()) - } - - #[test] - fn test_set_task() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - txn.set_task(uuid, taskmap_with(vec![("k".to_string(), "v".to_string())]))?; - txn.commit()?; - } - { - let mut txn = storage.txn()?; - let task = txn.get_task(uuid)?; - assert_eq!( - task, - Some(taskmap_with(vec![("k".to_string(), "v".to_string())])) - ); - } - Ok(()) - } - - #[test] - fn test_delete_task_missing() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(!txn.delete_task(uuid)?); - } - Ok(()) - } - - #[test] - fn test_delete_task_exists() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid)?); - txn.commit()?; - } - { - let mut txn = storage.txn()?; - assert!(txn.delete_task(uuid)?); - } - Ok(()) - } - - #[test] - fn test_all_tasks_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - { - let mut txn = storage.txn()?; - let tasks = txn.all_tasks()?; - assert_eq!(tasks, vec![]); - } - Ok(()) - } - - #[test] - fn test_all_tasks_and_uuids() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid1.clone())?); - txn.set_task( - uuid1.clone(), - taskmap_with(vec![("num".to_string(), "1".to_string())]), - )?; - assert!(txn.create_task(uuid2.clone())?); - txn.set_task( - uuid2.clone(), - taskmap_with(vec![("num".to_string(), "2".to_string())]), - )?; - txn.commit()?; - } - { - let mut txn = storage.txn()?; - let mut tasks = txn.all_tasks()?; - - // order is nondeterministic, so sort by uuid - tasks.sort_by(|a, b| a.0.cmp(&b.0)); - - let mut exp = vec![ - ( - uuid1.clone(), - taskmap_with(vec![("num".to_string(), "1".to_string())]), - ), - ( - uuid2.clone(), - taskmap_with(vec![("num".to_string(), "2".to_string())]), - ), - ]; - exp.sort_by(|a, b| a.0.cmp(&b.0)); - - assert_eq!(tasks, exp); - } - { - let mut txn = storage.txn()?; - let mut uuids = txn.all_task_uuids()?; - uuids.sort(); - - let mut exp = vec![uuid1.clone(), uuid2.clone()]; - exp.sort(); - - assert_eq!(uuids, exp); - } - Ok(()) - } - - #[test] - fn test_base_version_default() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - { - let mut txn = storage.txn()?; - assert_eq!(txn.base_version()?, DEFAULT_BASE_VERSION); - } - Ok(()) - } - - #[test] - fn test_base_version_setting() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let u = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - txn.set_base_version(u)?; - txn.commit()?; - } - { - let mut txn = storage.txn()?; - assert_eq!(txn.base_version()?, u); - } - Ok(()) - } - - #[test] - fn test_operations() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - let uuid3 = Uuid::new_v4(); - - // create some operations - { - let mut txn = storage.txn()?; - txn.add_operation(Operation::Create { uuid: uuid1 })?; - txn.add_operation(Operation::Create { uuid: uuid2 })?; - txn.commit()?; - } - - // read them back - { - let mut txn = storage.txn()?; - let ops = txn.operations()?; - assert_eq!( - ops, - vec![ - Operation::Create { uuid: uuid1 }, - Operation::Create { uuid: uuid2 }, - ] - ); - } - - // set them to a different bunch - { - let mut txn = storage.txn()?; - txn.set_operations(vec![ - Operation::Delete { uuid: uuid2 }, - Operation::Delete { uuid: uuid1 }, - ])?; - txn.commit()?; - } - - // create some more operations (to test adding operations after clearing) - { - let mut txn = storage.txn()?; - txn.add_operation(Operation::Create { uuid: uuid3 })?; - txn.add_operation(Operation::Delete { uuid: uuid3 })?; - txn.commit()?; - } - - // read them back - { - let mut txn = storage.txn()?; - let ops = txn.operations()?; - assert_eq!( - ops, - vec![ - Operation::Delete { uuid: uuid2 }, - Operation::Delete { uuid: uuid1 }, - Operation::Create { uuid: uuid3 }, - Operation::Delete { uuid: uuid3 }, - ] - ); - } - Ok(()) - } - - #[test] - fn get_working_set_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None]); - } - - Ok(()) - } - - #[test] - fn add_to_working_set() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1)?; - txn.add_to_working_set(uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, Some(uuid1), Some(uuid2)]); - } - - Ok(()) - } - - #[test] - fn clear_working_set() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1)?; - txn.add_to_working_set(uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - txn.clear_working_set()?; - txn.add_to_working_set(uuid2)?; - txn.add_to_working_set(uuid1)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, Some(uuid2), Some(uuid1)]); - } - - Ok(()) - } -} diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index a4c430b94..7520950e0 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -11,13 +11,12 @@ use uuid::Uuid; mod config; mod inmemory; -mod kv; mod operation; mod sqlite; -pub use self::kv::KvStorage; pub use config::StorageConfig; pub use inmemory::InMemoryStorage; +pub use sqlite::SqliteStorage; pub use operation::Operation; From d5724c4dc2ec9860c07b64363b1972a95cca15d3 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 10:37:04 +1000 Subject: [PATCH 233/548] Unused --- taskchampion/src/storage/sqlite.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 4f57ed3ab..f0eacf740 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -1,8 +1,6 @@ use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; -use crate::utils::Key; use anyhow::Context; use rusqlite::{params, Connection, OptionalExtension}; -use serde::serde_if_integer128; use std::path::Path; use uuid::Uuid; @@ -10,8 +8,6 @@ use uuid::Uuid; enum SqliteError { #[error("SQLite transaction already committted")] TransactionAlreadyCommitted, - #[error("Invalid UUID string from database: {0}")] - InvalidUuidString(String), } /// SqliteStorage is an on-disk storage backed by SQLite3. From cefd6fd6cc75a0a81729639fbe9678664bc95014 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 10:39:29 +1000 Subject: [PATCH 234/548] Serialize Uuid as string for nicer debugging Also implement ToSql/FromSql for Operation/TaskMap so errors are handled properly --- Cargo.lock | 2 - taskchampion/Cargo.toml | 2 +- taskchampion/src/storage/sqlite.rs | 116 +++++++++++++++++++++-------- 3 files changed, 86 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8777c807b..c4ba8516d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1898,9 +1898,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "memchr", - "serde_json", "smallvec", - "uuid", ] [[package]] diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 196c2a7d8..03a047c88 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -21,7 +21,7 @@ kv = {version = "^0.10.0", features = ["msgpack-value"]} ureq = "^2.1.0" log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } -rusqlite = { version = "0.25", features = ["bundled", "uuid", "serde_json"] } +rusqlite = { version = "0.25", features = ["bundled"] } [dev-dependencies] proptest = "^1.0.0" diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index f0eacf740..9736287d3 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -1,5 +1,6 @@ use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use anyhow::Context; +use rusqlite::types::{FromSql, ToSql}; use rusqlite::{params, Connection, OptionalExtension}; use std::path::Path; use uuid::Uuid; @@ -10,6 +11,64 @@ enum SqliteError { TransactionAlreadyCommitted, } +/// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` +struct StoredUuid(Uuid); + +/// Conversion from Uuid stored as a string (rusqlite's uuid feature stores as binary blob) +impl FromSql for StoredUuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let u = Uuid::parse_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(StoredUuid(u)) + } +} + +/// Store Uuid as string in database +impl ToSql for StoredUuid { + fn to_sql(&self) -> rusqlite::Result> { + let s = self.0.to_string(); + Ok(s.into()) + } +} + +/// Wraps [`TaskMap`] (type alias for HashMap) so we can implement rusqlite conversion traits for it +struct StoredTaskMap(TaskMap); + +impl FromSql for StoredTaskMap { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let o: TaskMap = serde_json::from_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(StoredTaskMap(o)) + } +} + +impl ToSql for StoredTaskMap { + fn to_sql(&self) -> rusqlite::Result> { + let s = serde_json::to_string(&self.0) + .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; + Ok(s.into()) + } +} + + +/// Stores [`Operation`] in SQLite +impl FromSql for Operation { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let o: Operation = serde_json::from_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(o) + } +} + +impl ToSql for Operation { + fn to_sql(&self) -> rusqlite::Result> { + let s = serde_json::to_string(&self) + .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; + Ok(s.into()) + } +} + + /// SqliteStorage is an on-disk storage backed by SQLite3. pub struct SqliteStorage { con: Connection, @@ -68,25 +127,23 @@ impl Storage for SqliteStorage { impl<'t> StorageTxn for Txn<'t> { fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { let t = self.get_txn()?; - let result: Option = t + let result: Option = t .query_row( "SELECT data FROM tasks WHERE uuid = ? LIMIT 1", - [&uuid], + [&StoredUuid(uuid)], |r| r.get("data"), ) .optional()?; - match result { - None => Ok(None), - Some(r) => Ok(serde_json::from_str(&r)?), - } + // Get task from "stored" wrapper + Ok(result.and_then(|t: StoredTaskMap| Some(t.0))) } fn create_task(&mut self, uuid: Uuid) -> anyhow::Result { let t = self.get_txn()?; let count: usize = t.query_row( "SELECT count(uuid) FROM tasks WHERE uuid = ?", - [&uuid], + [&StoredUuid(uuid)], |x| x.get(0), )?; if count > 0 { @@ -94,10 +151,9 @@ impl<'t> StorageTxn for Txn<'t> { } let data = TaskMap::default(); - let data_str = serde_json::to_string(&data)?; t.execute( "INSERT INTO tasks (uuid, data) VALUES (?, ?)", - params![&uuid, &data_str], + params![&StoredUuid(uuid), &StoredTaskMap(data)], ) .context("Create task query")?; Ok(true) @@ -105,10 +161,9 @@ impl<'t> StorageTxn for Txn<'t> { fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> anyhow::Result<()> { let t = self.get_txn()?; - let data_str = serde_json::to_string(&task)?; t.execute( "INSERT OR REPLACE INTO tasks (uuid, data) VALUES (?, ?)", - params![&uuid, &data_str], + params![&StoredUuid(uuid), &StoredTaskMap(task)], ) .context("Update task query")?; Ok(()) @@ -117,7 +172,7 @@ impl<'t> StorageTxn for Txn<'t> { fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result { let t = self.get_txn()?; let changed = t - .execute("DELETE FROM tasks WHERE uuid = ?", [&uuid]) + .execute("DELETE FROM tasks WHERE uuid = ?", [&StoredUuid(uuid)]) .context("Delete task query")?; Ok(changed > 0) } @@ -127,10 +182,9 @@ impl<'t> StorageTxn for Txn<'t> { let mut q = t.prepare("SELECT uuid, data FROM tasks")?; let rows = q.query_map([], |r| { - let uuid: Uuid = r.get("uuid")?; - let data_str: String = r.get("data")?; - let data: TaskMap = serde_json::from_str(&data_str).unwrap(); // FIXME: Remove unwrap - Ok((uuid, data)) + let uuid: StoredUuid = r.get("uuid")?; + let data: StoredTaskMap = r.get("data")?; + Ok((uuid.0, data.0)) })?; let mut ret = vec![]; @@ -145,8 +199,8 @@ impl<'t> StorageTxn for Txn<'t> { let mut q = t.prepare("SELECT uuid FROM tasks")?; let rows = q.query_map([], |r| { - let uuid: Uuid = r.get("uuid")?; - Ok(uuid) + let uuid: StoredUuid = r.get("uuid")?; + Ok(uuid.0) })?; let mut ret = vec![]; @@ -159,21 +213,23 @@ impl<'t> StorageTxn for Txn<'t> { fn base_version(&mut self) -> anyhow::Result { let t = self.get_txn()?; - let mut version = t + let version: Option = t .query_row( "SELECT value FROM sync_meta WHERE key = 'base_version'", [], |r| r.get("value"), ) .optional()?; - Ok(version.unwrap_or(DEFAULT_BASE_VERSION)) + Ok(version + .and_then(|u| Some(u.0)) + .unwrap_or(DEFAULT_BASE_VERSION)) } fn set_base_version(&mut self, version: VersionId) -> anyhow::Result<()> { let t = self.get_txn()?; t.execute( "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)", - params!["base_version", &version], + params!["base_version", &StoredUuid(version)], ) .context("Set base version")?; Ok(()) @@ -184,8 +240,7 @@ impl<'t> StorageTxn for Txn<'t> { let mut q = t.prepare("SELECT data FROM operations ORDER BY id ASC")?; let rows = q.query_map([], |r| { - let data_str: String = r.get("data")?; - let data: Operation = serde_json::from_str(&data_str).unwrap(); // FIXME: Remove unwrap + let data: Operation = r.get("data")?; Ok(data) })?; @@ -199,10 +254,9 @@ impl<'t> StorageTxn for Txn<'t> { fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { let t = self.get_txn()?; - let data_str = serde_json::to_string(&op)?; t.execute( "INSERT INTO operations (data) VALUES (?)", - params![&data_str], + params![&op], ) .context("Add operation query")?; Ok(()) @@ -226,8 +280,8 @@ impl<'t> StorageTxn for Txn<'t> { let rows = q .query_map([], |r| { let id: usize = r.get("id")?; - let uuid: Uuid = r.get("uuid")?; - Ok((id, uuid)) + let uuid: StoredUuid = r.get("uuid")?; + Ok((id, uuid.0)) }) .context("Get working set query")?; @@ -251,7 +305,7 @@ impl<'t> StorageTxn for Txn<'t> { t.execute( "INSERT INTO working_set (id, uuid) VALUES (?, ?)", - params![next_working_id, &uuid], + params![next_working_id, &StoredUuid(uuid)], ) .context("Create task query")?; @@ -264,7 +318,7 @@ impl<'t> StorageTxn for Txn<'t> { // Add or override item Some(uuid) => t.execute( "INSERT OR REPLACE INTO working_set (id, uuid) VALUES (?, ?)", - params![index, &uuid], + params![index, &StoredUuid(uuid)], ), // Setting to None removes the row from database None => t.execute("DELETE FROM working_set WHERE id = ?", [index]), @@ -645,7 +699,7 @@ mod test { dbg!(1); { let mut txn = storage.txn()?; - let ws = txn.set_working_set_item(1, None)?; + txn.set_working_set_item(1, None)?; txn.commit()?; } @@ -660,7 +714,7 @@ mod test { // Override item { let mut txn = storage.txn()?; - let ws = txn.set_working_set_item(2, Some(uuid1))?; + txn.set_working_set_item(2, Some(uuid1))?; txn.commit()?; } From e479c25c34c362a2db5e6e2175be2337d43d6fc5 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 10:41:17 +1000 Subject: [PATCH 235/548] Tidier --- taskchampion/src/storage/sqlite.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 9736287d3..344650598 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -136,7 +136,7 @@ impl<'t> StorageTxn for Txn<'t> { .optional()?; // Get task from "stored" wrapper - Ok(result.and_then(|t: StoredTaskMap| Some(t.0))) + Ok(result.map(|t| t.0)) } fn create_task(&mut self, uuid: Uuid) -> anyhow::Result { @@ -221,7 +221,7 @@ impl<'t> StorageTxn for Txn<'t> { ) .optional()?; Ok(version - .and_then(|u| Some(u.0)) + .map(|u| u.0) .unwrap_or(DEFAULT_BASE_VERSION)) } From 23531d73c43072df44da41bd6a3afc5be70f56d5 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 10:57:43 +1000 Subject: [PATCH 236/548] Remove debug junk --- taskchampion/src/storage/sqlite.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 344650598..837cbba36 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -696,21 +696,18 @@ mod test { } // Clear one item - dbg!(1); { let mut txn = storage.txn()?; txn.set_working_set_item(1, None)?; txn.commit()?; } - dbg!(2); { let mut txn = storage.txn()?; let ws = txn.get_working_set()?; assert_eq!(ws, vec![None, None, Some(uuid2)]); } - dbg!(3); // Override item { let mut txn = storage.txn()?; From a3c9a76f1d70341d11be073a8aba21685f6975ec Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 10:58:34 +1000 Subject: [PATCH 237/548] Explicit check that rusqlite::Transaction isn't auto-commited on drop --- taskchampion/src/storage/sqlite.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 837cbba36..35944de29 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -350,6 +350,35 @@ mod test { use crate::storage::taskmap_with; use tempfile::TempDir; + #[test] + fn drop_transaction() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid1)?); + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid2)?); + std::mem::drop(txn); // Unnecessary explicit drop of transaction + } + + { + let mut txn = storage.txn()?; + let uuids = txn.all_task_uuids()?; + + assert_eq!(uuids, [uuid1]); + } + + Ok(()) + } + #[test] fn test_create() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; From ff894f6ff634a9dc5d6d2b7511c258d4c0e8d102 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 11:01:46 +1000 Subject: [PATCH 238/548] Comments and rustfmt --- taskchampion/src/storage/sqlite.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 35944de29..91eaf1f8a 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -34,6 +34,7 @@ impl ToSql for StoredUuid { /// Wraps [`TaskMap`] (type alias for HashMap) so we can implement rusqlite conversion traits for it struct StoredTaskMap(TaskMap); +/// Parses TaskMap stored as JSON in string column impl FromSql for StoredTaskMap { fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { let o: TaskMap = serde_json::from_str(value.as_str()?) @@ -42,6 +43,7 @@ impl FromSql for StoredTaskMap { } } +/// Stores TaskMap in string column impl ToSql for StoredTaskMap { fn to_sql(&self) -> rusqlite::Result> { let s = serde_json::to_string(&self.0) @@ -50,7 +52,6 @@ impl ToSql for StoredTaskMap { } } - /// Stores [`Operation`] in SQLite impl FromSql for Operation { fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { @@ -60,6 +61,7 @@ impl FromSql for Operation { } } +/// Parsers Operation stored as JSON in string column impl ToSql for Operation { fn to_sql(&self) -> rusqlite::Result> { let s = serde_json::to_string(&self) @@ -68,7 +70,6 @@ impl ToSql for Operation { } } - /// SqliteStorage is an on-disk storage backed by SQLite3. pub struct SqliteStorage { con: Connection, @@ -220,9 +221,7 @@ impl<'t> StorageTxn for Txn<'t> { |r| r.get("value"), ) .optional()?; - Ok(version - .map(|u| u.0) - .unwrap_or(DEFAULT_BASE_VERSION)) + Ok(version.map(|u| u.0).unwrap_or(DEFAULT_BASE_VERSION)) } fn set_base_version(&mut self, version: VersionId) -> anyhow::Result<()> { @@ -254,11 +253,8 @@ impl<'t> StorageTxn for Txn<'t> { fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { let t = self.get_txn()?; - t.execute( - "INSERT INTO operations (data) VALUES (?)", - params![&op], - ) - .context("Add operation query")?; + t.execute("INSERT INTO operations (data) VALUES (?)", params![&op]) + .context("Add operation query")?; Ok(()) } From 60ff0a8d11fcc63dcd071aa94af8bfee157562d7 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 29 Apr 2021 16:00:22 +1000 Subject: [PATCH 239/548] Very WIP (i.e broken) start of SQLite storage in server --- Cargo.lock | 3 + sync-server/Cargo.toml | 3 + sync-server/src/storage/mod.rs | 3 + sync-server/src/storage/sqlite.rs | 270 ++++++++++++++++++++++++++++++ 4 files changed, 279 insertions(+) create mode 100644 sync-server/src/storage/sqlite.rs diff --git a/Cargo.lock b/Cargo.lock index c4ba8516d..75eabf642 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2228,8 +2228,11 @@ dependencies = [ "futures", "kv", "log", + "rusqlite", "serde", + "serde_json", "tempfile", + "thiserror", "uuid", ] diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 2a42298be..c9d665ba0 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -10,12 +10,15 @@ edition = "2018" uuid = { version = "^0.8.2", features = ["serde", "v4"] } actix-web = "^3.3.2" anyhow = "1.0" +thiserror = "1.0" futures = "^0.3.8" serde = "^1.0.125" +serde_json = "^1.0" kv = {version = "^0.10.0", features = ["msgpack-value"]} clap = "^2.33.0" log = "^0.4.14" env_logger = "^0.8.3" +rusqlite = { version = "0.25", features = ["bundled"] } [dev-dependencies] actix-rt = "^2.2.0" diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 1d1cbe139..41fb400e9 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -6,6 +6,9 @@ mod inmemory; #[cfg(test)] pub(crate) use inmemory::InMemoryStorage; +mod sqlite; +pub(crate) use self::sqlite::SqliteStorage; + mod kv; pub(crate) use self::kv::KvStorage; diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs new file mode 100644 index 000000000..a6eaa0df4 --- /dev/null +++ b/sync-server/src/storage/sqlite.rs @@ -0,0 +1,270 @@ +use super::{Client, Storage, StorageTxn, Uuid, Version}; +use rusqlite::types::{FromSql, ToSql}; +use rusqlite::{params, Connection, OptionalExtension}; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use anyhow::Context; + +#[derive(Debug, thiserror::Error)] +enum SqliteError { + #[error("SQLite transaction already committted")] + TransactionAlreadyCommitted, +} + + +/// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` +struct StoredUuid(Uuid); + +/// Conversion from Uuid stored as a string (rusqlite's uuid feature stores as binary blob) +impl FromSql for StoredUuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let u = Uuid::parse_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(StoredUuid(u)) + } +} + +/// Store Uuid as string in database +impl ToSql for StoredUuid { + fn to_sql(&self) -> rusqlite::Result> { + let s = self.0.to_string(); + Ok(s.into()) + } +} + +/// Stores [`Client`] in SQLite +impl FromSql for Client { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let o: Client = serde_json::from_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(o) + } +} + +/// Parsers Operation stored as JSON in string column +impl ToSql for Client { + fn to_sql(&self) -> rusqlite::Result> { + let s = serde_json::to_string(&self) + .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; + Ok(s.into()) + } +} + + + +/// DB Key for versions: concatenation of client_key and parent_version_id +type VersionDbKey = [u8; 32]; + +fn version_db_key(client_key: Uuid, parent_version_id: Uuid) -> VersionDbKey { + let mut key = [0u8; 32]; + key[..16].clone_from_slice(client_key.as_bytes()); + key[16..].clone_from_slice(parent_version_id.as_bytes()); + key +} + +/// Key for clients: just the client_key +type ClientDbKey = [u8; 16]; + +fn client_db_key(client_key: Uuid) -> ClientDbKey { + *client_key.as_bytes() +} + +/// An on-disk storage backend which uses SQLite +pub(crate) struct SqliteStorage { + db_file: std::path::PathBuf, +} + +impl SqliteStorage { + fn new_connection(&self) -> anyhow::Result { + Ok(Connection::open(&self.db_file)?) + } + + pub fn new>(directory: P) -> anyhow::Result { + let db_file = directory.as_ref().join("taskchampion-sync-server.sqlite3"); + + let o = SqliteStorage { db_file }; + + { + let mut con = o.new_connection()?; + let txn = con.transaction()?; + + let queries = vec![ + "CREATE TABLE IF NOT EXISTS clients (id INTEGER PRIMARY KEY AUTOINCREMENT, data STRING);", + ]; + for q in queries { + txn.execute(q, []).context("Creating table")?; + } + txn.commit()?; + } + + Ok(o) + } +} + +impl Storage for SqliteStorage { + fn txn<'a>(&'a self) -> anyhow::Result> { + let mut con = self.new_connection()?; + let mut t = Txn{con, txn: None}; + Ok(Box::new(t)) + } +} + +struct Txn<'t> { + con: Connection, + txn: Option<&'t rusqlite::Transaction<'t>>, +} + +impl <'t>Txn<'t> { + fn get_txn(&mut self) -> Result<&'t rusqlite::Transaction, SqliteError> { + Ok(&self.con.transaction().unwrap()) + } +} + + +impl <'t>StorageTxn for Txn<'t> { + fn get_client(&mut self, client_key: Uuid) -> anyhow::Result> { + let t = self.get_txn()?; + let result: Option = t + .query_row( + "SELECT data FROM clients WHERE client_key = ? LIMIT 1", + [&StoredUuid(client_key)], + |r| r.get("data"), + ) + .optional()?; + + Ok(result) + } + + fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { + let t = self.get_txn()?; + + let client = Client{ latest_version_id }; + t.execute( + "INSERT OR REPLACE INTO clients (client_key, latest_version_id) VALUES (?, ?)", + params![&StoredUuid(latest_version_id), &client], + ) + .context("Create client query")?; + Ok(()) + } + + fn set_client_latest_version_id( + &mut self, + client_key: Uuid, + latest_version_id: Uuid, + ) -> anyhow::Result<()> { + // Implementation is same as new_client + self.new_client(client_key, latest_version_id) + } + + fn get_version_by_parent( + &mut self, + client_key: Uuid, + parent_version_id: Uuid, + ) -> anyhow::Result> { + todo!() + } + + fn add_version( + &mut self, + client_key: Uuid, + version_id: Uuid, + parent_version_id: Uuid, + history_segment: Vec, + ) -> anyhow::Result<()> { + let version = Version { + version_id, + parent_version_id, + history_segment, + }; + todo!(); + Ok(()) + } + + fn commit(&mut self) -> anyhow::Result<()> { + let t = self + .txn + .take() + .ok_or(SqliteError::TransactionAlreadyCommitted)?; + t.commit().context("Committing transaction")?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use tempfile::TempDir; + + #[test] + fn test_get_client_empty() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let storage = SqliteStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + let maybe_client = txn.get_client(Uuid::new_v4())?; + assert!(maybe_client.is_none()); + Ok(()) + } + + #[test] + fn test_client_storage() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let storage = SqliteStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + + let client_key = Uuid::new_v4(); + let latest_version_id = Uuid::new_v4(); + txn.new_client(client_key, latest_version_id)?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + + let latest_version_id = Uuid::new_v4(); + txn.set_client_latest_version_id(client_key, latest_version_id)?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + + Ok(()) + } + + #[test] + fn test_gvbp_empty() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let storage = SqliteStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + let maybe_version = txn.get_version_by_parent(Uuid::new_v4(), Uuid::new_v4())?; + assert!(maybe_version.is_none()); + Ok(()) + } + + #[test] + fn test_add_version_and_gvbp() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let storage = SqliteStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + + let client_key = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abc".to_vec(); + txn.add_version( + client_key, + version_id, + parent_version_id, + history_segment.clone(), + )?; + let version = txn + .get_version_by_parent(client_key, parent_version_id)? + .unwrap(); + + assert_eq!( + version, + Version { + version_id, + parent_version_id, + history_segment, + } + ); + Ok(()) + } +} From c696bf35d453850df810d14ba7a597488e10036e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 1 May 2021 12:25:19 -0400 Subject: [PATCH 240/548] Fix doc links `installation.md` was not being included due to using `-` instead of `*` in `SUMMARY.md`. The usage link was just incorrect. --- docs/src/SUMMARY.md | 18 +++++++++--------- docs/src/welcome.md | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index d8112b285..5d47ac45a 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,7 +1,7 @@ # Summary - [Welcome to TaskChampion](./welcome.md) - - [Installation](./installation.md) + * [Installation](./installation.md) * [Using the Task Command](./using-task-command.md) * [Configuration](./config-file.md) * [Reports](./reports.md) @@ -10,11 +10,11 @@ * [Running the Sync Server](./running-sync-server.md) * [Debugging](./debugging.md) - [Internal Details](./internals.md) - - [Data Model](./data-model.md) - - [Replica Storage](./storage.md) - - [Task Database](./taskdb.md) - - [Tasks](./tasks.md) - - [Synchronization and the Sync Server](./sync.md) - - [Synchronization Model](./sync-model.md) - - [Server-Replica Protocol](./sync-protocol.md) - - [Planned Functionality](./plans.md) + * [Data Model](./data-model.md) + * [Replica Storage](./storage.md) + * [Task Database](./taskdb.md) + * [Tasks](./tasks.md) + * [Synchronization and the Sync Server](./sync.md) + * [Synchronization Model](./sync-model.md) + * [Server-Replica Protocol](./sync-protocol.md) + * [Planned Functionality](./plans.md) diff --git a/docs/src/welcome.md b/docs/src/welcome.md index a650c543a..fc6d33149 100644 --- a/docs/src/welcome.md +++ b/docs/src/welcome.md @@ -60,4 +60,4 @@ server_origin: "https://taskchampion.example.com" The next run of `task sync` will upload your task history to that server. Configuring another device identically and running `task sync` will download that task history, and continue to stay in sync with subsequent runs of the command. -See [Usage](./usage.md) for more detailed information on using TaskChampion. +See [Usage](./using-task-command.md) for more detailed information on using TaskChampion. From 94d1217d81966532acf0c26086f4decc7af5de4c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 2 May 2021 16:59:51 -0400 Subject: [PATCH 241/548] Switch to TOML for configuration --- Cargo.lock | 36 +- cli/Cargo.toml | 6 +- cli/src/bin/task.rs | 2 +- cli/src/invocation/cmd/report.rs | 6 +- cli/src/invocation/mod.rs | 24 +- cli/src/invocation/report.rs | 15 +- cli/src/lib.rs | 5 +- cli/src/report.rs | 582 ------------------------------- cli/src/settings.rs | 85 ----- cli/src/settings/mod.rs | 227 ++++++++++++ cli/src/settings/report.rs | 535 ++++++++++++++++++++++++++++ cli/src/settings/util.rs | 41 +++ cli/tests/cli.rs | 26 +- docs/src/config-file.md | 12 +- docs/src/reports.md | 75 ++-- 15 files changed, 901 insertions(+), 776 deletions(-) delete mode 100644 cli/src/report.rs delete mode 100644 cli/src/settings.rs create mode 100644 cli/src/settings/mod.rs create mode 100644 cli/src/settings/report.rs create mode 100644 cli/src/settings/util.rs diff --git a/Cargo.lock b/Cargo.lock index 3d695cbfe..3e2127449 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -573,18 +573,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "config" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" -dependencies = [ - "lazy_static", - "nom 5.1.2", - "serde", - "yaml-rust", -] - [[package]] name = "const_fn" version = "0.4.6" @@ -1363,17 +1351,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "nom" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" -dependencies = [ - "lexical-core", - "memchr", - "version_check", -] - [[package]] name = "nom" version = "6.1.2" @@ -2170,17 +2147,17 @@ dependencies = [ "anyhow", "assert_cmd", "atty", - "config", "dirs-next", "env_logger", "log", - "nom 6.1.2", + "nom", "predicates", "prettytable-rs", "taskchampion", "tempfile", "termcolor", "textwrap 0.13.4", + "toml", ] [[package]] @@ -2778,12 +2755,3 @@ name = "wyz" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 570d106da..8d07f7e21 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -14,11 +14,7 @@ prettytable-rs = "^0.8.0" textwrap = { version="^0.13.4", features=["terminal_size"] } termcolor = "^1.1.2" atty = "^0.2.14" - -[dependencies.config] -default-features = false -features = ["yaml"] -version = "^0.11.0" +toml = "^0.5.8" [dependencies.taskchampion] path = "../taskchampion" diff --git a/cli/src/bin/task.rs b/cli/src/bin/task.rs index 8d8a756cb..ecf529be3 100644 --- a/cli/src/bin/task.rs +++ b/cli/src/bin/task.rs @@ -2,7 +2,7 @@ use std::process::exit; pub fn main() { if let Err(err) = taskchampion_cli::main() { - eprintln!("{}", err); + eprintln!("{:?}", err); exit(1); } } diff --git a/cli/src/invocation/cmd/report.rs b/cli/src/invocation/cmd/report.rs index bcb258298..7123f0353 100644 --- a/cli/src/invocation/cmd/report.rs +++ b/cli/src/invocation/cmd/report.rs @@ -1,13 +1,13 @@ use crate::argparse::Filter; use crate::invocation::display_report; -use config::Config; +use crate::settings::Settings; use taskchampion::Replica; use termcolor::WriteColor; pub(crate) fn execute( w: &mut W, replica: &mut Replica, - settings: &Config, + settings: &Settings, report_name: String, filter: Filter, ) -> anyhow::Result<()> { @@ -30,7 +30,7 @@ mod test { // The function being tested is only one line long, so this is sort of an integration test // for display_report. - let settings = crate::settings::default_settings().unwrap(); + let settings = Default::default(); let report_name = "next".to_owned(); let filter = Filter { ..Default::default() diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index feff61c27..997440b2e 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -1,7 +1,7 @@ //! The invocation module handles invoking the commands parsed by the argparse module. use crate::argparse::{Command, Subcommand}; -use config::Config; +use crate::settings::Settings; use taskchampion::{Replica, Server, ServerConfig, StorageConfig, Uuid}; use termcolor::{ColorChoice, StandardStream}; @@ -19,7 +19,7 @@ use report::display_report; /// Invoke the given Command in the context of the given settings #[allow(clippy::needless_return)] -pub(crate) fn invoke(command: Command, settings: Config) -> anyhow::Result<()> { +pub(crate) fn invoke(command: Command, settings: Settings) -> anyhow::Result<()> { log::debug!("command: {:?}", command); log::debug!("settings: {:?}", settings); @@ -100,35 +100,33 @@ pub(crate) fn invoke(command: Command, settings: Config) -> anyhow::Result<()> { // utilities for invoke /// Get the replica for this invocation -fn get_replica(settings: &Config) -> anyhow::Result { - let taskdb_dir = settings.get_str("data_dir")?.into(); +fn get_replica(settings: &Settings) -> anyhow::Result { + let taskdb_dir = settings.data_dir.clone(); log::debug!("Replica data_dir: {:?}", taskdb_dir); let storage_config = StorageConfig::OnDisk { taskdb_dir }; Ok(Replica::new(storage_config.into_storage()?)) } /// Get the server for this invocation -fn get_server(settings: &Config) -> anyhow::Result> { +fn get_server(settings: &Settings) -> anyhow::Result> { // if server_client_key and server_origin are both set, use // the remote server - let config = if let (Ok(client_key), Ok(origin)) = ( - settings.get_str("server_client_key"), - settings.get_str("server_origin"), + let config = if let (Some(client_key), Some(origin), Some(encryption_secret)) = ( + settings.server_client_key.as_ref(), + settings.server_origin.as_ref(), + settings.encryption_secret.as_ref(), ) { let client_key = Uuid::parse_str(&client_key)?; - let encryption_secret = settings - .get_str("encryption_secret") - .map_err(|_| anyhow::anyhow!("Could not read `encryption_secret` configuration"))?; log::debug!("Using sync-server with origin {}", origin); log::debug!("Sync client ID: {}", client_key); ServerConfig::Remote { - origin, + origin: origin.clone(), client_key, encryption_secret: encryption_secret.as_bytes().to_vec(), } } else { - let server_dir = settings.get_str("server_dir")?.into(); + let server_dir = settings.server_dir.clone(); log::debug!("Using local sync-server at `{:?}`", server_dir); ServerConfig::Local { server_dir } }; diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 0556d3b9d..48db2d4e6 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -1,8 +1,8 @@ use crate::argparse::Filter; use crate::invocation::filtered_tasks; -use crate::report::{Column, Property, Report, SortBy}; +use crate::settings::{Column, Property, Report, Settings, SortBy}; use crate::table; -use config::Config; +use anyhow::anyhow; use prettytable::{Row, Table}; use std::cmp::Ordering; use taskchampion::{Replica, Task, WorkingSet}; @@ -79,7 +79,7 @@ fn task_column(task: &Task, column: &Column, working_set: &WorkingSet) -> String pub(super) fn display_report( w: &mut W, replica: &mut Replica, - settings: &Config, + settings: &Settings, report_name: String, filter: Filter, ) -> anyhow::Result<()> { @@ -87,8 +87,11 @@ pub(super) fn display_report( let working_set = replica.working_set()?; // Get the report from settings - let mut report = Report::from_config(settings.get(&format!("reports.{}", report_name))?) - .map_err(|e| anyhow::anyhow!("report.{}{}", report_name, e))?; + let mut report = settings + .reports + .get(&report_name) + .ok_or_else(|| anyhow!("report `{}` not defined", report_name))? + .clone(); // include any user-supplied filter conditions report.filter = report.filter.intersect(filter); @@ -122,7 +125,7 @@ pub(super) fn display_report( mod test { use super::*; use crate::invocation::test::*; - use crate::report::Sort; + use crate::settings::Sort; use std::convert::TryInto; use taskchampion::{Status, Uuid}; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index e71dcc024..a846fa9f1 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -38,11 +38,12 @@ mod macros; mod argparse; mod invocation; -mod report; mod settings; mod table; mod usage; +use settings::Settings; + /// The main entry point for the command-line interface. This builds an Invocation /// from the particulars of the operating-system interface, and then executes it. pub fn main() -> anyhow::Result<()> { @@ -59,7 +60,7 @@ pub fn main() -> anyhow::Result<()> { let command = argparse::Command::from_argv(&argv[..])?; // load the application settings - let settings = settings::read_settings()?; + let settings = Settings::read()?; invocation::invoke(command, settings)?; Ok(()) diff --git a/cli/src/report.rs b/cli/src/report.rs deleted file mode 100644 index e2cdddac4..000000000 --- a/cli/src/report.rs +++ /dev/null @@ -1,582 +0,0 @@ -//! This module contains the data structures used to define reports. - -use crate::argparse::{Condition, Filter}; -use anyhow::bail; - -/// A report specifies a filter as well as a sort order and information about which -/// task attributes to display -#[derive(Clone, Debug, PartialEq, Default)] -pub(crate) struct Report { - /// Columns to display in this report - pub columns: Vec, - /// Sort order for this report - pub sort: Vec, - /// Filter selecting tasks for this report - pub filter: Filter, -} - -/// A column to display in a report -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct Column { - /// The label for this column - pub label: String, - - /// The property to display - pub property: Property, -} - -/// Task property to display in a report -#[derive(Clone, Debug, PartialEq)] -#[allow(dead_code)] -pub(crate) enum Property { - /// The task's ID, either working-set index or Uuid if not in the working set - Id, - - /// The task's full UUID - Uuid, - - /// Whether the task is active or not - Active, - - /// The task's description - Description, - - /// The task's tags - Tags, -} - -/// A sorting criterion for a sort operation. -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct Sort { - /// True if the sort should be "ascending" (a -> z, 0 -> 9, etc.) - pub ascending: bool, - - /// The property to sort on - pub sort_by: SortBy, -} - -/// Task property to sort by -#[derive(Clone, Debug, PartialEq)] -#[allow(dead_code)] -pub(crate) enum SortBy { - /// The task's ID, either working-set index or a UUID prefix; working - /// set tasks sort before others. - Id, - - /// The task's full UUID - Uuid, - - /// The task's description - Description, -} - -// Conversions from config::Value. Note that these cannot ergonomically use TryFrom/TryInto; see -// https://github.com/mehcode/config-rs/issues/162 - -impl Report { - /// Create a Report from a config value. This should be the `report.` value. - /// The error message begins with any additional path information, e.g., `.sort[1].sort_by: - /// ..`. - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let mut map = cfg.into_table().map_err(|e| anyhow::anyhow!(": {}", e))?; - let sort = if let Some(sort_array) = map.remove("sort") { - sort_array - .into_array() - .map_err(|e| anyhow::anyhow!(".sort: {}", e))? - .drain(..) - .enumerate() - .map(|(i, v)| { - Sort::from_config(v).map_err(|e| anyhow::anyhow!(".sort[{}]{}", i, e)) - }) - .collect::>>()? - } else { - vec![] - }; - - let columns = map - .remove("columns") - .ok_or_else(|| anyhow::anyhow!(": 'columns' property is required"))? - .into_array() - .map_err(|e| anyhow::anyhow!(".columns: {}", e))? - .drain(..) - .enumerate() - .map(|(i, v)| { - Column::from_config(v).map_err(|e| anyhow::anyhow!(".columns[{}]{}", i, e)) - }) - .collect::>>()?; - - let conditions = if let Some(conditions) = map.remove("filter") { - conditions - .into_array() - .map_err(|e| anyhow::anyhow!(".filter: {}", e))? - .drain(..) - .enumerate() - .map(|(i, v)| { - v.into_str() - .map_err(|e| e.into()) - .and_then(|s| Condition::parse_str(&s)) - .map_err(|e| anyhow::anyhow!(".filter[{}]: {}", i, e)) - }) - .collect::>>()? - } else { - vec![] - }; - - let filter = Filter { conditions }; - - if !map.is_empty() { - bail!(": unknown properties"); - } - - Ok(Report { - columns, - sort, - filter, - }) - } -} - -impl Column { - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let mut map = cfg.into_table().map_err(|e| anyhow::anyhow!(": {}", e))?; - let label = map - .remove("label") - .ok_or_else(|| anyhow::anyhow!(": 'label' property is required"))? - .into_str() - .map_err(|e| anyhow::anyhow!(".label: {}", e))?; - let property: config::Value = map - .remove("property") - .ok_or_else(|| anyhow::anyhow!(": 'property' property is required"))?; - let property = - Property::from_config(property).map_err(|e| anyhow::anyhow!(".property{}", e))?; - - if !map.is_empty() { - bail!(": unknown properties"); - } - - Ok(Column { label, property }) - } -} - -impl Property { - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let s = cfg.into_str().map_err(|e| anyhow::anyhow!(": {}", e))?; - Ok(match s.as_ref() { - "id" => Property::Id, - "uuid" => Property::Uuid, - "active" => Property::Active, - "description" => Property::Description, - "tags" => Property::Tags, - _ => bail!(": unknown property {}", s), - }) - } -} - -impl Sort { - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let mut map = cfg.into_table().map_err(|e| anyhow::anyhow!(": {}", e))?; - let ascending = match map.remove("ascending") { - Some(v) => v - .into_bool() - .map_err(|e| anyhow::anyhow!(".ascending: {}", e))?, - None => true, // default - }; - let sort_by: config::Value = map - .remove("sort_by") - .ok_or_else(|| anyhow::anyhow!(": 'sort_by' property is required"))?; - let sort_by = SortBy::from_config(sort_by).map_err(|e| anyhow::anyhow!(".sort_by{}", e))?; - - if !map.is_empty() { - bail!(": unknown properties"); - } - - Ok(Sort { ascending, sort_by }) - } -} - -impl SortBy { - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let s = cfg.into_str().map_err(|e| anyhow::anyhow!(": {}", e))?; - Ok(match s.as_ref() { - "id" => SortBy::Id, - "uuid" => SortBy::Uuid, - "description" => SortBy::Description, - _ => bail!(": unknown sort_by {}", s), - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use config::{Config, File, FileFormat, FileSourceString}; - use taskchampion::Status; - use textwrap::{dedent, indent}; - - fn config_from(cfg: &str) -> config::Value { - // wrap this in a "table" so that we can get any type of value at the top level. - let yaml = format!("val:\n{}", indent(&dedent(&cfg), " ")); - let mut settings = Config::new(); - let cfg_file: File = File::from_str(&yaml, FileFormat::Yaml); - settings.merge(cfg_file).unwrap(); - settings.cache.into_table().unwrap().remove("val").unwrap() - } - - #[test] - fn test_report_ok() { - let val = config_from( - " - filter: [] - sort: [] - columns: [] - filter: - - status:pending", - ); - let report = Report::from_config(val).unwrap(); - assert_eq!( - report.filter, - Filter { - conditions: vec![Condition::Status(Status::Pending),], - } - ); - assert_eq!(report.columns, vec![]); - assert_eq!(report.sort, vec![]); - } - - #[test] - fn test_report_no_sort() { - let val = config_from( - " - filter: [] - columns: []", - ); - let report = Report::from_config(val).unwrap(); - assert_eq!(report.sort, vec![]); - } - - #[test] - fn test_report_sort_not_array() { - let val = config_from( - " - filter: [] - sort: true - columns: []", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ".sort: invalid type: boolean `true`, expected an array" - ); - } - - #[test] - fn test_report_sort_error() { - let val = config_from( - " - filter: [] - sort: - - sort_by: id - - true - columns: []", - ); - assert!(&Report::from_config(val) - .unwrap_err() - .to_string() - .starts_with(".sort[1]")); - } - - #[test] - fn test_report_unknown_prop() { - let val = config_from( - " - columns: [] - filter: [] - sort: [] - nosuch: true - ", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ": unknown properties" - ); - } - - #[test] - fn test_report_no_columns() { - let val = config_from( - " - filter: [] - sort: []", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ": \'columns\' property is required" - ); - } - - #[test] - fn test_report_columns_not_array() { - let val = config_from( - " - filter: [] - sort: [] - columns: true", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ".columns: invalid type: boolean `true`, expected an array" - ); - } - - #[test] - fn test_report_column_error() { - let val = config_from( - " - filter: [] - sort: [] - columns: - - label: ID - property: id - - true", - ); - assert!(&Report::from_config(val) - .unwrap_err() - .to_string() - .starts_with(".columns[1]:")); - } - - #[test] - fn test_report_filter_not_array() { - let val = config_from( - " - filter: [] - sort: [] - columns: [] - filter: true", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ".filter: invalid type: boolean `true`, expected an array" - ); - } - - #[test] - fn test_report_filter_error() { - let val = config_from( - " - filter: [] - sort: [] - columns: [] - filter: - - nosuchfilter", - ); - assert!(&Report::from_config(val) - .unwrap_err() - .to_string() - .starts_with(".filter[0]: invalid filter condition:")); - } - - #[test] - fn test_column() { - let val = config_from( - " - label: ID - property: id", - ); - let column = Column::from_config(val).unwrap(); - assert_eq!( - column, - Column { - label: "ID".to_owned(), - property: Property::Id, - } - ); - } - - #[test] - fn test_column_unknown_prop() { - let val = config_from( - " - label: ID - property: id - nosuch: foo", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ": unknown properties" - ); - } - - #[test] - fn test_column_no_label() { - let val = config_from( - " - property: id", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ": 'label' property is required" - ); - } - - #[test] - fn test_column_invalid_label() { - let val = config_from( - " - label: [] - property: id", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ".label: invalid type: sequence, expected a string" - ); - } - - #[test] - fn test_column_no_property() { - let val = config_from( - " - label: ID", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ": 'property' property is required" - ); - } - - #[test] - fn test_column_invalid_property() { - let val = config_from( - " - label: ID - property: []", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ".property: invalid type: sequence, expected a string" - ); - } - - #[test] - fn test_property() { - let val = config_from("uuid"); - let prop = Property::from_config(val).unwrap(); - assert_eq!(prop, Property::Uuid); - } - - #[test] - fn test_property_invalid_type() { - let val = config_from("{}"); - assert_eq!( - &Property::from_config(val).unwrap_err().to_string(), - ": invalid type: map, expected a string" - ); - } - - #[test] - fn test_sort() { - let val = config_from( - " - ascending: false - sort_by: id", - ); - let sort = Sort::from_config(val).unwrap(); - assert_eq!( - sort, - Sort { - ascending: false, - sort_by: SortBy::Id, - } - ); - } - - #[test] - fn test_sort_no_ascending() { - let val = config_from( - " - sort_by: id", - ); - let sort = Sort::from_config(val).unwrap(); - assert_eq!( - sort, - Sort { - ascending: true, - sort_by: SortBy::Id, - } - ); - } - - #[test] - fn test_sort_unknown_prop() { - let val = config_from( - " - sort_by: id - nosuch: foo", - ); - assert_eq!( - &Sort::from_config(val).unwrap_err().to_string(), - ": unknown properties" - ); - } - - #[test] - fn test_sort_no_sort_by() { - let val = config_from( - " - ascending: true", - ); - assert_eq!( - &Sort::from_config(val).unwrap_err().to_string(), - ": 'sort_by' property is required" - ); - } - - #[test] - fn test_sort_invalid_ascending() { - let val = config_from( - " - sort_by: id - ascending: {}", - ); - assert_eq!( - &Sort::from_config(val).unwrap_err().to_string(), - ".ascending: invalid type: map, expected a boolean" - ); - } - - #[test] - fn test_sort_invalid_sort_by() { - let val = config_from( - " - sort_by: {}", - ); - assert_eq!( - &Sort::from_config(val).unwrap_err().to_string(), - ".sort_by: invalid type: map, expected a string" - ); - } - - #[test] - fn test_sort_by() { - let val = config_from("uuid"); - let prop = SortBy::from_config(val).unwrap(); - assert_eq!(prop, SortBy::Uuid); - } - - #[test] - fn test_sort_by_unknown() { - let val = config_from("nosuch"); - assert_eq!( - &SortBy::from_config(val).unwrap_err().to_string(), - ": unknown sort_by nosuch" - ); - } - - #[test] - fn test_sort_by_invalid_type() { - let val = config_from("{}"); - assert_eq!( - &SortBy::from_config(val).unwrap_err().to_string(), - ": invalid type: map, expected a string" - ); - } -} diff --git a/cli/src/settings.rs b/cli/src/settings.rs deleted file mode 100644 index de8c137f2..000000000 --- a/cli/src/settings.rs +++ /dev/null @@ -1,85 +0,0 @@ -use config::{Config, Environment, File, FileFormat, FileSourceFile, FileSourceString}; -use std::env; -use std::path::PathBuf; - -const DEFAULTS: &str = r#" -reports: - list: - sort: - - sort_by: uuid - columns: - - label: Id - property: id - - label: Description - property: description - - label: Active - property: active - - label: Tags - property: tags - next: - filter: - - "status:pending" - sort: - - sort_by: uuid - columns: - - label: Id - property: id - - label: Description - property: description - - label: Active - property: active - - label: Tags - property: tags -"#; - -/// Get the default settings for this application -pub(crate) fn default_settings() -> anyhow::Result { - let mut settings = Config::default(); - - // set up defaults - if let Some(dir) = dirs_next::data_local_dir() { - let mut tc_dir = dir.clone(); - tc_dir.push("taskchampion"); - settings.set_default( - "data_dir", - // the config crate does not support non-string paths - tc_dir.to_str().expect("data_local_dir is not utf-8"), - )?; - - let mut server_dir = dir; - server_dir.push("taskchampion-sync-server"); - settings.set_default( - "server_dir", - // the config crate does not support non-string paths - server_dir.to_str().expect("data_local_dir is not utf-8"), - )?; - } - - let defaults: File = File::from_str(DEFAULTS, FileFormat::Yaml); - settings.merge(defaults)?; - - Ok(settings) -} - -pub(crate) fn read_settings() -> anyhow::Result { - let mut settings = default_settings()?; - - // load either from the path in TASKCHAMPION_CONFIG, or from CONFIG_DIR/taskchampion - if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") { - log::debug!("Loading configuration from {:?}", config_file); - let config_file: PathBuf = config_file.into(); - let config_file: File = config_file.into(); - settings.merge(config_file.required(true))?; - env::remove_var("TASKCHAMPION_CONFIG"); - } else if let Some(mut dir) = dirs_next::config_dir() { - dir.push("taskchampion"); - log::debug!("Loading configuration from {:?} (optional)", dir); - let config_file: File = dir.into(); - settings.merge(config_file.required(false))?; - } - - // merge environment variables - settings.merge(Environment::with_prefix("TASKCHAMPION"))?; - - Ok(settings) -} diff --git a/cli/src/settings/mod.rs b/cli/src/settings/mod.rs new file mode 100644 index 000000000..a164150f4 --- /dev/null +++ b/cli/src/settings/mod.rs @@ -0,0 +1,227 @@ +//! Support for the CLI's configuration file, including default settings. +//! +//! Configuration is stored in a "parsed" format, meaning that any syntax errors will be caught on +//! startup and not just when those values are used. + +mod report; +mod util; + +use crate::argparse::{Condition, Filter}; +use anyhow::{anyhow, Context, Result}; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::env; +use std::fs; +use std::path::PathBuf; +use taskchampion::Status; +use toml::value::Table; +use util::table_with_keys; + +pub(crate) use report::{Column, Property, Report, Sort, SortBy}; + +#[derive(Debug)] +pub(crate) struct Settings { + // replica + pub(crate) data_dir: PathBuf, + + // remote sync server + pub(crate) server_client_key: Option, + pub(crate) server_origin: Option, + pub(crate) encryption_secret: Option, + + // local sync server + pub(crate) server_dir: PathBuf, + + // reports + pub(crate) reports: HashMap, +} + +impl Settings { + pub(crate) fn read() -> Result { + if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") { + log::debug!("Loading configuration from {:?}", config_file); + env::remove_var("TASKCHAMPION_CONFIG"); + Self::load_from_file(config_file.into(), true) + } else if let Some(mut dir) = dirs_next::config_dir() { + dir.push("taskchampion.toml"); + log::debug!("Loading configuration from {:?} (optional)", dir); + Self::load_from_file(dir, false) + } else { + Ok(Default::default()) + } + } + + fn load_from_file(config_file: PathBuf, required: bool) -> Result { + let mut settings = Self::default(); + + let config_toml = match fs::read_to_string(config_file.clone()) { + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + return if required { + Err(e.into()) + } else { + Ok(settings) + }; + } + Err(e) => return Err(e.into()), + Ok(s) => s, + }; + + let config_toml = config_toml + .parse::() + .with_context(|| format!("error while reading {:?}", config_file))?; + settings + .update_from_toml(&config_toml) + .with_context(|| format!("error while parsing {:?}", config_file))?; + + Ok(settings) + } + + /// Update this object with configuration from the given config file. This is + /// broken out mostly for convenience in error handling + fn update_from_toml(&mut self, config_toml: &toml::Value) -> Result<()> { + let table_keys = [ + "data_dir", + "server_client_key", + "server_origin", + "encryption_secret", + "server_dir", + "reports", + ]; + let table = table_with_keys(&config_toml, &table_keys)?; + + fn get_str_cfg( + table: &Table, + name: &'static str, + setter: F, + ) -> Result<()> { + if let Some(v) = table.get(name) { + setter( + v.as_str() + .ok_or_else(|| anyhow!(".{}: not a string", name))? + .to_owned(), + ); + } + Ok(()) + } + + get_str_cfg(table, "data_dir", |v| { + self.data_dir = v.into(); + })?; + + get_str_cfg(table, "server_client_key", |v| { + self.server_client_key = Some(v); + })?; + + get_str_cfg(table, "server_origin", |v| { + self.server_origin = Some(v); + })?; + + get_str_cfg(table, "encryption_secret", |v| { + self.encryption_secret = Some(v); + })?; + + get_str_cfg(table, "server_dir", |v| { + self.server_dir = v.into(); + })?; + + if let Some(v) = table.get("reports") { + let report_cfgs = v + .as_table() + .ok_or_else(|| anyhow!(".reports: not a table"))?; + for (name, cfg) in report_cfgs { + let report = Report::try_from(cfg).map_err(|e| anyhow!("reports.{}{}", name, e))?; + self.reports.insert(name.clone(), report); + } + } + + Ok(()) + } +} + +impl Default for Settings { + fn default() -> Self { + let data_dir; + let server_dir; + + if let Some(dir) = dirs_next::data_local_dir() { + data_dir = dir.join("taskchampion"); + server_dir = dir.join("taskchampion-sync-server"); + } else { + // fallback + data_dir = PathBuf::from("."); + server_dir = PathBuf::from("."); + } + + // define the default reports + let mut reports = HashMap::new(); + + reports.insert( + "list".to_owned(), + Report { + sort: vec![Sort { + ascending: true, + sort_by: SortBy::Uuid, + }], + columns: vec![ + Column { + label: "id".to_owned(), + property: Property::Id, + }, + Column { + label: "description".to_owned(), + property: Property::Description, + }, + Column { + label: "active".to_owned(), + property: Property::Active, + }, + Column { + label: "tags".to_owned(), + property: Property::Tags, + }, + ], + filter: Default::default(), + }, + ); + + reports.insert( + "next".to_owned(), + Report { + sort: vec![Sort { + ascending: true, + sort_by: SortBy::Uuid, + }], + columns: vec![ + Column { + label: "id".to_owned(), + property: Property::Id, + }, + Column { + label: "description".to_owned(), + property: Property::Description, + }, + Column { + label: "active".to_owned(), + property: Property::Active, + }, + Column { + label: "tags".to_owned(), + property: Property::Tags, + }, + ], + filter: Filter { + conditions: vec![Condition::Status(Status::Pending)], + }, + }, + ); + + Self { + data_dir, + server_client_key: None, + server_origin: None, + encryption_secret: None, + server_dir, + reports, + } + } +} diff --git a/cli/src/settings/report.rs b/cli/src/settings/report.rs new file mode 100644 index 000000000..959494e5d --- /dev/null +++ b/cli/src/settings/report.rs @@ -0,0 +1,535 @@ +//! This module contains the data structures used to define reports. + +use crate::argparse::{Condition, Filter}; +use crate::settings::util::table_with_keys; +use anyhow::{anyhow, bail, Result}; +use std::convert::{TryFrom, TryInto}; + +/// A report specifies a filter as well as a sort order and information about which +/// task attributes to display +#[derive(Clone, Debug, PartialEq, Default)] +pub(crate) struct Report { + /// Columns to display in this report + pub columns: Vec, + /// Sort order for this report + pub sort: Vec, + /// Filter selecting tasks for this report + pub filter: Filter, +} + +/// A column to display in a report +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Column { + /// The label for this column + pub label: String, + + /// The property to display + pub property: Property, +} + +/// Task property to display in a report +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum Property { + /// The task's ID, either working-set index or Uuid if not in the working set + Id, + + /// The task's full UUID + Uuid, + + /// Whether the task is active or not + Active, + + /// The task's description + Description, + + /// The task's tags + Tags, +} + +/// A sorting criterion for a sort operation. +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Sort { + /// True if the sort should be "ascending" (a -> z, 0 -> 9, etc.) + pub ascending: bool, + + /// The property to sort on + pub sort_by: SortBy, +} + +/// Task property to sort by +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum SortBy { + /// The task's ID, either working-set index or a UUID prefix; working + /// set tasks sort before others. + Id, + + /// The task's full UUID + Uuid, + + /// The task's description + Description, +} + +// Conversions from settings::Settings. + +impl TryFrom for Report { + type Error = anyhow::Error; + + fn try_from(cfg: toml::Value) -> Result { + Report::try_from(&cfg) + } +} + +impl TryFrom<&toml::Value> for Report { + type Error = anyhow::Error; + + /// Create a Report from a toml value. This should be the `report.` value. + /// The error message begins with any additional path information, e.g., `.sort[1].sort_by: + /// ..`. + fn try_from(cfg: &toml::Value) -> Result { + let keys = ["sort", "columns", "filter"]; + let table = table_with_keys(cfg, &keys).map_err(|e| anyhow!(": {}", e))?; + + let sort = match table.get("sort") { + Some(v) => v + .as_array() + .ok_or_else(|| anyhow!(".sort: not an array"))? + .iter() + .enumerate() + .map(|(i, v)| v.try_into().map_err(|e| anyhow!(".sort[{}]{}", i, e))) + .collect::>>()?, + None => vec![], + }; + + let columns = match table.get("columns") { + Some(v) => v + .as_array() + .ok_or_else(|| anyhow!(".columns: not an array"))? + .iter() + .enumerate() + .map(|(i, v)| v.try_into().map_err(|e| anyhow!(".columns[{}]{}", i, e))) + .collect::>>()?, + None => bail!(": `columns` property is required"), + }; + + let conditions = match table.get("filter") { + Some(v) => v + .as_array() + .ok_or_else(|| anyhow!(".filter: not an array"))? + .iter() + .enumerate() + .map(|(i, v)| { + v.as_str() + .ok_or_else(|| anyhow!(".filter[{}]: not a string", i)) + .and_then(|s| Condition::parse_str(&s)) + .map_err(|e| anyhow!(".filter[{}]: {}", i, e)) + }) + .collect::>>()?, + None => vec![], + }; + + Ok(Report { + columns, + sort, + filter: Filter { conditions }, + }) + } +} + +impl TryFrom<&toml::Value> for Column { + type Error = anyhow::Error; + + fn try_from(cfg: &toml::Value) -> Result { + let keys = ["label", "property"]; + let table = table_with_keys(cfg, &keys).map_err(|e| anyhow!(": {}", e))?; + + let label = match table.get("label") { + Some(v) => v + .as_str() + .ok_or_else(|| anyhow!(".label: not a string"))? + .to_owned(), + None => bail!(": `label` property is required"), + }; + + let property = match table.get("property") { + Some(v) => v.try_into().map_err(|e| anyhow!(".property{}", e))?, + None => bail!(": `property` property is required"), + }; + + Ok(Column { label, property }) + } +} + +impl TryFrom<&toml::Value> for Property { + type Error = anyhow::Error; + + fn try_from(cfg: &toml::Value) -> Result { + let s = cfg.as_str().ok_or_else(|| anyhow!(": not a string"))?; + Ok(match s { + "id" => Property::Id, + "uuid" => Property::Uuid, + "active" => Property::Active, + "description" => Property::Description, + "tags" => Property::Tags, + _ => bail!(": unknown property {}", s), + }) + } +} + +impl TryFrom<&toml::Value> for Sort { + type Error = anyhow::Error; + + fn try_from(cfg: &toml::Value) -> Result { + let keys = ["ascending", "sort_by"]; + let table = table_with_keys(cfg, &keys).map_err(|e| anyhow!(": {}", e))?; + let ascending = match table.get("ascending") { + Some(v) => v + .as_bool() + .ok_or_else(|| anyhow!(".ascending: not a boolean value"))?, + None => true, // default + }; + + let sort_by = match table.get("sort_by") { + Some(v) => v.try_into().map_err(|e| anyhow!(".sort_by{}", e))?, + None => bail!(": `sort_by` property is required"), + }; + + Ok(Sort { ascending, sort_by }) + } +} + +impl TryFrom<&toml::Value> for SortBy { + type Error = anyhow::Error; + + fn try_from(cfg: &toml::Value) -> Result { + let s = cfg.as_str().ok_or_else(|| anyhow!(": not a string"))?; + Ok(match s { + "id" => SortBy::Id, + "uuid" => SortBy::Uuid, + "description" => SortBy::Description, + _ => bail!(": unknown sort_by value `{}`", s), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use taskchampion::Status; + use toml::toml; + + #[test] + fn test_report_ok() { + let val = toml! { + sort = [] + columns = [] + filter = ["status:pending"] + }; + let report: Report = TryInto::try_into(val).unwrap(); + assert_eq!( + report.filter, + Filter { + conditions: vec![Condition::Status(Status::Pending),], + } + ); + assert_eq!(report.columns, vec![]); + assert_eq!(report.sort, vec![]); + } + + #[test] + fn test_report_no_sort() { + let val = toml! { + filter = [] + columns = [] + }; + let report = Report::try_from(val).unwrap(); + assert_eq!(report.sort, vec![]); + } + + #[test] + fn test_report_sort_not_array() { + let val = toml! { + filter = [] + sort = true + columns = [] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ".sort: not an array"); + } + + #[test] + fn test_report_sort_error() { + let val = toml! { + filter = [] + sort = [ { sort_by = "id" }, true ] + columns = [] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert!(err.starts_with(".sort[1]")); + } + + #[test] + fn test_report_unknown_prop() { + let val = toml! { + columns = [] + filter = [] + sort = [] + nosuch = true + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ": unknown table key `nosuch`"); + } + + #[test] + fn test_report_no_columns() { + let val = toml! { + filter = [] + sort = [] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ": `columns` property is required"); + } + + #[test] + fn test_report_columns_not_array() { + let val = toml! { + filter = [] + sort = [] + columns = true + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ".columns: not an array"); + } + + #[test] + fn test_report_column_error() { + let val = toml! { + filter = [] + sort = [] + + [[columns]] + label = "ID" + property = "id" + + [[columns]] + foo = 10 + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ".columns[1]: unknown table key `foo`"); + } + + #[test] + fn test_report_filter_not_array() { + let val = toml! { + filter = "foo" + sort = [] + columns = [] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ".filter: not an array"); + } + + #[test] + fn test_report_filter_error() { + let val = toml! { + sort = [] + columns = [] + filter = [ "nosuchfilter" ] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert!(err.starts_with(".filter[0]: invalid filter condition:")); + } + + #[test] + fn test_column() { + let val = toml! { + label = "ID" + property = "id" + }; + let column = Column::try_from(&val).unwrap(); + assert_eq!( + column, + Column { + label: "ID".to_owned(), + property: Property::Id, + } + ); + } + + #[test] + fn test_column_unknown_prop() { + let val = toml! { + label = "ID" + property = "id" + nosuch = "foo" + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ": unknown table key `nosuch`" + ); + } + + #[test] + fn test_column_no_label() { + let val = toml! { + property = "id" + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ": `label` property is required" + ); + } + + #[test] + fn test_column_invalid_label() { + let val = toml! { + label = [] + property = "id" + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ".label: not a string" + ); + } + + #[test] + fn test_column_no_property() { + let val = toml! { + label = "ID" + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ": `property` property is required" + ); + } + + #[test] + fn test_column_invalid_property() { + let val = toml! { + label = "ID" + property = [] + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ".property: not a string" + ); + } + + #[test] + fn test_property() { + let val = toml::Value::String("uuid".to_owned()); + let prop = Property::try_from(&val).unwrap(); + assert_eq!(prop, Property::Uuid); + } + + #[test] + fn test_property_invalid_type() { + let val = toml::Value::Array(vec![]); + assert_eq!( + &Property::try_from(&val).unwrap_err().to_string(), + ": not a string" + ); + } + + #[test] + fn test_sort() { + let val = toml! { + ascending = false + sort_by = "id" + }; + let sort = Sort::try_from(&val).unwrap(); + assert_eq!( + sort, + Sort { + ascending: false, + sort_by: SortBy::Id, + } + ); + } + + #[test] + fn test_sort_no_ascending() { + let val = toml! { + sort_by = "id" + }; + let sort = Sort::try_from(&val).unwrap(); + assert_eq!( + sort, + Sort { + ascending: true, + sort_by: SortBy::Id, + } + ); + } + + #[test] + fn test_sort_unknown_prop() { + let val = toml! { + sort_by = "id" + nosuch = true + }; + assert_eq!( + &Sort::try_from(&val).unwrap_err().to_string(), + ": unknown table key `nosuch`" + ); + } + + #[test] + fn test_sort_no_sort_by() { + let val = toml! { + ascending = true + }; + assert_eq!( + &Sort::try_from(&val).unwrap_err().to_string(), + ": `sort_by` property is required" + ); + } + + #[test] + fn test_sort_invalid_ascending() { + let val = toml! { + sort_by = "id" + ascending = {} + }; + assert_eq!( + &Sort::try_from(&val).unwrap_err().to_string(), + ".ascending: not a boolean value" + ); + } + + #[test] + fn test_sort_invalid_sort_by() { + let val = toml! { + sort_by = {} + }; + assert_eq!( + &Sort::try_from(&val).unwrap_err().to_string(), + ".sort_by: not a string" + ); + } + + #[test] + fn test_sort_by() { + let val = toml::Value::String("uuid".to_string()); + let prop = SortBy::try_from(&val).unwrap(); + assert_eq!(prop, SortBy::Uuid); + } + + #[test] + fn test_sort_by_unknown() { + let val = toml::Value::String("nosuch".to_string()); + assert_eq!( + &SortBy::try_from(&val).unwrap_err().to_string(), + ": unknown sort_by value `nosuch`" + ); + } + + #[test] + fn test_sort_by_invalid_type() { + let val = toml::Value::Array(vec![]); + assert_eq!( + &SortBy::try_from(&val).unwrap_err().to_string(), + ": not a string" + ); + } +} diff --git a/cli/src/settings/util.rs b/cli/src/settings/util.rs new file mode 100644 index 000000000..ea585bef2 --- /dev/null +++ b/cli/src/settings/util.rs @@ -0,0 +1,41 @@ +use anyhow::{anyhow, bail, Result}; +use toml::value::Table; + +/// Check that the input is a table and contains no keys not in the given list, returning +/// the table. +pub(super) fn table_with_keys<'a>(cfg: &'a toml::Value, keys: &[&str]) -> Result<&'a Table> { + let table = cfg.as_table().ok_or_else(|| anyhow!("not a table"))?; + + for tk in table.keys() { + if !keys.iter().any(|k| k == tk) { + bail!("unknown table key `{}`", tk); + } + } + Ok(table) +} + +#[cfg(test)] +mod test { + use super::*; + use toml::toml; + + #[test] + fn test_dissect_table_missing() { + let val = toml! { bar = true }; + let diss = table_with_keys(&val, &["foo", "bar"]).unwrap(); + assert_eq!(diss.get("bar"), Some(&toml::Value::Boolean(true))); + assert_eq!(diss.get("foo"), None); + } + + #[test] + fn test_dissect_table_extra() { + let val = toml! { nosuch = 10 }; + assert!(table_with_keys(&val, &["foo", "bar"]).is_err()); + } + + #[test] + fn test_dissect_table_not_a_table() { + let val = toml::Value::Array(vec![]); + assert!(table_with_keys(&val, &["foo", "bar"]).is_err()); + } +} diff --git a/cli/tests/cli.rs b/cli/tests/cli.rs index e4eb0250b..3a39948b7 100644 --- a/cli/tests/cli.rs +++ b/cli/tests/cli.rs @@ -1,13 +1,31 @@ use assert_cmd::prelude::*; use predicates::prelude::*; +use std::fs; use std::process::Command; +use tempfile::TempDir; // NOTE: This tests that the task binary is running and parsing arguments. The details of // subcommands are handled with unit tests. +/// These tests force config to be read via TASKCHAMPION_CONFIG so that a user's own config file +/// (in their homedir) does not interfere with tests. +fn test_cmd(dir: &TempDir) -> Result> { + let config_filename = dir.path().join("config.toml"); + fs::write( + config_filename.clone(), + format!("data_dir = {:?}", dir.path()), + )?; + + let config_filename = config_filename.to_str().unwrap(); + let mut cmd = Command::cargo_bin("task")?; + cmd.env("TASKCHAMPION_CONFIG", config_filename); + Ok(cmd) +} + #[test] fn help() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("task")?; + let dir = TempDir::new().unwrap(); + let mut cmd = test_cmd(&dir)?; cmd.arg("--help"); cmd.assert() @@ -19,7 +37,8 @@ fn help() -> Result<(), Box> { #[test] fn version() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("task")?; + let dir = TempDir::new().unwrap(); + let mut cmd = test_cmd(&dir)?; cmd.arg("--version"); cmd.assert() @@ -31,7 +50,8 @@ fn version() -> Result<(), Box> { #[test] fn invalid_option() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("task")?; + let dir = TempDir::new().unwrap(); + let mut cmd = test_cmd(&dir)?; cmd.arg("--no-such-option"); cmd.assert() diff --git a/docs/src/config-file.md b/docs/src/config-file.md index 74737b98c..f5daa5be5 100644 --- a/docs/src/config-file.md +++ b/docs/src/config-file.md @@ -2,14 +2,18 @@ The `task` command will work out-of-the-box with no configuration file, using default values. -Configuration is read from `taskchampion.yaml` in your config directory. +Configuration is read from `taskchampion.toml` in your config directory. On Linux systems, that directory is `~/.config`. On OS X, it's `~/Library/Preferences`. On Windows, it's `AppData/Roaming` in your home directory. -The path can be overridden by setting `$TASKCHAMPION_CONFIG`. +This can be overridden by setting `$TASKCHAMPION_CONFIG` to the configuration filename. -Individual configuration parameters can be overridden by environment variables, converted to upper-case and prefixed with `TASKCHAMPION_`, e.g., `TASKCHAMPION_DATA_DIR`. -Nested configuration parameters such as `reports` cannot be overridden by environment variables. +The file format is [TOML](https://toml.io/). +For example: + +```toml +data_dir = "/home/myuser/.tasks" +``` ## Directories diff --git a/docs/src/reports.md b/docs/src/reports.md index 376bf3531..5a7accc05 100644 --- a/docs/src/reports.md +++ b/docs/src/reports.md @@ -14,6 +14,7 @@ $ task Id Description Active Tags 1 learn about TaskChampion +next 2 buy wedding gift * +buy +3 plant tomatoes +garden ``` The `Id` column contains short numeric IDs that are assigned to pending tasks. @@ -23,58 +24,56 @@ The `list` report lists all tasks, with a similar set of columns. ## Custom Reports -Custom reports are defined in the configuration file's `reports` property. +Custom reports are defined in the configuration file's `reports` table. This is a mapping from each report's name to its definition. Each definition has the following properties: -* `filter` - criteria for the tasks to include in the report -* `sort` - how to order the tasks +* `filter` - criteria for the tasks to include in the report (optional) +* `sort` - how to order the tasks (optional) * `columns` - the columns of information to display for each task +For example: + +```toml +[reports.garden] +sort = [ + { sort_by = "description" } +] +filter = [ + "status:pending", + "+garden" +] +columns = [ + { label = "ID", property = "id" }, + { label = "Description", property = "description" }, +] +``` + The filter is a list of filter arguments, just like those that can be used on the command line. -See the `task help` output for more details on this syntax. -For example: +See the `ta help` output for more details on this syntax. +It will be merged with any filters provided on the command line, when the report is invoked. -```yaml -reports: - garden: - filter: - - "status:pending" - - "+garden" -``` - -The sort order is defined by an array of objects containing a `sort_by` property and an optional `ascending` property. +The sort order is defined by an array of tables containing a `sort_by` property and an optional `ascending` property. Tasks are compared by the first criterion, and if that is equal by the second, and so on. -For example: - -```yaml -reports: - garden: - sort: - - sort_by: description - - sort_by: uuid - ascending: false -``` If `ascending` is given, it can be `true` for the default sort order, or `false` for the reverse. +In most cases tasks are just sorted by one criterion, but a more advanced example might look like: + +```toml +[reports.garden] +sort = [ + { sort_by = "description" } + { sort_by = "uuid", ascending = false } +] +... +``` + The available values of `sort_by` are (TODO: generate automatically) -Finally, the configuration specifies the list of columns to display in the `columns` property. -Each element has a `label` and a `property`: - -```yaml -reports: - garden: - columns: - - label: Id - property: id - - label: Description - property: description - - label: Tags - property: tags -``` +Finally, the `columns` configuration specifies the list of columns to display. +Each element has a `label` and a `property`, as shown in the example above. The avaliable properties are: From 0f0f2b0e752a7516aef1d8cff92ef51c910ab3fd Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 1 May 2021 14:07:00 -0400 Subject: [PATCH 242/548] rename CLI to `ta` --- cli/src/argparse/command.rs | 4 ++-- cli/src/bin/{task.rs => ta.rs} | 0 cli/src/invocation/cmd/help.rs | 4 ++-- cli/src/usage.rs | 19 ++++++++++--------- cli/tests/cli.rs | 4 ++-- docs/src/config-file.md | 2 +- docs/src/debugging.md | 4 ++-- docs/src/reports.md | 4 ++-- docs/src/sync-model.md | 2 +- docs/src/tags.md | 2 +- docs/src/task-sync.md | 6 +++--- docs/src/using-task-command.md | 4 ++-- docs/src/welcome.md | 20 ++++++++++---------- 13 files changed, 38 insertions(+), 37 deletions(-) rename cli/src/bin/{task.rs => ta.rs} (100%) diff --git a/cli/src/argparse/command.rs b/cli/src/argparse/command.rs index f9c71352c..79a715ec7 100644 --- a/cli/src/argparse/command.rs +++ b/cli/src/argparse/command.rs @@ -49,10 +49,10 @@ mod test { #[test] fn test_version() { assert_eq!( - Command::from_argv(argv!["task", "version"]).unwrap(), + Command::from_argv(argv!["ta", "version"]).unwrap(), Command { subcommand: Subcommand::Version, - command_name: s!("task"), + command_name: s!("ta"), } ); } diff --git a/cli/src/bin/task.rs b/cli/src/bin/ta.rs similarity index 100% rename from cli/src/bin/task.rs rename to cli/src/bin/ta.rs diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index 878234933..421c140a7 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -19,12 +19,12 @@ mod test { #[test] fn test_summary() { let mut w = test_writer(); - execute(&mut w, s!("task"), true).unwrap(); + execute(&mut w, s!("ta"), true).unwrap(); } #[test] fn test_long() { let mut w = test_writer(); - execute(&mut w, s!("task"), false).unwrap(); + execute(&mut w, s!("ta"), false).unwrap(); } } diff --git a/cli/src/usage.rs b/cli/src/usage.rs index 159a7d368..f1bacf674 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -42,7 +42,7 @@ impl Usage { writeln!(w, "USAGE:\n {} [args]\n", command_name)?; writeln!(w, "TaskChampion subcommands:")?; for subcommand in self.subcommands.iter() { - subcommand.write_help(&mut w, summary)?; + subcommand.write_help(&mut w, command_name, summary)?; } writeln!(w, "Filter Expressions:\n")?; writeln!( @@ -56,7 +56,7 @@ impl Usage { ) )?; for filter in self.filters.iter() { - filter.write_help(&mut w, summary)?; + filter.write_help(&mut w, command_name, summary)?; } writeln!(w, "Modifications:\n")?; writeln!( @@ -70,10 +70,10 @@ impl Usage { ) )?; for modification in self.modifications.iter() { - modification.write_help(&mut w, summary)?; + modification.write_help(&mut w, command_name, summary)?; } if !summary { - writeln!(w, "\nSee `task help` for more detail")?; + writeln!(w, "\nSee `{} help` for more detail", command_name)?; } Ok(()) } @@ -108,13 +108,14 @@ pub(crate) struct Subcommand { } impl Subcommand { - fn write_help(&self, mut w: W, summary: bool) -> Result<()> { + fn write_help(&self, mut w: W, command_name: &str, summary: bool) -> Result<()> { if summary { - writeln!(w, " task {} - {}", self.name, self.summary)?; + writeln!(w, " {} {} - {}", command_name, self.name, self.summary)?; } else { writeln!( w, - " task {}\n{}", + " {} {}\n{}", + command_name, self.syntax, indented(self.description, " ") )?; @@ -138,7 +139,7 @@ pub(crate) struct Filter { } impl Filter { - fn write_help(&self, mut w: W, summary: bool) -> Result<()> { + fn write_help(&self, mut w: W, _: &str, summary: bool) -> Result<()> { if summary { writeln!(w, " {} - {}", self.syntax, self.summary)?; } else { @@ -168,7 +169,7 @@ pub(crate) struct Modification { } impl Modification { - fn write_help(&self, mut w: W, summary: bool) -> Result<()> { + fn write_help(&self, mut w: W, _: &str, summary: bool) -> Result<()> { if summary { writeln!(w, " {} - {}", self.syntax, self.summary)?; } else { diff --git a/cli/tests/cli.rs b/cli/tests/cli.rs index 3a39948b7..7eabe2c77 100644 --- a/cli/tests/cli.rs +++ b/cli/tests/cli.rs @@ -4,7 +4,7 @@ use std::fs; use std::process::Command; use tempfile::TempDir; -// NOTE: This tests that the task binary is running and parsing arguments. The details of +// NOTE: This tests that the `ta` binary is running and parsing arguments. The details of // subcommands are handled with unit tests. /// These tests force config to be read via TASKCHAMPION_CONFIG so that a user's own config file @@ -17,7 +17,7 @@ fn test_cmd(dir: &TempDir) -> Result> { )?; let config_filename = config_filename.to_str().unwrap(); - let mut cmd = Command::cargo_bin("task")?; + let mut cmd = Command::cargo_bin("ta")?; cmd.env("TASKCHAMPION_CONFIG", config_filename); Ok(cmd) } diff --git a/docs/src/config-file.md b/docs/src/config-file.md index f5daa5be5..bb3ba31d3 100644 --- a/docs/src/config-file.md +++ b/docs/src/config-file.md @@ -1,6 +1,6 @@ # Configuration -The `task` command will work out-of-the-box with no configuration file, using default values. +The `ta` command will work out-of-the-box with no configuration file, using default values. Configuration is read from `taskchampion.toml` in your config directory. On Linux systems, that directory is `~/.config`. diff --git a/docs/src/debugging.md b/docs/src/debugging.md index f3de1c7a7..d81c9d746 100644 --- a/docs/src/debugging.md +++ b/docs/src/debugging.md @@ -1,9 +1,9 @@ # Debugging -Both `task` and `taskchampion-sync-server` use [env-logger](https://docs.rs/env_logger) and can be configured to log at various levels with the `RUST_LOG` environment variable. +Both `ta` and `taskchampion-sync-server` use [env-logger](https://docs.rs/env_logger) and can be configured to log at various levels with the `RUST_LOG` environment variable. For example: ```shell -$ RUST_LOG=taskchampion=trace task add foo +$ RUST_LOG=taskchampion=trace ta add foo ``` The output may provide valuable clues in debugging problems. diff --git a/docs/src/reports.md b/docs/src/reports.md index 5a7accc05..05026da6f 100644 --- a/docs/src/reports.md +++ b/docs/src/reports.md @@ -10,7 +10,7 @@ TaskChampion includes several "built-in" reports, as well as supporting custom r The `next` report is the default, and lists all pending tasks: ```text -$ task +$ ta Id Description Active Tags 1 learn about TaskChampion +next 2 buy wedding gift * +buy @@ -18,7 +18,7 @@ Id Description Active Tags ``` The `Id` column contains short numeric IDs that are assigned to pending tasks. -These IDs are easy to type, such as to mark task 2 done (`task 2 done`). +These IDs are easy to type, such as to mark task 2 done (`ta 2 done`). The `list` report lists all tasks, with a similar set of columns. diff --git a/docs/src/sync-model.md b/docs/src/sync-model.md index 691312efa..e75bab670 100644 --- a/docs/src/sync-model.md +++ b/docs/src/sync-model.md @@ -125,4 +125,4 @@ Without synchronization, its list of pending operations would grow indefinitely, So all replicas, even "singleton" replicas which do not replicate task data with any other replica, must synchronize periodically. TaskChampion provides a `LocalServer` for this purpose. -It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally, all within the `task` binary. +It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally, all within the `ta` binary. diff --git a/docs/src/tags.md b/docs/src/tags.md index 4148d5117..7c159c644 100644 --- a/docs/src/tags.md +++ b/docs/src/tags.md @@ -4,7 +4,7 @@ Each task has a collection of associated tags. Tags are short words that categorize tasks, typically written with a leading `+`, such as `+next` or `+jobsearch`. Tags are useful for filtering tasks in reports or on the command line. -For example, when it's time to continue the job search, `task +jobsearch` will show pending tasks with the `jobsearch` tag. +For example, when it's time to continue the job search, `ta +jobsearch` will show pending tasks with the `jobsearch` tag. ## Allowed Tags diff --git a/docs/src/task-sync.md b/docs/src/task-sync.md index 7d874760c..a4252c6ac 100644 --- a/docs/src/task-sync.md +++ b/docs/src/task-sync.md @@ -4,7 +4,7 @@ A single TaskChampion task database is known as a "replica". A replica "synchronizes" its local information with other replicas via a sync server. Many replicas can thus share the same task history. -This operation is triggered by running `task sync`. +This operation is triggered by running `ta sync`. Typically this runs frequently in a cron task. Synchronization is quick, especially if no changes have occurred. @@ -14,10 +14,10 @@ Without periodic syncs, the storage space used for the task database will grow q By default, TaskChampion syncs to a "local server", as specified by the `server_dir` configuration parameter. Every replica sharing a task history should have precisely the same configuration for `server_origin`, `server_client_key`, and `encryption_secret`. -Synchronizing a new replica to an existing task history is easy: begin with an empty replica, configured for the remote server, and run `task sync`. +Synchronizing a new replica to an existing task history is easy: begin with an empty replica, configured for the remote server, and run `ta sync`. The replica will download the entire task history. -It is possible to switch a single replica to a remote server by simply configuring for the remote server and running `task sync`. +It is possible to switch a single replica to a remote server by simply configuring for the remote server and running `ta sync`. The replica will upload the entire task history to the server. Once this is complete, additional replicas can be configured with the same settings in order to share the task history. diff --git a/docs/src/using-task-command.md b/docs/src/using-task-command.md index dcc57b6f5..d2e7f0ca0 100644 --- a/docs/src/using-task-command.md +++ b/docs/src/using-task-command.md @@ -1,9 +1,9 @@ # Using the Task Command -The main interface to your tasks is the `task` command, which supports various subcommands such as `add`, `modify`, `start`, and `done`. +The main interface to your tasks is the `ta` command, which supports various subcommands such as `add`, `modify`, `start`, and `done`. Customizable [reports](./reports.md) are also available as subcommands, such as `next`. The command reads a [configuration file](./config-file.md) for its settings, including where to find the task database. And the `sync` subcommand [synchronizes tasks with a sync server](./task-sync.md). -You can find a list of all subcommands, as well as the built-in reports, with `task help`. +You can find a list of all subcommands, as well as the built-in reports, with `ta help`. > NOTE: the `task` interface does not precisely match that of TaskWarrior. diff --git a/docs/src/welcome.md b/docs/src/welcome.md index fc6d33149..0c21e9f53 100644 --- a/docs/src/welcome.md +++ b/docs/src/welcome.md @@ -1,7 +1,7 @@ # TaskChampion TaskChampion is a personal task-tracking tool. -It works from the command line, with simple commands like `task add "fix the kitchen sink"`. +It works from the command line, with simple commands like `ta add "fix the kitchen sink"`. It can synchronize tasks on multiple devices, and does so in an "offline" mode so you can update your tasks even when you can't reach the server. If you've heard of [TaskWarrior](https://taskwarrior.org/), this tool is very similar, but with some different design choices and greater reliability. @@ -10,18 +10,18 @@ If you've heard of [TaskWarrior](https://taskwarrior.org/), this tool is very si > NOTE: TaskChampion is still in development and not yet feature-complete. > This section is limited to completed functionality. -Once you've [installed TaskChampion](./installation.md), your interface will be via the `task` command. +Once you've [installed TaskChampion](./installation.md), your interface will be via the `ta` command. Start by adding a task: ```shell -$ task add learn how to use taskchampion +$ ta add learn how to use taskchampion added task ba57deaf-f97b-4e9c-b9ab-04bc1ecb22b8 ``` -You can see all of your pending tasks with `task next`, or just `task` for short: +You can see all of your pending tasks with `ta next`, or just `ta` for short: ```shell -$ task +$ ta Id Description Active Tags 1 learn how to use taskchampion ``` @@ -29,13 +29,13 @@ $ task Tell TaskChampion you're working on the task, using the shorthand id: ```shell -$ task start 1 +$ ta start 1 ``` and when you're done with the task, mark it as complete: ```shell -$ task done 1 +$ ta done 1 ``` ## Synchronizing @@ -44,7 +44,7 @@ Even if you don't have a server, it's a good idea to sync your task database per This acts as a backup and also enables some internal house-cleaning. ```shell -$ task sync +$ ta sync ``` Typically sync is run from a crontab, on whatever schedule fits your needs. @@ -57,7 +57,7 @@ server_client_key: "f8d4d09d-f6c7-4dd2-ab50-634ed20a3ff2" server_origin: "https://taskchampion.example.com" ``` -The next run of `task sync` will upload your task history to that server. -Configuring another device identically and running `task sync` will download that task history, and continue to stay in sync with subsequent runs of the command. +The next run of `ta sync` will upload your task history to that server. +Configuring another device identically and running `ta sync` will download that task history, and continue to stay in sync with subsequent runs of the command. See [Usage](./using-task-command.md) for more detailed information on using TaskChampion. From 1d0ed132df1aafa7f25a81ba96eb890076e1fec7 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 5 May 2021 16:55:21 +0000 Subject: [PATCH 243/548] remove spurious dependency from mdbook-deploy workflow Fixes #227. --- .github/workflows/publish-docs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 933386615..a9fed18ba 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -8,7 +8,6 @@ on: jobs: mdbook-deploy: runs-on: ubuntu-latest - needs: mdbook steps: - uses: actions/checkout@v1 From 7ff54ed0dedc5b20a2dc696074304f80b6b93130 Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 7 May 2021 22:24:55 +1000 Subject: [PATCH 244/548] WIP. Open new connection for each transaction as workaround --- sync-server/src/storage/sqlite.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index a6eaa0df4..98ac5feab 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -103,20 +103,20 @@ impl SqliteStorage { impl Storage for SqliteStorage { fn txn<'a>(&'a self) -> anyhow::Result> { - let mut con = self.new_connection()?; - let mut t = Txn{con, txn: None}; + let con = self.new_connection()?; + let t = Txn{con, txn: None}; Ok(Box::new(t)) } } struct Txn<'t> { con: Connection, - txn: Option<&'t rusqlite::Transaction<'t>>, + txn: Option>, } impl <'t>Txn<'t> { - fn get_txn(&mut self) -> Result<&'t rusqlite::Transaction, SqliteError> { - Ok(&self.con.transaction().unwrap()) + fn get_txn(&mut self) -> Result { + Ok(self.con.transaction().unwrap()) } } @@ -171,20 +171,28 @@ impl <'t>StorageTxn for Txn<'t> { parent_version_id: Uuid, history_segment: Vec, ) -> anyhow::Result<()> { + let t = self.get_txn()?; + let version = Version { version_id, parent_version_id, history_segment, }; - todo!(); + + t.execute( + "INSERT INTO versions (client_key, id, parent, history_segment)", + params![], + ).context("Add version query")?; + Ok(()) } fn commit(&mut self) -> anyhow::Result<()> { - let t = self + let t: rusqlite::Transaction = self .txn .take() - .ok_or(SqliteError::TransactionAlreadyCommitted)?; + .unwrap(); + //.ok_or(SqliteError::TransactionAlreadyCommitted)?; t.commit().context("Committing transaction")?; Ok(()) } From 7aea34c7a3c482717032f355fdd4e7b3157dad39 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 7 May 2021 11:16:10 -0400 Subject: [PATCH 245/548] Document environment variables all in one place --- docs/src/SUMMARY.md | 2 +- docs/src/config-file.md | 2 +- docs/src/debugging.md | 9 --------- docs/src/environment.md | 21 +++++++++++++++++++++ 4 files changed, 23 insertions(+), 11 deletions(-) delete mode 100644 docs/src/debugging.md create mode 100644 docs/src/environment.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 5d47ac45a..f69280b27 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -6,9 +6,9 @@ * [Configuration](./config-file.md) * [Reports](./reports.md) * [Tags](./tags.md) + * [Environment](./environment.md) * [Synchronization](./task-sync.md) * [Running the Sync Server](./running-sync-server.md) - * [Debugging](./debugging.md) - [Internal Details](./internals.md) * [Data Model](./data-model.md) * [Replica Storage](./storage.md) diff --git a/docs/src/config-file.md b/docs/src/config-file.md index bb3ba31d3..90a892805 100644 --- a/docs/src/config-file.md +++ b/docs/src/config-file.md @@ -6,7 +6,7 @@ Configuration is read from `taskchampion.toml` in your config directory. On Linux systems, that directory is `~/.config`. On OS X, it's `~/Library/Preferences`. On Windows, it's `AppData/Roaming` in your home directory. -This can be overridden by setting `$TASKCHAMPION_CONFIG` to the configuration filename. +This can be overridden by setting `TASKCHAMPION_CONFIG` to the configuration filename. The file format is [TOML](https://toml.io/). For example: diff --git a/docs/src/debugging.md b/docs/src/debugging.md deleted file mode 100644 index d81c9d746..000000000 --- a/docs/src/debugging.md +++ /dev/null @@ -1,9 +0,0 @@ -# Debugging - -Both `ta` and `taskchampion-sync-server` use [env-logger](https://docs.rs/env_logger) and can be configured to log at various levels with the `RUST_LOG` environment variable. -For example: -```shell -$ RUST_LOG=taskchampion=trace ta add foo -``` - -The output may provide valuable clues in debugging problems. diff --git a/docs/src/environment.md b/docs/src/environment.md new file mode 100644 index 000000000..14fea8aba --- /dev/null +++ b/docs/src/environment.md @@ -0,0 +1,21 @@ +# Environment Variables + +## Configuration + +Set `TASKCHAMPION_CONFIG` to the location of a configuration file in order to override the default location. + +## Terminal Output + +Taskchampion uses [termcolor](https://github.com/BurntSushi/termcolor) to color its output. +This library interprets [`TERM` and `NO_COLOR`](https://github.com/BurntSushi/termcolor#automatic-color-selection) to determine how it should behave, when writing to a tty. +Set `NO_COLOR` to any value to force plain-text output. + +## Debugging + +Both `ta` and `taskchampion-sync-server` use [env-logger](https://docs.rs/env_logger) and can be configured to log at various levels with the `RUST_LOG` environment variable. +For example: +```shell +$ RUST_LOG=taskchampion=trace ta add foo +``` + +The output may provide valuable clues in debugging problems. From 027225d2a3a62dd5443776aa1c0f4820a61cbdde Mon Sep 17 00:00:00 2001 From: dbr Date: Sat, 8 May 2021 22:09:48 +1000 Subject: [PATCH 246/548] More WIP. Workaround for !Send SQLite causing problems as each get_txn creates a new transaction --- sync-server/src/storage/sqlite.rs | 51 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index 98ac5feab..81343b35d 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -1,9 +1,9 @@ use super::{Client, Storage, StorageTxn, Uuid, Version}; +use anyhow::Context; use rusqlite::types::{FromSql, ToSql}; use rusqlite::{params, Connection, OptionalExtension}; use std::path::Path; use std::sync::{Arc, Mutex}; -use anyhow::Context; #[derive(Debug, thiserror::Error)] enum SqliteError { @@ -11,7 +11,6 @@ enum SqliteError { TransactionAlreadyCommitted, } - /// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` struct StoredUuid(Uuid); @@ -50,8 +49,6 @@ impl ToSql for Client { } } - - /// DB Key for versions: concatenation of client_key and parent_version_id type VersionDbKey = [u8; 32]; @@ -89,7 +86,8 @@ impl SqliteStorage { let txn = con.transaction()?; let queries = vec![ - "CREATE TABLE IF NOT EXISTS clients (id INTEGER PRIMARY KEY AUTOINCREMENT, data STRING);", + "CREATE TABLE IF NOT EXISTS clients (client_key STRING PRIMARY KEY, latest_version_id STRING);", + "CREATE TABLE IF NOT EXISTS versions (id STRING PRIMARY KEY, client_key STRING, parent STRING, history_segment STRING);", ]; for q in queries { txn.execute(q, []).context("Creating table")?; @@ -104,7 +102,7 @@ impl SqliteStorage { impl Storage for SqliteStorage { fn txn<'a>(&'a self) -> anyhow::Result> { let con = self.new_connection()?; - let t = Txn{con, txn: None}; + let t = Txn { con, txn: None }; Ok(Box::new(t)) } } @@ -114,23 +112,28 @@ struct Txn<'t> { txn: Option>, } -impl <'t>Txn<'t> { +impl<'t> Txn<'t> { fn get_txn(&mut self) -> Result { Ok(self.con.transaction().unwrap()) } } - -impl <'t>StorageTxn for Txn<'t> { +impl<'t> StorageTxn for Txn<'t> { fn get_client(&mut self, client_key: Uuid) -> anyhow::Result> { let t = self.get_txn()?; let result: Option = t .query_row( - "SELECT data FROM clients WHERE client_key = ? LIMIT 1", + "SELECT latest_version_id FROM clients WHERE client_key = ? LIMIT 1", [&StoredUuid(client_key)], - |r| r.get("data"), + |r| { + let latest_version_id: StoredUuid = r.get(0)?; + Ok(Client { + latest_version_id: latest_version_id.0, + }) + }, ) - .optional()?; + .optional() + .context("Get client query")?; Ok(result) } @@ -138,10 +141,9 @@ impl <'t>StorageTxn for Txn<'t> { fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { let t = self.get_txn()?; - let client = Client{ latest_version_id }; t.execute( "INSERT OR REPLACE INTO clients (client_key, latest_version_id) VALUES (?, ?)", - params![&StoredUuid(latest_version_id), &client], + params![&StoredUuid(client_key), &StoredUuid(latest_version_id)], ) .context("Create client query")?; Ok(()) @@ -173,16 +175,16 @@ impl <'t>StorageTxn for Txn<'t> { ) -> anyhow::Result<()> { let t = self.get_txn()?; - let version = Version { - version_id, - parent_version_id, - history_segment, - }; - t.execute( - "INSERT INTO versions (client_key, id, parent, history_segment)", - params![], - ).context("Add version query")?; + "INSERT INTO versions (id, client_key, parent, history_segment) VALUES(?, ?, ?, ?)", + params![ + StoredUuid(version_id), + StoredUuid(client_key), + StoredUuid(parent_version_id), + history_segment + ], + ) + .context("Add version query")?; Ok(()) } @@ -191,8 +193,7 @@ impl <'t>StorageTxn for Txn<'t> { let t: rusqlite::Transaction = self .txn .take() - .unwrap(); - //.ok_or(SqliteError::TransactionAlreadyCommitted)?; + .ok_or(SqliteError::TransactionAlreadyCommitted)?; t.commit().context("Committing transaction")?; Ok(()) } From ac53383aead42d9feb840dce4d35511e96e417a5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 9 May 2021 21:06:17 -0400 Subject: [PATCH 247/548] remove debugging prints --- cli/src/invocation/report.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 48db2d4e6..36d15574a 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -18,8 +18,6 @@ fn sort_tasks(tasks: &mut Vec, report: &Report, working_set: &WorkingSet) let b_uuid = b.get_uuid(); let a_id = working_set.by_uuid(a_uuid); let b_id = working_set.by_uuid(b_uuid); - println!("a_uuid {} -> a_id {:?}", a_uuid, a_id); - println!("b_uuid {} -> b_id {:?}", b_uuid, b_id); match (a_id, b_id) { (Some(a_id), Some(b_id)) => a_id.cmp(&b_id), (Some(_), None) => Ordering::Less, From febe6d8b687bff0d262c708452f55fccf37a8f39 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 9 May 2021 20:58:33 -0400 Subject: [PATCH 248/548] sort the 'next' repot by id --- cli/src/settings/mod.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cli/src/settings/mod.rs b/cli/src/settings/mod.rs index a164150f4..e6c061562 100644 --- a/cli/src/settings/mod.rs +++ b/cli/src/settings/mod.rs @@ -187,10 +187,16 @@ impl Default for Settings { reports.insert( "next".to_owned(), Report { - sort: vec![Sort { - ascending: true, - sort_by: SortBy::Uuid, - }], + sort: vec![ + Sort { + ascending: true, + sort_by: SortBy::Id, + }, + Sort { + ascending: true, + sort_by: SortBy::Uuid, + }, + ], columns: vec![ Column { label: "id".to_owned(), From 373cef9d33c4b6837bcaf101a9e56307e9feda2d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 14 May 2021 12:51:24 -0400 Subject: [PATCH 249/548] [breaking] Include /v1/ in the sync-server paths This is an incompatible change to the sync-server protocol. --- docs/src/sync-protocol.md | 4 ++-- sync-server/src/api/add_version.rs | 10 +++++----- sync-server/src/api/get_child_version.rs | 8 ++++---- taskchampion/src/server/remote/mod.rs | 7 +++++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md index e96f7cc83..db02d03a2 100644 --- a/docs/src/sync-protocol.md +++ b/docs/src/sync-protocol.md @@ -73,7 +73,7 @@ This value is passed with every request in the `X-Client-Id` header, in its dash ### AddVersion -The request is a `POST` to `/client/add-version/`. +The request is a `POST` to `/v1/client/add-version/`. The request body contains the history segment, optionally encoded using any encoding supported by actix-web. The content-type must be `application/vnd.taskchampion.history-segment`. @@ -87,7 +87,7 @@ Other error responses (4xx or 5xx) may be returned and should be treated appropr ### GetChildVersion -The request is a `GET` to `/client/get-child-version/`. +The request is a `GET` to `/v1/client/get-child-version/`. The response is 404 NOT FOUND if no such version exists. Otherwise, the response is a 200 OK. The version's history segment is returned in the response body, with content-type `application/vnd.taskchampion.history-segment`. diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 4ca7cbdb1..60db1f385 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -19,7 +19,7 @@ const MAX_SIZE: usize = 100 * 1024 * 1024; /// parent version ID in the `X-Parent-Version-Id` header. /// /// Returns other 4xx or 5xx responses on other errors. -#[post("/client/add-version/{parent_version_id}")] +#[post("/v1/client/add-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, server_state: web::Data, @@ -99,7 +99,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/add-version/{}", parent_version_id); + let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( @@ -136,7 +136,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/add-version/{}", parent_version_id); + let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( @@ -163,7 +163,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/add-version/{}", parent_version_id); + let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header("Content-Type", "not/correct") @@ -182,7 +182,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/add-version/{}", parent_version_id); + let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 0bf04d96a..a38113803 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -13,7 +13,7 @@ use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; /// /// If no such child exists, returns a 404 with no content. /// Returns other 4xx or 5xx responses on other errors. -#[get("/client/get-child-version/{parent_version_id}")] +#[get("/v1/client/get-child-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, server_state: web::Data, @@ -68,7 +68,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/get-child-version/{}", parent_version_id); + let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() .uri(&uri) .header("X-Client-Key", client_key.to_string()) @@ -101,7 +101,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/get-child-version/{}", parent_version_id); + let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() .uri(&uri) .header("X-Client-Key", client_key.to_string()) @@ -126,7 +126,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/get-child-version/{}", parent_version_id); + let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() .uri(&uri) .header("X-Client-Key", client_key.to_string()) diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index 8916c7a80..f6fbb4b0a 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -49,7 +49,10 @@ impl Server for RemoteServer { parent_version_id: VersionId, history_segment: HistorySegment, ) -> anyhow::Result { - let url = format!("{}/client/add-version/{}", self.origin, parent_version_id); + let url = format!( + "{}/v1/client/add-version/{}", + self.origin, parent_version_id + ); let history_cleartext = HistoryCleartext { parent_version_id, history_segment, @@ -82,7 +85,7 @@ impl Server for RemoteServer { parent_version_id: VersionId, ) -> anyhow::Result { let url = format!( - "{}/client/get-child-version/{}", + "{}/v1/client/get-child-version/{}", self.origin, parent_version_id ); match self From cbe11a1d3d130f468b090552f710b5bb17f42a8a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 16 May 2021 09:38:40 -0400 Subject: [PATCH 250/548] fix new clippy warnings --- taskchampion/src/taskdb.rs | 6 ++---- taskchampion/src/workingset.rs | 8 +------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index f4850f487..06c1ff6d9 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -144,10 +144,8 @@ impl TaskDb { // if renumbering, clear the working set and re-add if renumber { txn.clear_working_set()?; - for elt in new_ws.drain(0..new_ws.len()) { - if let Some(uuid) = elt { - txn.add_to_working_set(uuid)?; - } + for uuid in new_ws.drain(0..new_ws.len()).flatten() { + txn.add_to_working_set(uuid)?; } } else { // ..otherwise, just clear the None items determined above from the working set diff --git a/taskchampion/src/workingset.rs b/taskchampion/src/workingset.rs index 5bdc3696b..586a16cf4 100644 --- a/taskchampion/src/workingset.rs +++ b/taskchampion/src/workingset.rs @@ -58,13 +58,7 @@ impl WorkingSet { self.by_index .iter() .enumerate() - .filter_map(|(index, uuid)| { - if let Some(uuid) = uuid { - Some((index, *uuid)) - } else { - None - } - }) + .filter_map(|(index, uuid)| uuid.as_ref().map(|uuid| (index, *uuid))) } } From fa9e6ddcd54cc72ee7b16ac120f715b710beea5d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 7 May 2021 22:35:42 +0000 Subject: [PATCH 251/548] Don't unwrap in production code --- cli/src/invocation/cmd/sync.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index 9a1bb18d2..2e9400642 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -6,7 +6,7 @@ pub(crate) fn execute( replica: &mut Replica, server: &mut Box, ) -> anyhow::Result<()> { - replica.sync(server).unwrap(); + replica.sync(server)?; writeln!(w, "sync complete.")?; Ok(()) } From 3a2450cb231a0bf078b44c81f63e74268405fde6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 7 May 2021 22:36:10 +0000 Subject: [PATCH 252/548] provide context for errors to help debugging --- taskchampion/src/replica.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index ac1baea88..1e0a47382 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -4,6 +4,7 @@ use crate::storage::{Operation, Storage, TaskMap}; use crate::task::{Status, Task}; use crate::taskdb::TaskDb; use crate::workingset::WorkingSet; +use anyhow::Context; use chrono::Utc; use log::trace; use std::collections::HashMap; @@ -123,8 +124,10 @@ impl Replica { /// this occurs, but without renumbering, so any newly-pending tasks should appear in /// the working set. pub fn sync(&mut self, server: &mut Box) -> anyhow::Result<()> { - self.taskdb.sync(server)?; + self.taskdb.sync(server).context("Failed to synchronize")?; self.rebuild_working_set(false) + .context("Failed to rebuild working set after sync")?; + Ok(()) } /// Rebuild this replica's working set, based on whether tasks are pending or not. If From fa7623ebe741f177956ead740d8491769c5cac1b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 7 May 2021 22:36:23 +0000 Subject: [PATCH 253/548] Handle setting a working set item to None twice Without this, setting an item to None that did not already exist failed, because the kv delete operation did not find the referenced key. This also checks that the index is not 0, which is not allowed as the working set is 1-indexed. --- taskchampion/src/storage/kv.rs | 84 +++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/taskchampion/src/storage/kv.rs b/taskchampion/src/storage/kv.rs index e65e31dfa..505e3f095 100644 --- a/taskchampion/src/storage/kv.rs +++ b/taskchampion/src/storage/kv.rs @@ -308,7 +308,7 @@ impl<'t> StorageTxn for Txn<'t> { Err(e) => return Err(e.into()), }; - if index >= next_index { + if index < 1 || index >= next_index { anyhow::bail!("Index {} is not in the working set", index); } @@ -319,7 +319,11 @@ impl<'t> StorageTxn for Txn<'t> { Msgpack::to_value_buf(uuid)?, )?; } else { - kvtxn.del(working_set_bucket, index.into())?; + match kvtxn.del(working_set_bucket, index.into()) { + Ok(_) => {} + Err(Error::NotFound) => {} + Err(e) => return Err(e.into()), + }; } Ok(()) @@ -650,6 +654,82 @@ mod test { Ok(()) } + #[test] + fn set_working_set_item() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = KvStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1)?; + txn.add_to_working_set(uuid2)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + txn.set_working_set_item(1, Some(uuid2))?; + txn.set_working_set_item(2, None)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, Some(uuid2), None]); + } + + Ok(()) + } + + #[test] + fn set_working_set_item_nonexistent() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = KvStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + txn.set_working_set_item(1, None)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + // set it to None again, to check idempotency + txn.set_working_set_item(1, None)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, None]); + } + + Ok(()) + } + + #[test] + fn set_working_set_item_zero() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = KvStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + + let mut txn = storage.txn()?; + assert!(txn.set_working_set_item(0, Some(uuid1)).is_err()); + + Ok(()) + } + #[test] fn clear_working_set() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; From 5f6918fbc7097e5b891164ca2db2ab3cde752f05 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 7 May 2021 22:37:40 +0000 Subject: [PATCH 254/548] Skip element 0 when rebuilding the working set The existing code was correct, assuming that element 0 is always None, but this is clearer. --- taskchampion/src/taskdb.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 06c1ff6d9..ef5fb6c8d 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -117,14 +117,14 @@ impl TaskDb { { let mut txn = self.storage.txn()?; - let mut new_ws = vec![]; + let mut new_ws = vec![None]; // index 0 is always None let mut seen = HashSet::new(); // The goal here is for existing working-set items to be "compressed' down to index 1, so // we begin by scanning the current working set and inserting any tasks that should still // be in the set into new_ws, implicitly dropping any tasks that are no longer in the // working set. - for elt in txn.get_working_set()? { + for elt in txn.get_working_set()?.drain(1..) { if let Some(uuid) = elt { if let Some(task) = txn.get_task(uuid)? { if in_working_set(&task) { @@ -144,12 +144,12 @@ impl TaskDb { // if renumbering, clear the working set and re-add if renumber { txn.clear_working_set()?; - for uuid in new_ws.drain(0..new_ws.len()).flatten() { - txn.add_to_working_set(uuid)?; + for elt in new_ws.drain(1..new_ws.len()).flatten() { + txn.add_to_working_set(elt)?; } } else { // ..otherwise, just clear the None items determined above from the working set - for (i, elt) in new_ws.iter().enumerate() { + for (i, elt) in new_ws.iter().enumerate().skip(1) { if elt.is_none() { txn.set_working_set_item(i, None)?; } From 73b6648d06a181b52b597e6968217b5011462197 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 7 May 2021 22:40:48 +0000 Subject: [PATCH 255/548] assert that working-set element 0 is None --- taskchampion/src/workingset.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/taskchampion/src/workingset.rs b/taskchampion/src/workingset.rs index 586a16cf4..04aa4dcc5 100644 --- a/taskchampion/src/workingset.rs +++ b/taskchampion/src/workingset.rs @@ -21,6 +21,10 @@ impl WorkingSet { /// Create a new WorkingSet. Typically this is acquired via `replica.working_set()` pub(crate) fn new(by_index: Vec>) -> Self { let mut by_uuid = HashMap::new(); + + // working sets are 1-indexed, so element 0 should always be None + assert!(by_index.is_empty() || by_index[0].is_none()); + for (index, uuid) in by_index.iter().enumerate() { if let Some(uuid) = uuid { by_uuid.insert(*uuid, index); From 09efb330732e8437353b2018667b52958061e6f6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 5 May 2021 14:21:01 -0400 Subject: [PATCH 256/548] move Settings to its own module ..and add some tests for it --- cli/src/lib.rs | 1 + cli/src/settings/mod.rs | 226 +-------------------------- cli/src/settings/settings.rs | 287 +++++++++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+), 224 deletions(-) create mode 100644 cli/src/settings/settings.rs diff --git a/cli/src/lib.rs b/cli/src/lib.rs index a846fa9f1..2890e4b9f 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,5 +1,6 @@ #![deny(clippy::all)] #![allow(clippy::unnecessary_wraps)] // for Rust 1.50, https://github.com/rust-lang/rust-clippy/pull/6765 +#![allow(clippy::module_inception)] // we use re-exports to shorten stuttering paths like settings::settings::Settings /*! This crate implements the command-line interface to TaskChampion. diff --git a/cli/src/settings/mod.rs b/cli/src/settings/mod.rs index e6c061562..896de38b8 100644 --- a/cli/src/settings/mod.rs +++ b/cli/src/settings/mod.rs @@ -4,230 +4,8 @@ //! startup and not just when those values are used. mod report; +mod settings; mod util; -use crate::argparse::{Condition, Filter}; -use anyhow::{anyhow, Context, Result}; -use std::collections::HashMap; -use std::convert::TryFrom; -use std::env; -use std::fs; -use std::path::PathBuf; -use taskchampion::Status; -use toml::value::Table; -use util::table_with_keys; - pub(crate) use report::{Column, Property, Report, Sort, SortBy}; - -#[derive(Debug)] -pub(crate) struct Settings { - // replica - pub(crate) data_dir: PathBuf, - - // remote sync server - pub(crate) server_client_key: Option, - pub(crate) server_origin: Option, - pub(crate) encryption_secret: Option, - - // local sync server - pub(crate) server_dir: PathBuf, - - // reports - pub(crate) reports: HashMap, -} - -impl Settings { - pub(crate) fn read() -> Result { - if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") { - log::debug!("Loading configuration from {:?}", config_file); - env::remove_var("TASKCHAMPION_CONFIG"); - Self::load_from_file(config_file.into(), true) - } else if let Some(mut dir) = dirs_next::config_dir() { - dir.push("taskchampion.toml"); - log::debug!("Loading configuration from {:?} (optional)", dir); - Self::load_from_file(dir, false) - } else { - Ok(Default::default()) - } - } - - fn load_from_file(config_file: PathBuf, required: bool) -> Result { - let mut settings = Self::default(); - - let config_toml = match fs::read_to_string(config_file.clone()) { - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - return if required { - Err(e.into()) - } else { - Ok(settings) - }; - } - Err(e) => return Err(e.into()), - Ok(s) => s, - }; - - let config_toml = config_toml - .parse::() - .with_context(|| format!("error while reading {:?}", config_file))?; - settings - .update_from_toml(&config_toml) - .with_context(|| format!("error while parsing {:?}", config_file))?; - - Ok(settings) - } - - /// Update this object with configuration from the given config file. This is - /// broken out mostly for convenience in error handling - fn update_from_toml(&mut self, config_toml: &toml::Value) -> Result<()> { - let table_keys = [ - "data_dir", - "server_client_key", - "server_origin", - "encryption_secret", - "server_dir", - "reports", - ]; - let table = table_with_keys(&config_toml, &table_keys)?; - - fn get_str_cfg( - table: &Table, - name: &'static str, - setter: F, - ) -> Result<()> { - if let Some(v) = table.get(name) { - setter( - v.as_str() - .ok_or_else(|| anyhow!(".{}: not a string", name))? - .to_owned(), - ); - } - Ok(()) - } - - get_str_cfg(table, "data_dir", |v| { - self.data_dir = v.into(); - })?; - - get_str_cfg(table, "server_client_key", |v| { - self.server_client_key = Some(v); - })?; - - get_str_cfg(table, "server_origin", |v| { - self.server_origin = Some(v); - })?; - - get_str_cfg(table, "encryption_secret", |v| { - self.encryption_secret = Some(v); - })?; - - get_str_cfg(table, "server_dir", |v| { - self.server_dir = v.into(); - })?; - - if let Some(v) = table.get("reports") { - let report_cfgs = v - .as_table() - .ok_or_else(|| anyhow!(".reports: not a table"))?; - for (name, cfg) in report_cfgs { - let report = Report::try_from(cfg).map_err(|e| anyhow!("reports.{}{}", name, e))?; - self.reports.insert(name.clone(), report); - } - } - - Ok(()) - } -} - -impl Default for Settings { - fn default() -> Self { - let data_dir; - let server_dir; - - if let Some(dir) = dirs_next::data_local_dir() { - data_dir = dir.join("taskchampion"); - server_dir = dir.join("taskchampion-sync-server"); - } else { - // fallback - data_dir = PathBuf::from("."); - server_dir = PathBuf::from("."); - } - - // define the default reports - let mut reports = HashMap::new(); - - reports.insert( - "list".to_owned(), - Report { - sort: vec![Sort { - ascending: true, - sort_by: SortBy::Uuid, - }], - columns: vec![ - Column { - label: "id".to_owned(), - property: Property::Id, - }, - Column { - label: "description".to_owned(), - property: Property::Description, - }, - Column { - label: "active".to_owned(), - property: Property::Active, - }, - Column { - label: "tags".to_owned(), - property: Property::Tags, - }, - ], - filter: Default::default(), - }, - ); - - reports.insert( - "next".to_owned(), - Report { - sort: vec![ - Sort { - ascending: true, - sort_by: SortBy::Id, - }, - Sort { - ascending: true, - sort_by: SortBy::Uuid, - }, - ], - columns: vec![ - Column { - label: "id".to_owned(), - property: Property::Id, - }, - Column { - label: "description".to_owned(), - property: Property::Description, - }, - Column { - label: "active".to_owned(), - property: Property::Active, - }, - Column { - label: "tags".to_owned(), - property: Property::Tags, - }, - ], - filter: Filter { - conditions: vec![Condition::Status(Status::Pending)], - }, - }, - ); - - Self { - data_dir, - server_client_key: None, - server_origin: None, - encryption_secret: None, - server_dir, - reports, - } - } -} +pub(crate) use settings::Settings; diff --git a/cli/src/settings/settings.rs b/cli/src/settings/settings.rs new file mode 100644 index 000000000..1c5a2b9dd --- /dev/null +++ b/cli/src/settings/settings.rs @@ -0,0 +1,287 @@ +use super::util::table_with_keys; +use super::{Column, Property, Report, Sort, SortBy}; +use crate::argparse::{Condition, Filter}; +use anyhow::{anyhow, Context, Result}; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::env; +use std::fs; +use std::path::PathBuf; +use taskchampion::Status; +use toml::value::Table; + +#[derive(Debug, PartialEq)] +pub(crate) struct Settings { + // replica + pub(crate) data_dir: PathBuf, + + // remote sync server + pub(crate) server_client_key: Option, + pub(crate) server_origin: Option, + pub(crate) encryption_secret: Option, + + // local sync server + pub(crate) server_dir: PathBuf, + + // reports + pub(crate) reports: HashMap, +} + +impl Settings { + pub(crate) fn read() -> Result { + if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") { + log::debug!("Loading configuration from {:?}", config_file); + env::remove_var("TASKCHAMPION_CONFIG"); + Self::load_from_file(config_file.into(), true) + } else if let Some(mut dir) = dirs_next::config_dir() { + dir.push("taskchampion.toml"); + log::debug!("Loading configuration from {:?} (optional)", dir); + Self::load_from_file(dir, false) + } else { + Ok(Default::default()) + } + } + + fn load_from_file(config_file: PathBuf, required: bool) -> Result { + let mut settings = Self::default(); + + let config_toml = match fs::read_to_string(config_file.clone()) { + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + return if required { + Err(e.into()) + } else { + Ok(settings) + }; + } + Err(e) => return Err(e.into()), + Ok(s) => s, + }; + + let config_toml = config_toml + .parse::() + .with_context(|| format!("error while reading {:?}", config_file))?; + settings + .update_from_toml(&config_toml) + .with_context(|| format!("error while parsing {:?}", config_file))?; + + Ok(settings) + } + + /// Update this object with configuration from the given config file. This is + /// broken out mostly for convenience in error handling + fn update_from_toml(&mut self, config_toml: &toml::Value) -> Result<()> { + let table_keys = [ + "data_dir", + "server_client_key", + "server_origin", + "encryption_secret", + "server_dir", + "reports", + ]; + let table = table_with_keys(&config_toml, &table_keys)?; + + fn get_str_cfg( + table: &Table, + name: &'static str, + setter: F, + ) -> Result<()> { + if let Some(v) = table.get(name) { + setter( + v.as_str() + .ok_or_else(|| anyhow!(".{}: not a string", name))? + .to_owned(), + ); + } + Ok(()) + } + + get_str_cfg(table, "data_dir", |v| { + self.data_dir = v.into(); + })?; + + get_str_cfg(table, "server_client_key", |v| { + self.server_client_key = Some(v); + })?; + + get_str_cfg(table, "server_origin", |v| { + self.server_origin = Some(v); + })?; + + get_str_cfg(table, "encryption_secret", |v| { + self.encryption_secret = Some(v); + })?; + + get_str_cfg(table, "server_dir", |v| { + self.server_dir = v.into(); + })?; + + if let Some(v) = table.get("reports") { + let report_cfgs = v + .as_table() + .ok_or_else(|| anyhow!(".reports: not a table"))?; + for (name, cfg) in report_cfgs { + let report = Report::try_from(cfg).map_err(|e| anyhow!("reports.{}{}", name, e))?; + self.reports.insert(name.clone(), report); + } + } + + Ok(()) + } +} + +impl Default for Settings { + fn default() -> Self { + let data_dir; + let server_dir; + + if let Some(dir) = dirs_next::data_local_dir() { + data_dir = dir.join("taskchampion"); + server_dir = dir.join("taskchampion-sync-server"); + } else { + // fallback + data_dir = PathBuf::from("."); + server_dir = PathBuf::from("."); + } + + // define the default reports + let mut reports = HashMap::new(); + + reports.insert( + "list".to_owned(), + Report { + sort: vec![Sort { + ascending: true, + sort_by: SortBy::Uuid, + }], + columns: vec![ + Column { + label: "id".to_owned(), + property: Property::Id, + }, + Column { + label: "description".to_owned(), + property: Property::Description, + }, + Column { + label: "active".to_owned(), + property: Property::Active, + }, + Column { + label: "tags".to_owned(), + property: Property::Tags, + }, + ], + filter: Default::default(), + }, + ); + + reports.insert( + "next".to_owned(), + Report { + sort: vec![ + Sort { + ascending: true, + sort_by: SortBy::Id, + }, + Sort { + ascending: true, + sort_by: SortBy::Uuid, + }, + ], + columns: vec![ + Column { + label: "id".to_owned(), + property: Property::Id, + }, + Column { + label: "description".to_owned(), + property: Property::Description, + }, + Column { + label: "active".to_owned(), + property: Property::Active, + }, + Column { + label: "tags".to_owned(), + property: Property::Tags, + }, + ], + filter: Filter { + conditions: vec![Condition::Status(Status::Pending)], + }, + }, + ); + + Self { + data_dir, + server_client_key: None, + server_origin: None, + encryption_secret: None, + server_dir, + reports, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use tempfile::TempDir; + use toml::toml; + + #[test] + fn test_load_from_file_not_required() { + let cfg_dir = TempDir::new().unwrap(); + + let settings = Settings::load_from_file(cfg_dir.path().join("foo.toml"), false).unwrap(); + assert_eq!(settings, Settings::default()); + } + + #[test] + fn test_load_from_file_required() { + let cfg_dir = TempDir::new().unwrap(); + + assert!(Settings::load_from_file(cfg_dir.path().join("foo.toml"), true).is_err()); + } + + #[test] + fn test_load_from_file_exists() { + let cfg_dir = TempDir::new().unwrap(); + fs::write(cfg_dir.path().join("foo.toml"), "data_dir = \"/nowhere\"").unwrap(); + + let settings = Settings::load_from_file(cfg_dir.path().join("foo.toml"), true).unwrap(); + assert_eq!(settings.data_dir, PathBuf::from("/nowhere")); + } + + #[test] + fn test_update_from_toml_top_level_keys() { + let val = toml! { + data_dir = "/data" + server_client_key = "sck" + server_origin = "so" + encryption_secret = "es" + server_dir = "/server" + }; + let mut settings = Settings::default(); + settings.update_from_toml(&val).unwrap(); + + assert_eq!(settings.data_dir, PathBuf::from("/data")); + assert_eq!(settings.server_client_key, Some("sck".to_owned())); + assert_eq!(settings.server_origin, Some("so".to_owned())); + assert_eq!(settings.encryption_secret, Some("es".to_owned())); + assert_eq!(settings.server_dir, PathBuf::from("/server")); + } + + #[test] + fn test_update_from_toml_report() { + let val = toml! { + [reports.foo] + sort = [ { sort_by = "id" } ] + columns = [ { label = "ID", property = "id" } ] + }; + let mut settings = Settings::default(); + settings.update_from_toml(&val).unwrap(); + + assert!(settings.reports.get("foo").is_some()); + } +} From a778423cbcd59c3d350c15675398fced8ff28920 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 5 May 2021 14:41:25 -0400 Subject: [PATCH 257/548] store the filename of the loaded config file --- cli/src/settings/settings.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/cli/src/settings/settings.rs b/cli/src/settings/settings.rs index 1c5a2b9dd..935e5e8ab 100644 --- a/cli/src/settings/settings.rs +++ b/cli/src/settings/settings.rs @@ -12,6 +12,9 @@ use toml::value::Table; #[derive(Debug, PartialEq)] pub(crate) struct Settings { + // filename from which this configuration was loaded, if any + pub(crate) filename: Option, + // replica pub(crate) data_dir: PathBuf, @@ -33,15 +36,24 @@ impl Settings { log::debug!("Loading configuration from {:?}", config_file); env::remove_var("TASKCHAMPION_CONFIG"); Self::load_from_file(config_file.into(), true) - } else if let Some(mut dir) = dirs_next::config_dir() { - dir.push("taskchampion.toml"); - log::debug!("Loading configuration from {:?} (optional)", dir); - Self::load_from_file(dir, false) + } else if let Some(filename) = Settings::default_filename() { + log::debug!("Loading configuration from {:?} (optional)", filename); + Self::load_from_file(filename, false) } else { Ok(Default::default()) } } + /// Get the default filename for the configuration, or None if that cannot + /// be determined. + pub(crate) fn default_filename() -> Option { + if let Some(dir) = dirs_next::config_dir() { + Some(dir.join("taskchampion.toml")) + } else { + None + } + } + fn load_from_file(config_file: PathBuf, required: bool) -> Result { let mut settings = Self::default(); @@ -60,6 +72,8 @@ impl Settings { let config_toml = config_toml .parse::() .with_context(|| format!("error while reading {:?}", config_file))?; + + settings.filename = Some(config_file.clone()); settings .update_from_toml(&config_toml) .with_context(|| format!("error while parsing {:?}", config_file))?; @@ -213,6 +227,7 @@ impl Default for Settings { ); Self { + filename: None, data_dir, server_client_key: None, server_origin: None, @@ -247,10 +262,12 @@ mod test { #[test] fn test_load_from_file_exists() { let cfg_dir = TempDir::new().unwrap(); - fs::write(cfg_dir.path().join("foo.toml"), "data_dir = \"/nowhere\"").unwrap(); + let cfg_file = cfg_dir.path().join("foo.toml"); + fs::write(cfg_file.clone(), "data_dir = \"/nowhere\"").unwrap(); - let settings = Settings::load_from_file(cfg_dir.path().join("foo.toml"), true).unwrap(); + let settings = Settings::load_from_file(cfg_file.clone(), true).unwrap(); assert_eq!(settings.data_dir, PathBuf::from("/nowhere")); + assert_eq!(settings.filename, Some(cfg_file)); } #[test] @@ -283,5 +300,6 @@ mod test { settings.update_from_toml(&val).unwrap(); assert!(settings.reports.get("foo").is_some()); + // beyond existence of this report, we can rely on Report's unit tests } } From fd62c8327b223f4f73597d7ce6dc0bed6eb9b894 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 5 May 2021 14:18:17 -0400 Subject: [PATCH 258/548] Add a `ta config set` subcommand This uses `toml_edit` to edit the config file in-place. For the moment, it only supports top-level arguments, but can be extended to do other things later. --- Cargo.lock | 46 +++++++++++++++++++++ cli/Cargo.toml | 1 + cli/src/argparse/config.rs | 36 +++++++++++++++++ cli/src/argparse/mod.rs | 2 + cli/src/argparse/subcommand.rs | 42 ++++++++++++++++++- cli/src/invocation/cmd/config.rs | 62 ++++++++++++++++++++++++++++ cli/src/invocation/cmd/mod.rs | 1 + cli/src/invocation/mod.rs | 8 ++++ cli/src/settings/settings.rs | 69 +++++++++++++++++++++++++++++--- 9 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 cli/src/argparse/config.rs create mode 100644 cli/src/invocation/cmd/config.rs diff --git a/Cargo.lock b/Cargo.lock index 3e2127449..8a50e2d2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -324,6 +324,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "ascii" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" + [[package]] name = "assert_cmd" version = "1.0.3" @@ -573,6 +579,19 @@ dependencies = [ "vec_map", ] +[[package]] +name = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] + [[package]] name = "const_fn" version = "0.4.6" @@ -2158,6 +2177,7 @@ dependencies = [ "termcolor", "textwrap 0.13.4", "toml", + "toml_edit", ] [[package]] @@ -2404,6 +2424,17 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09391a441b373597cf0888d2b052dcf82c5be4fee05da3636ae30fb57aad8484" +dependencies = [ + "chrono", + "combine", + "linked-hash-map", +] + [[package]] name = "tracing" version = "0.1.25" @@ -2522,6 +2553,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -2578,6 +2618,12 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8d07f7e21..d66cc63b9 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,6 +15,7 @@ textwrap = { version="^0.13.4", features=["terminal_size"] } termcolor = "^1.1.2" atty = "^0.2.14" toml = "^0.5.8" +toml_edit = "^0.2.0" [dependencies.taskchampion] path = "../taskchampion" diff --git a/cli/src/argparse/config.rs b/cli/src/argparse/config.rs new file mode 100644 index 000000000..924164564 --- /dev/null +++ b/cli/src/argparse/config.rs @@ -0,0 +1,36 @@ +use super::args::{any, arg_matching, literal}; +use super::ArgList; +use crate::usage; +use nom::{combinator::*, sequence::*, IResult}; + +#[derive(Debug, PartialEq)] +/// A config operation +pub(crate) enum ConfigOperation { + /// Set a configuration value + Set(String, String), +} + +impl ConfigOperation { + pub(super) fn parse(input: ArgList) -> IResult { + fn set_to_op(input: (&str, &str, &str)) -> Result { + Ok(ConfigOperation::Set(input.1.to_owned(), input.2.to_owned())) + } + map_res( + tuple(( + arg_matching(literal("set")), + arg_matching(any), + arg_matching(any), + )), + set_to_op, + )(input) + } + + pub(super) fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "config set", + syntax: "config set ", + summary: "Set a configuration value", + description: "Update Taskchampion configuration file to set key = value", + }); + } +} diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index 3bdac1a8f..88de59046 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -18,12 +18,14 @@ That is, they contain no references, and have no methods to aid in their executi */ mod args; mod command; +mod config; mod filter; mod modification; mod subcommand; pub(crate) use args::TaskId; pub(crate) use command::Command; +pub(crate) use config::ConfigOperation; pub(crate) use filter::{Condition, Filter}; pub(crate) use modification::{DescriptionMod, Modification}; pub(crate) use subcommand::Subcommand; diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 1d7ddc2ba..5609192b7 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -1,5 +1,5 @@ use super::args::*; -use super::{ArgList, DescriptionMod, Filter, Modification}; +use super::{ArgList, ConfigOperation, DescriptionMod, Filter, Modification}; use crate::usage; use nom::{branch::alt, combinator::*, sequence::*, IResult}; use taskchampion::Status; @@ -25,6 +25,11 @@ pub(crate) enum Subcommand { summary: bool, }, + /// Manipulate configuration + Config { + config_operation: ConfigOperation, + }, + /// Add a new task Add { modification: Modification, @@ -61,6 +66,7 @@ impl Subcommand { all_consuming(alt(( Version::parse, Help::parse, + Config::parse, Add::parse, Modify::parse, Info::parse, @@ -74,6 +80,7 @@ impl Subcommand { pub(super) fn get_usage(u: &mut usage::Usage) { Version::get_usage(u); Help::get_usage(u); + Config::get_usage(u); Add::get_usage(u); Modify::get_usage(u); Info::get_usage(u); @@ -131,6 +138,26 @@ impl Help { fn get_usage(_u: &mut usage::Usage) {} } +struct Config; + +impl Config { + fn parse(input: ArgList) -> IResult { + fn to_subcommand(input: (&str, ConfigOperation)) -> Result { + Ok(Subcommand::Config { + config_operation: input.1, + }) + } + map_res( + tuple((arg_matching(literal("config")), ConfigOperation::parse)), + to_subcommand, + )(input) + } + + fn get_usage(u: &mut usage::Usage) { + ConfigOperation::get_usage(u); + } +} + struct Add; impl Add { @@ -427,6 +454,19 @@ mod test { ); } + #[test] + fn test_config_set() { + assert_eq!( + Subcommand::parse(argv!["config", "set", "x", "y"]).unwrap(), + ( + &EMPTY[..], + Subcommand::Config { + config_operation: ConfigOperation::Set("x".to_owned(), "y".to_owned()) + } + ) + ); + } + #[test] fn test_add_description() { let subcommand = Subcommand::Add { diff --git a/cli/src/invocation/cmd/config.rs b/cli/src/invocation/cmd/config.rs new file mode 100644 index 000000000..0f34defae --- /dev/null +++ b/cli/src/invocation/cmd/config.rs @@ -0,0 +1,62 @@ +use crate::argparse::ConfigOperation; +use crate::settings::Settings; +use termcolor::{ColorSpec, WriteColor}; + +pub(crate) fn execute( + w: &mut W, + config_operation: ConfigOperation, + settings: &Settings, +) -> anyhow::Result<()> { + match config_operation { + ConfigOperation::Set(key, value) => { + let filename = settings.set(&key, &value)?; + write!(w, "Set configuration value ")?; + w.set_color(ColorSpec::new().set_bold(true))?; + write!(w, "{}", &key)?; + w.set_color(ColorSpec::new().set_bold(false))?; + write!(w, " in ")?; + w.set_color(ColorSpec::new().set_bold(true))?; + writeln!(w, "{:?}.", filename)?; + w.set_color(ColorSpec::new().set_bold(false))?; + } + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::test::*; + use std::fs; + use tempfile::TempDir; + + #[test] + fn test_config_set() { + let cfg_dir = TempDir::new().unwrap(); + let cfg_file = cfg_dir.path().join("foo.toml"); + fs::write( + cfg_file.clone(), + "# store data everywhere\ndata_dir = \"/nowhere\"\n", + ) + .unwrap(); + + let settings = Settings::load_from_file(cfg_file.clone(), true).unwrap(); + + let mut w = test_writer(); + + execute( + &mut w, + ConfigOperation::Set("data_dir".to_owned(), "/somewhere".to_owned()), + &settings, + ) + .unwrap(); + assert!(w.into_string().starts_with("Set configuration value ")); + + let updated_toml = fs::read_to_string(cfg_file.clone()).unwrap(); + dbg!(&updated_toml); + assert_eq!( + updated_toml, + "# store data everywhere\ndata_dir = \"/somewhere\"\n" + ); + } +} diff --git a/cli/src/invocation/cmd/mod.rs b/cli/src/invocation/cmd/mod.rs index 18a973ebb..9371f1f2c 100644 --- a/cli/src/invocation/cmd/mod.rs +++ b/cli/src/invocation/cmd/mod.rs @@ -1,6 +1,7 @@ //! Responsible for executing commands as parsed by [`crate::argparse`]. pub(crate) mod add; +pub(crate) mod config; pub(crate) mod gc; pub(crate) mod help; pub(crate) mod info; diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 997440b2e..a9723fe9a 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -35,6 +35,10 @@ pub(crate) fn invoke(command: Command, settings: Settings) -> anyhow::Result<()> subcommand: Subcommand::Help { summary }, command_name, } => return cmd::help::execute(&mut w, command_name, summary), + Command { + subcommand: Subcommand::Config { config_operation }, + .. + } => return cmd::config::execute(&mut w, config_operation, &settings), Command { subcommand: Subcommand::Version, .. @@ -90,6 +94,10 @@ pub(crate) fn invoke(command: Command, settings: Settings) -> anyhow::Result<()> subcommand: Subcommand::Help { .. }, .. } => unreachable!(), + Command { + subcommand: Subcommand::Config { .. }, + .. + } => unreachable!(), Command { subcommand: Subcommand::Version, .. diff --git a/cli/src/settings/settings.rs b/cli/src/settings/settings.rs index 935e5e8ab..592874e6a 100644 --- a/cli/src/settings/settings.rs +++ b/cli/src/settings/settings.rs @@ -1,7 +1,7 @@ use super::util::table_with_keys; use super::{Column, Property, Report, Sort, SortBy}; use crate::argparse::{Condition, Filter}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use std::collections::HashMap; use std::convert::TryFrom; use std::env; @@ -9,6 +9,7 @@ use std::fs; use std::path::PathBuf; use taskchampion::Status; use toml::value::Table; +use toml_edit::Document; #[derive(Debug, PartialEq)] pub(crate) struct Settings { @@ -46,7 +47,7 @@ impl Settings { /// Get the default filename for the configuration, or None if that cannot /// be determined. - pub(crate) fn default_filename() -> Option { + fn default_filename() -> Option { if let Some(dir) = dirs_next::config_dir() { Some(dir.join("taskchampion.toml")) } else { @@ -54,7 +55,9 @@ impl Settings { } } - fn load_from_file(config_file: PathBuf, required: bool) -> Result { + /// Update this settings object with the contents of the given TOML file. Top-level settings + /// are overwritten, and reports are overwritten by name. + pub(crate) fn load_from_file(config_file: PathBuf, required: bool) -> Result { let mut settings = Self::default(); let config_toml = match fs::read_to_string(config_file.clone()) { @@ -62,6 +65,7 @@ impl Settings { return if required { Err(e.into()) } else { + settings.filename = Some(config_file); Ok(settings) }; } @@ -141,6 +145,40 @@ impl Settings { Ok(()) } + + /// Set a value in the config file, modifying it in place. Returns the filename. + pub(crate) fn set(&self, key: &str, value: &str) -> Result { + let allowed_keys = [ + "data_dir", + "server_client_key", + "server_origin", + "encryption_secret", + "server_dir", + // reports is not allowed, since it is not a string + ]; + if !allowed_keys.contains(&key) { + bail!("No such configuration key {}", key); + } + + let filename = if let Some(ref f) = self.filename { + f.clone() + } else { + Settings::default_filename() + .ok_or_else(|| anyhow!("Could not determine config file name"))? + }; + + let mut document = fs::read_to_string(filename.clone()) + .context("Could not read existing configuration file")? + .parse::() + .context("Could not parse existing configuration file")?; + + document[key] = toml_edit::value(value); + + fs::write(filename.clone(), document.to_string()) + .context("Could not write updated configuration file")?; + + Ok(filename) + } } impl Default for Settings { @@ -247,9 +285,13 @@ mod test { #[test] fn test_load_from_file_not_required() { let cfg_dir = TempDir::new().unwrap(); + let cfg_file = cfg_dir.path().join("foo.toml"); - let settings = Settings::load_from_file(cfg_dir.path().join("foo.toml"), false).unwrap(); - assert_eq!(settings, Settings::default()); + let settings = Settings::load_from_file(cfg_file.clone(), false).unwrap(); + + let mut expected = Settings::default(); + expected.filename = Some(cfg_file.clone()); + assert_eq!(settings, expected); } #[test] @@ -302,4 +344,21 @@ mod test { assert!(settings.reports.get("foo").is_some()); // beyond existence of this report, we can rely on Report's unit tests } + + #[test] + fn test_set_valid_key() { + let cfg_dir = TempDir::new().unwrap(); + let cfg_file = cfg_dir.path().join("foo.toml"); + fs::write(cfg_file.clone(), "server_dir = \"/srv\"").unwrap(); + + let settings = Settings::load_from_file(cfg_file.clone(), true).unwrap(); + assert_eq!(settings.filename, Some(cfg_file.clone())); + settings.set("data_dir", "/data").unwrap(); + + // load the file again and see the change + let settings = Settings::load_from_file(cfg_file.clone(), true).unwrap(); + assert_eq!(settings.data_dir, PathBuf::from("/data")); + assert_eq!(settings.server_dir, PathBuf::from("/srv")); + assert_eq!(settings.filename, Some(cfg_file)); + } } From 3bb198425cb3b456538154f78350616ee6ff2a60 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 5 May 2021 16:25:05 -0400 Subject: [PATCH 259/548] Use `ta config set` in documentation --- docs/src/config-file.md | 10 +++++++++- docs/src/task-sync.md | 28 +++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/src/config-file.md b/docs/src/config-file.md index 90a892805..6968e6e51 100644 --- a/docs/src/config-file.md +++ b/docs/src/config-file.md @@ -40,7 +40,15 @@ If using a remote server: * `server_client_key` - Client key to identify this replica to the sync server (a UUID) If not set, then sync is done to a local server. -# Reports +## Reports * `reports` - a mapping of each report's name to its definition. See [Reports](./reports.md) for details. + +## Editing + +As a shortcut, the simple, top-level configuration values can be edited from the command line: + +```shell +ta config set data_dir /home/myuser/.taskchampion +``` diff --git a/docs/src/task-sync.md b/docs/src/task-sync.md index a4252c6ac..82200ae48 100644 --- a/docs/src/task-sync.md +++ b/docs/src/task-sync.md @@ -11,13 +11,39 @@ Synchronization is quick, especially if no changes have occurred. Each replica expects to be synchronized frequently, even if no server is involved. Without periodic syncs, the storage space used for the task database will grow quickly, and performance will suffer. +## Local Sync + By default, TaskChampion syncs to a "local server", as specified by the `server_dir` configuration parameter. +This defaults to `taskchampion-sync-server` in your [data directory](https://docs.rs/dirs-next/2.0.0/dirs_next/fn.data_dir.html), but can be customized in the configuration file. + +## Remote Sync + +For remote synchronization, you will need a few pieces of information. +From the server operator, you will need an origin and a client key. +Configure these with + +```shell +ta config set server_origin "" +ta config set server_client_key "" +``` + +You will need to generate your own encryption secret. +This is used to encrypt your task history, so treat it as a password. +The following will use the `openssl` utility to generate a suitable value: + +```shell +ta config set encryption_secret $(openssl rand -hex 35) +``` + Every replica sharing a task history should have precisely the same configuration for `server_origin`, `server_client_key`, and `encryption_secret`. +### Adding a New Replica + Synchronizing a new replica to an existing task history is easy: begin with an empty replica, configured for the remote server, and run `ta sync`. The replica will download the entire task history. +### Upgrading a Locally-Sync'd Replica + It is possible to switch a single replica to a remote server by simply configuring for the remote server and running `ta sync`. The replica will upload the entire task history to the server. Once this is complete, additional replicas can be configured with the same settings in order to share the task history. - From 2345a57940925791ff1e9d2b777fe665f3cc9771 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 18 May 2021 18:57:29 +0000 Subject: [PATCH 260/548] fix clippy warning --- cli/src/settings/settings.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cli/src/settings/settings.rs b/cli/src/settings/settings.rs index 592874e6a..b17750fcb 100644 --- a/cli/src/settings/settings.rs +++ b/cli/src/settings/settings.rs @@ -48,11 +48,7 @@ impl Settings { /// Get the default filename for the configuration, or None if that cannot /// be determined. fn default_filename() -> Option { - if let Some(dir) = dirs_next::config_dir() { - Some(dir.join("taskchampion.toml")) - } else { - None - } + dirs_next::config_dir().map(|dir| dir.join("taskchampion.toml")) } /// Update this settings object with the contents of the given TOML file. Top-level settings From 0852bfd195f3b916f9cb39cba409cf7b4ba675d3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 1 May 2021 12:49:49 -0400 Subject: [PATCH 261/548] Require a filter be specified for modifications This filter can either be `*` or some "real" filter. But an empty set of arguments no longer automatically matches all tasks. --- cli/src/argparse/filter.rs | 109 ++++++++++++++++++++++++++++----- cli/src/argparse/subcommand.rs | 34 +++++----- 2 files changed, 109 insertions(+), 34 deletions(-) diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 49273999b..be63aa1af 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -1,8 +1,13 @@ -use super::args::{arg_matching, id_list, minus_tag, plus_tag, status_colon, TaskId}; +use super::args::{arg_matching, id_list, literal, minus_tag, plus_tag, status_colon, TaskId}; use super::ArgList; use crate::usage; use anyhow::bail; -use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; +use nom::{ + branch::alt, + combinator::*, + multi::{fold_many0, fold_many1}, + IResult, +}; use taskchampion::Status; /// A filter represents a selection of a particular set of tasks. @@ -85,7 +90,9 @@ impl Condition { } impl Filter { - pub(super) fn parse(input: ArgList) -> IResult { + /// Parse a filter that can include an empty set of args (meaning + /// all tasks) + pub(super) fn parse0(input: ArgList) -> IResult { fold_many0( Condition::parse, Filter { @@ -95,6 +102,30 @@ impl Filter { )(input) } + /// Parse a filter that must have at least one arg, which can be `all` + /// to mean all tasks + pub(super) fn parse1(input: ArgList) -> IResult { + alt(( + Filter::parse_all, + fold_many1( + Condition::parse, + Filter { + ..Default::default() + }, + |acc, arg| acc.with_arg(arg), + ), + ))(input) + } + + fn parse_all(input: ArgList) -> IResult { + fn to_filter(_: &str) -> Result { + Ok(Filter { + ..Default::default() + }) + } + map_res(arg_matching(literal("all")), to_filter)(input) + } + /// fold multiple filter args into a single Filter instance fn with_arg(mut self, cond: Condition) -> Filter { if let Condition::IdList(mut id_list) = cond { @@ -157,6 +188,13 @@ impl Filter { description: " Select tasks with the given status.", }); + u.filters.push(usage::Filter { + syntax: "all", + summary: "All tasks", + description: " + When specified alone for task-modification commands, `all` matches all tasks. + For example, `task all done` will mark all tasks as done.", + }); } } @@ -165,8 +203,8 @@ mod test { use super::*; #[test] - fn test_empty() { - let (input, filter) = Filter::parse(argv![]).unwrap(); + fn test_empty_parse0() { + let (input, filter) = Filter::parse0(argv![]).unwrap(); assert_eq!(input.len(), 0); assert_eq!( filter, @@ -176,9 +214,46 @@ mod test { ); } + #[test] + fn test_empty_parse1() { + // parse1 does not allow empty input + assert!(Filter::parse1(argv![]).is_err()); + } + + #[test] + fn test_all_parse0() { + let (input, _) = Filter::parse0(argv!["all"]).unwrap(); + assert_eq!(input.len(), 1); // did not parse "all" + } + + #[test] + fn test_all_parse1() { + let (input, filter) = Filter::parse1(argv!["all"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + filter, + Filter { + ..Default::default() + } + ); + } + + #[test] + fn test_all_with_other_stuff() { + let (input, filter) = Filter::parse1(argv!["all", "+foo"]).unwrap(); + // filter ends after `all` + assert_eq!(input.len(), 1); + assert_eq!( + filter, + Filter { + ..Default::default() + } + ); + } + #[test] fn test_id_list_single() { - let (input, filter) = Filter::parse(argv!["1"]).unwrap(); + let (input, filter) = Filter::parse0(argv!["1"]).unwrap(); assert_eq!(input.len(), 0); assert_eq!( filter, @@ -190,7 +265,7 @@ mod test { #[test] fn test_id_list_commas() { - let (input, filter) = Filter::parse(argv!["1,2,3"]).unwrap(); + let (input, filter) = Filter::parse0(argv!["1,2,3"]).unwrap(); assert_eq!(input.len(), 0); assert_eq!( filter, @@ -206,7 +281,7 @@ mod test { #[test] fn test_id_list_multi_arg() { - let (input, filter) = Filter::parse(argv!["1,2", "3,4"]).unwrap(); + let (input, filter) = Filter::parse0(argv!["1,2", "3,4"]).unwrap(); assert_eq!(input.len(), 0); assert_eq!( filter, @@ -223,7 +298,7 @@ mod test { #[test] fn test_id_list_uuids() { - let (input, filter) = Filter::parse(argv!["1,abcd1234"]).unwrap(); + let (input, filter) = Filter::parse0(argv!["1,abcd1234"]).unwrap(); assert_eq!(input.len(), 0); assert_eq!( filter, @@ -238,7 +313,7 @@ mod test { #[test] fn test_tags() { - let (input, filter) = Filter::parse(argv!["1", "+yes", "-no"]).unwrap(); + let (input, filter) = Filter::parse0(argv!["1", "+yes", "-no"]).unwrap(); assert_eq!(input.len(), 0); assert_eq!( filter, @@ -254,7 +329,7 @@ mod test { #[test] fn test_status() { - let (input, filter) = Filter::parse(argv!["status:completed", "status:pending"]).unwrap(); + let (input, filter) = Filter::parse0(argv!["status:completed", "status:pending"]).unwrap(); assert_eq!(input.len(), 0); assert_eq!( filter, @@ -269,8 +344,8 @@ mod test { #[test] fn intersect_idlist_idlist() { - let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1; - let right = Filter::parse(argv!["2,3", "+no"]).unwrap().1; + let left = Filter::parse0(argv!["1,2", "+yes"]).unwrap().1; + let right = Filter::parse0(argv!["2,3", "+no"]).unwrap().1; let both = left.intersect(right); assert_eq!( both, @@ -289,8 +364,8 @@ mod test { #[test] fn intersect_idlist_alltasks() { - let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1; - let right = Filter::parse(argv!["+no"]).unwrap().1; + let left = Filter::parse0(argv!["1,2", "+yes"]).unwrap().1; + let right = Filter::parse0(argv!["+no"]).unwrap().1; let both = left.intersect(right); assert_eq!( both, @@ -308,8 +383,8 @@ mod test { #[test] fn intersect_alltasks_alltasks() { - let left = Filter::parse(argv!["+yes"]).unwrap().1; - let right = Filter::parse(argv!["+no"]).unwrap().1; + let left = Filter::parse0(argv!["+yes"]).unwrap().1; + let right = Filter::parse0(argv!["+no"]).unwrap().1; let both = left.intersect(right); assert_eq!( both, diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 5609192b7..53d67556f 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -217,7 +217,7 @@ impl Modify { } map_res( tuple(( - Filter::parse, + Filter::parse1, alt(( arg_matching(literal("modify")), arg_matching(literal("prepend")), @@ -235,47 +235,47 @@ impl Modify { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { name: "modify", - syntax: "[filter] modify [modification]", + syntax: " modify [modification]", summary: "Modify tasks", description: " - Modify all tasks matching the filter.", + Modify all tasks matching the required filter.", }); u.subcommands.push(usage::Subcommand { name: "prepend", - syntax: "[filter] prepend [modification]", + syntax: " prepend [modification]", summary: "Prepend task description", description: " - Modify all tasks matching the filter by inserting the given description before each + Modify all tasks matching the required filter by inserting the given description before each task's description.", }); u.subcommands.push(usage::Subcommand { name: "append", - syntax: "[filter] append [modification]", + syntax: " append [modification]", summary: "Append task description", description: " - Modify all tasks matching the filter by adding the given description to the end + Modify all tasks matching the required filter by adding the given description to the end of each task's description.", }); u.subcommands.push(usage::Subcommand { name: "start", - syntax: "[filter] start [modification]", + syntax: " start [modification]", summary: "Start tasks", description: " - Start all tasks matching the filter, additionally applying any given modifications." + Start all tasks matching the required filter, additionally applying any given modifications." }); u.subcommands.push(usage::Subcommand { name: "stop", - syntax: "[filter] stop [modification]", + syntax: " stop [modification]", summary: "Stop tasks", description: " - Stop all tasks matching the filter, additionally applying any given modifications.", + Stop all tasks matching the required filter, additionally applying any given modifications.", }); u.subcommands.push(usage::Subcommand { name: "done", - syntax: "[filter] done [modification]", + syntax: " done [modification]", summary: "Mark tasks as completed", description: " - Mark all tasks matching the filter as completed, additionally applying any given + Mark all tasks matching the required filter as completed, additionally applying any given modifications.", }); } @@ -293,14 +293,14 @@ impl Report { } // allow the filter expression before or after the report name alt(( - map_res(pair(arg_matching(report_name), Filter::parse), |input| { + map_res(pair(arg_matching(report_name), Filter::parse0), |input| { to_subcommand(input.1, input.0) }), - map_res(pair(Filter::parse, arg_matching(report_name)), |input| { + map_res(pair(Filter::parse0, arg_matching(report_name)), |input| { to_subcommand(input.0, input.1) }), // default to a "next" report - map_res(Filter::parse, |input| to_subcommand(input, "next")), + map_res(Filter::parse0, |input| to_subcommand(input, "next")), ))(input) } @@ -335,7 +335,7 @@ impl Info { } map_res( pair( - Filter::parse, + Filter::parse1, alt(( arg_matching(literal("info")), arg_matching(literal("debug")), From bb7130f96041e5c22e4559374dd5fdb9a6cd3190 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 3 May 2021 17:57:04 -0400 Subject: [PATCH 262/548] Support multiple exit codes ..with more specific error enums. --- Cargo.lock | 1 + POLICY.md | 10 +++--- cli/Cargo.toml | 1 + cli/src/argparse/command.rs | 18 +++++++--- cli/src/bin/ta.rs | 9 +++-- cli/src/errors.rs | 59 +++++++++++++++++++++++++++++++ cli/src/invocation/cmd/add.rs | 2 +- cli/src/invocation/cmd/config.rs | 2 +- cli/src/invocation/cmd/gc.rs | 2 +- cli/src/invocation/cmd/help.rs | 2 +- cli/src/invocation/cmd/info.rs | 2 +- cli/src/invocation/cmd/modify.rs | 2 +- cli/src/invocation/cmd/report.rs | 2 +- cli/src/invocation/cmd/sync.rs | 2 +- cli/src/invocation/cmd/version.rs | 2 +- cli/src/invocation/mod.rs | 2 +- cli/src/invocation/report.rs | 2 +- cli/src/lib.rs | 7 ++-- cli/tests/cli.rs | 3 +- taskchampion/src/errors.rs | 7 ++-- taskchampion/src/lib.rs | 1 + taskchampion/src/replica.rs | 2 +- taskchampion/src/taskdb.rs | 6 ++-- 23 files changed, 112 insertions(+), 34 deletions(-) create mode 100644 cli/src/errors.rs diff --git a/Cargo.lock b/Cargo.lock index 8a50e2d2a..6853f373a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2176,6 +2176,7 @@ dependencies = [ "tempfile", "termcolor", "textwrap 0.13.4", + "thiserror", "toml", "toml_edit", ] diff --git a/POLICY.md b/POLICY.md index 9f9af590b..fb673155d 100644 --- a/POLICY.md +++ b/POLICY.md @@ -35,12 +35,10 @@ Considered to be part of the API policy. ## CLI exit codes -- `0` No errors, normal exit. -- `1` Generic error. -- `2` Never used to avoid conflicts with Bash. -- `3` Unable to execute with the given parameters. -- `4` I/O error. -- `5` Database error. +- `0` - No errors, normal exit. +- `1` - Generic error. +- `2` - Never used to avoid conflicts with Bash. +- `3` - Command-line Syntax Error. # Security diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d66cc63b9..a3a5ad20c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,6 +8,7 @@ version = "0.3.0" dirs-next = "^2.0.0" env_logger = "^0.8.3" anyhow = "1.0" +thiserror = "1.0" log = "^0.4.14" nom = "^6.1.2" prettytable-rs = "^0.8.0" diff --git a/cli/src/argparse/command.rs b/cli/src/argparse/command.rs index 79a715ec7..891a4525f 100644 --- a/cli/src/argparse/command.rs +++ b/cli/src/argparse/command.rs @@ -1,6 +1,5 @@ use super::args::*; use super::{ArgList, Subcommand}; -use anyhow::bail; use nom::{combinator::*, sequence::*, Err, IResult}; /// A command is the overall command that the CLI should execute. @@ -29,13 +28,22 @@ impl Command { } /// Parse a command from the given list of strings. - pub fn from_argv(argv: &[&str]) -> anyhow::Result { + pub fn from_argv(argv: &[&str]) -> Result { match Command::parse(argv) { Ok((&[], cmd)) => Ok(cmd), - Ok((trailing, _)) => bail!("command line has trailing arguments: {:?}", trailing), + Ok((trailing, _)) => Err(crate::Error::for_arguments(format!( + "command line has trailing arguments: {:?}", + trailing + ))), Err(Err::Incomplete(_)) => unreachable!(), - Err(Err::Error(e)) => bail!("command line not recognized: {:?}", e), - Err(Err::Failure(e)) => bail!("command line not recognized: {:?}", e), + Err(Err::Error(e)) => Err(crate::Error::for_arguments(format!( + "command line not recognized: {:?}", + e + ))), + Err(Err::Failure(e)) => Err(crate::Error::for_arguments(format!( + "command line not recognized: {:?}", + e + ))), } } } diff --git a/cli/src/bin/ta.rs b/cli/src/bin/ta.rs index ecf529be3..efdee99da 100644 --- a/cli/src/bin/ta.rs +++ b/cli/src/bin/ta.rs @@ -1,8 +1,11 @@ use std::process::exit; pub fn main() { - if let Err(err) = taskchampion_cli::main() { - eprintln!("{:?}", err); - exit(1); + match taskchampion_cli::main() { + Ok(_) => exit(0), + Err(e) => { + eprintln!("{:?}", e); + exit(e.exit_status()); + } } } diff --git a/cli/src/errors.rs b/cli/src/errors.rs new file mode 100644 index 000000000..16ac96285 --- /dev/null +++ b/cli/src/errors.rs @@ -0,0 +1,59 @@ +use taskchampion::Error as TcError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Command-Line Syntax Error: {0}")] + Arguments(String), + + #[error(transparent)] + TaskChampion(#[from] TcError), + + #[error(transparent)] + Other(#[from] anyhow::Error), +} + +impl Error { + /// Construct a new command-line argument error + pub(crate) fn for_arguments(msg: S) -> Self { + Error::Arguments(msg.to_string()) + } + + /// Determine the exit status for this error, as documented. + pub fn exit_status(&self) -> i32 { + match *self { + Error::Arguments(_) => 3, + _ => 1, + } + } +} + +impl From for Error { + fn from(err: std::io::Error) -> Self { + let err: anyhow::Error = err.into(); + Error::Other(err) + } +} + +#[cfg(test)] +mod test { + use super::*; + use anyhow::anyhow; + + #[test] + fn test_exit_status() { + let mut err: Error; + + err = anyhow!("uhoh").into(); + assert_eq!(err.exit_status(), 1); + + err = Error::Arguments("uhoh".to_string()); + assert_eq!(err.exit_status(), 3); + + err = std::io::Error::last_os_error().into(); + assert_eq!(err.exit_status(), 1); + + err = TcError::Database("uhoh".to_string()).into(); + assert_eq!(err.exit_status(), 1); + } +} diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index 8a4456282..abee1bf4d 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -6,7 +6,7 @@ pub(crate) fn execute( w: &mut W, replica: &mut Replica, modification: Modification, -) -> anyhow::Result<()> { +) -> Result<(), crate::Error> { let description = match modification.description { DescriptionMod::Set(ref s) => s.clone(), _ => "(no description)".to_owned(), diff --git a/cli/src/invocation/cmd/config.rs b/cli/src/invocation/cmd/config.rs index 0f34defae..2b57aa52d 100644 --- a/cli/src/invocation/cmd/config.rs +++ b/cli/src/invocation/cmd/config.rs @@ -6,7 +6,7 @@ pub(crate) fn execute( w: &mut W, config_operation: ConfigOperation, settings: &Settings, -) -> anyhow::Result<()> { +) -> Result<(), crate::Error> { match config_operation { ConfigOperation::Set(key, value) => { let filename = settings.set(&key, &value)?; diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs index 775b3096f..9b14b9fbb 100644 --- a/cli/src/invocation/cmd/gc.rs +++ b/cli/src/invocation/cmd/gc.rs @@ -1,7 +1,7 @@ use taskchampion::Replica; use termcolor::WriteColor; -pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> anyhow::Result<()> { +pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Result<(), crate::Error> { log::debug!("rebuilding working set"); replica.rebuild_working_set(true)?; writeln!(w, "garbage collected.")?; diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index 421c140a7..2f81a08f8 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -5,7 +5,7 @@ pub(crate) fn execute( w: &mut W, command_name: String, summary: bool, -) -> anyhow::Result<()> { +) -> Result<(), crate::Error> { let usage = Usage::new(); usage.write_help(w, command_name.as_ref(), summary)?; Ok(()) diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index 1f90b79f3..5d26213d1 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -10,7 +10,7 @@ pub(crate) fn execute( replica: &mut Replica, filter: Filter, debug: bool, -) -> anyhow::Result<()> { +) -> Result<(), crate::Error> { let working_set = replica.working_set()?; for task in filtered_tasks(replica, &filter)? { diff --git a/cli/src/invocation/cmd/modify.rs b/cli/src/invocation/cmd/modify.rs index 06b4ccc0e..6f17f0dba 100644 --- a/cli/src/invocation/cmd/modify.rs +++ b/cli/src/invocation/cmd/modify.rs @@ -8,7 +8,7 @@ pub(crate) fn execute( replica: &mut Replica, filter: Filter, modification: Modification, -) -> anyhow::Result<()> { +) -> Result<(), crate::Error> { for task in filtered_tasks(replica, &filter)? { let mut task = task.into_mut(replica); diff --git a/cli/src/invocation/cmd/report.rs b/cli/src/invocation/cmd/report.rs index 7123f0353..ab079af28 100644 --- a/cli/src/invocation/cmd/report.rs +++ b/cli/src/invocation/cmd/report.rs @@ -10,7 +10,7 @@ pub(crate) fn execute( settings: &Settings, report_name: String, filter: Filter, -) -> anyhow::Result<()> { +) -> Result<(), crate::Error> { display_report(w, replica, settings, report_name, filter) } diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index 2e9400642..ce213a5ba 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -5,7 +5,7 @@ pub(crate) fn execute( w: &mut W, replica: &mut Replica, server: &mut Box, -) -> anyhow::Result<()> { +) -> Result<(), crate::Error> { replica.sync(server)?; writeln!(w, "sync complete.")?; Ok(()) diff --git a/cli/src/invocation/cmd/version.rs b/cli/src/invocation/cmd/version.rs index baef94161..5ff2fea57 100644 --- a/cli/src/invocation/cmd/version.rs +++ b/cli/src/invocation/cmd/version.rs @@ -1,6 +1,6 @@ use termcolor::{ColorSpec, WriteColor}; -pub(crate) fn execute(w: &mut W) -> anyhow::Result<()> { +pub(crate) fn execute(w: &mut W) -> Result<(), crate::Error> { write!(w, "TaskChampion ")?; w.set_color(ColorSpec::new().set_bold(true))?; writeln!(w, "{}", env!("CARGO_PKG_VERSION"))?; diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index a9723fe9a..985c4d99e 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -19,7 +19,7 @@ use report::display_report; /// Invoke the given Command in the context of the given settings #[allow(clippy::needless_return)] -pub(crate) fn invoke(command: Command, settings: Settings) -> anyhow::Result<()> { +pub(crate) fn invoke(command: Command, settings: Settings) -> Result<(), crate::Error> { log::debug!("command: {:?}", command); log::debug!("settings: {:?}", settings); diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 36d15574a..512866381 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -80,7 +80,7 @@ pub(super) fn display_report( settings: &Settings, report_name: String, filter: Filter, -) -> anyhow::Result<()> { +) -> Result<(), crate::Error> { let mut t = Table::new(); let working_set = replica.working_set()?; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 2890e4b9f..6996e55f6 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -38,23 +38,26 @@ use std::string::FromUtf8Error; mod macros; mod argparse; +mod errors; mod invocation; mod settings; mod table; mod usage; +pub(crate) use errors::Error; use settings::Settings; /// The main entry point for the command-line interface. This builds an Invocation /// from the particulars of the operating-system interface, and then executes it. -pub fn main() -> anyhow::Result<()> { +pub fn main() -> Result<(), Error> { env_logger::init(); // parse the command line into a vector of &str, failing if // there are invalid utf-8 sequences. let argv: Vec = std::env::args_os() .map(|oss| String::from_utf8(oss.into_vec())) - .collect::>()?; + .collect::>() + .map_err(|_| Error::for_arguments("arguments must be valid utf-8"))?; let argv: Vec<&str> = argv.iter().map(|s| s.as_ref()).collect(); // parse the command line diff --git a/cli/tests/cli.rs b/cli/tests/cli.rs index 7eabe2c77..6325a6d3e 100644 --- a/cli/tests/cli.rs +++ b/cli/tests/cli.rs @@ -56,7 +56,8 @@ fn invalid_option() -> Result<(), Box> { cmd.arg("--no-such-option"); cmd.assert() .failure() - .stderr(predicate::str::contains("command line not recognized")); + .stderr(predicate::str::contains("command line not recognized")) + .code(predicate::eq(3)); Ok(()) } diff --git a/taskchampion/src/errors.rs b/taskchampion/src/errors.rs index 9e2a712a5..44bad9881 100644 --- a/taskchampion/src/errors.rs +++ b/taskchampion/src/errors.rs @@ -1,6 +1,9 @@ use thiserror::Error; + #[derive(Debug, Error, Eq, PartialEq, Clone)] +#[non_exhaustive] +/// Errors returned from taskchampion operations pub enum Error { - #[error("Task Database Error: {}", _0)] - DbError(String), + #[error("Task Database Error: {0}")] + Database(String), } diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index da61235d9..a05b1ab7b 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -40,6 +40,7 @@ mod taskdb; mod utils; mod workingset; +pub use errors::Error; pub use replica::Replica; pub use server::{Server, ServerConfig}; pub use storage::StorageConfig; diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 1e0a47382..361476951 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -113,7 +113,7 @@ impl Replica { // check that it already exists; this is a convenience check, as the task may already exist // when this Create operation is finally sync'd with operations from other replicas if self.taskdb.get_task(uuid)?.is_none() { - return Err(Error::DbError(format!("Task {} does not exist", uuid)).into()); + return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); } self.taskdb.apply(Operation::Delete { uuid })?; trace!("task {} deleted", uuid); diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index ef5fb6c8d..b373bc582 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -49,12 +49,12 @@ impl TaskDb { Operation::Create { uuid } => { // insert if the task does not already exist if !txn.create_task(*uuid)? { - return Err(Error::DbError(format!("Task {} already exists", uuid)).into()); + return Err(Error::Database(format!("Task {} already exists", uuid)).into()); } } Operation::Delete { ref uuid } => { if !txn.delete_task(*uuid)? { - return Err(Error::DbError(format!("Task {} does not exist", uuid)).into()); + return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); } } Operation::Update { @@ -71,7 +71,7 @@ impl TaskDb { }; txn.set_task(*uuid, task)?; } else { - return Err(Error::DbError(format!("Task {} does not exist", uuid)).into()); + return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); } } } From 991b29da6cd58aaa3dbfb9a0c5a857e7ea0eca83 Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 21 May 2021 15:52:07 +1000 Subject: [PATCH 263/548] WIP --- sync-server/src/storage/sqlite.rs | 1 - taskchampion/src/server/local.rs | 118 +++++++++++++++--------------- 2 files changed, 57 insertions(+), 62 deletions(-) diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index 81343b35d..e974e33ef 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -3,7 +3,6 @@ use anyhow::Context; use rusqlite::types::{FromSql, ToSql}; use rusqlite::{params, Connection, OptionalExtension}; use std::path::Path; -use std::sync::{Arc, Mutex}; #[derive(Debug, thiserror::Error)] enum SqliteError { diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index bdd3c5dbd..aab3d86c1 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -1,12 +1,34 @@ + use rusqlite::params; + use anyhow::Context; use crate::server::{ AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, }; -use crate::utils::Key; -use kv::msgpack::Msgpack; -use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; use serde::{Deserialize, Serialize}; use std::path::Path; use uuid::Uuid; +use rusqlite::types::{FromSql, ToSql}; + use rusqlite::OptionalExtension; + +// FIXME: Duplicated +/// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` +pub struct StoredUuid(Uuid); + +/// Conversion from Uuid stored as a string (rusqlite's uuid feature stores as binary blob) +impl FromSql for StoredUuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let u = Uuid::parse_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(StoredUuid(u)) + } +} + +/// Store Uuid as string in database +impl ToSql for StoredUuid { + fn to_sql(&self) -> rusqlite::Result> { + let s = self.0.to_string(); + Ok(s.into()) + } +} #[derive(Serialize, Deserialize, Debug)] struct Version { @@ -15,58 +37,48 @@ struct Version { history_segment: HistorySegment, } -pub struct LocalServer<'t> { - store: Store, - // NOTE: indexed by parent_version_id! - versions_bucket: Bucket<'t, Key, ValueBuf>>, - latest_version_bucket: Bucket<'t, Integer, ValueBuf>>, +pub struct LocalServer { + con: rusqlite::Connection, } -impl<'t> LocalServer<'t> { - /// A test server has no notion of clients, signatures, encryption, etc. - pub fn new>(directory: P) -> anyhow::Result> { - let mut config = Config::default(directory); - config.bucket("versions", None); - config.bucket("numbers", None); - config.bucket("latest_version", None); - config.bucket("operations", None); - config.bucket("working_set", None); - let store = Store::new(config)?; +impl LocalServer { + fn txn<'a>(&'a mut self) -> anyhow::Result { + let txn = self.con.transaction()?; + Ok(txn) + } - // versions are stored indexed by VersionId (uuid) - let versions_bucket = store.bucket::>>(Some("versions"))?; + /// A server which has no notion of clients, signatures, encryption, etc. + pub fn new>(directory: P) -> anyhow::Result { + let db_file = directory.as_ref().join("taskchampion-local-sync-server.sqlite3"); + let con = rusqlite::Connection::open(&db_file)?; - // this bucket contains the latest version at key 0 - let latest_version_bucket = - store.int_bucket::>>(Some("latest_version"))?; + let queries = vec![ + "CREATE TABLE IF NOT EXISTS data (key STRING PRIMARY KEY, value STRING);", + ]; + for q in queries { + con.execute(q, []).context("Creating table")?; + } Ok(LocalServer { - store, - versions_bucket, - latest_version_bucket, + con, }) } fn get_latest_version_id(&mut self) -> anyhow::Result { - let txn = self.store.read_txn()?; - let base_version = match txn.get(&self.latest_version_bucket, 0.into()) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(NO_VERSION_ID), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(base_version as VersionId) + let t = self.txn()?; + let result: Option = t.query_row("SELECT value FROM data WHERE key = latest_version_id LIMIT 1", rusqlite::params![], |r| r.get(0)).optional()?; + Ok(result.map(|x| x.0).unwrap_or(NO_VERSION_ID)) } fn set_latest_version_id(&mut self, version_id: VersionId) -> anyhow::Result<()> { - let mut txn = self.store.write_txn()?; - txn.set( - &self.latest_version_bucket, - 0.into(), - Msgpack::to_value_buf(version_id as Uuid)?, - )?; - txn.commit()?; + + let t = self.txn()?; + t.execute( + "INSERT OR REPLACE INTO tasks (uuid, data) VALUES (?, ?)", + params![&StoredUuid(version_id)], + ) + .context("Update task query")?; + t.commit()?; Ok(()) } @@ -74,31 +86,15 @@ impl<'t> LocalServer<'t> { &mut self, parent_version_id: VersionId, ) -> anyhow::Result> { - let txn = self.store.read_txn()?; - - let version = match txn.get(&self.versions_bucket, parent_version_id.into()) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(None), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(Some(version)) + todo!() } fn add_version_by_parent_version_id(&mut self, version: Version) -> anyhow::Result<()> { - let mut txn = self.store.write_txn()?; - txn.set( - &self.versions_bucket, - version.parent_version_id.into(), - Msgpack::to_value_buf(version)?, - )?; - txn.commit()?; - Ok(()) + todo!() } } -impl<'t> Server for LocalServer<'t> { +impl Server for LocalServer { // TODO: better transaction isolation for add_version (gets and sets should be in the same // transaction) From 00089639fe21fd5f8c62f2b91f445c302a0edf08 Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 21 May 2021 16:31:25 +1000 Subject: [PATCH 264/548] Include git HEAD rev in version output Closes #241 --- cli/Cargo.toml | 2 ++ cli/build.rs | 34 +++++++++++++++++++++++++++++++ cli/src/invocation/cmd/version.rs | 7 ++++++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 cli/build.rs diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d66cc63b9..23f68e12e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -4,6 +4,8 @@ edition = "2018" name = "taskchampion-cli" version = "0.3.0" +build = "build.rs" + [dependencies] dirs-next = "^2.0.0" env_logger = "^0.8.3" diff --git a/cli/build.rs b/cli/build.rs new file mode 100644 index 000000000..3c87576f9 --- /dev/null +++ b/cli/build.rs @@ -0,0 +1,34 @@ +use std::process::Command; + +fn main() { + // Query HEAD revision and expose as $TC_GIT_REV during build + // + // Adapted from https://stackoverflow.com/questions/43753491 + let cmd = Command::new("git") + .args(&["rev-parse", "--short", "HEAD"]) + .spawn() + // Wait for process to exit + .and_then(|cmd| cmd.wait_with_output()) + // Handle error if failed to launch git + .map_err(|_e| println!("cargo:warning=Failed to run 'git' to determine HEAD rev")) + // Remap to Some/None for simpler error handling + .ok() + // Handle command failing + .and_then(|o| { + if o.status.success() { + Some(o) + } else { + println!( + "cargo:warning='git' exited with non-zero exit code while determining HEAD rev" + ); + None + } + }) + // Get output as UTF-8 string + .map(|out| String::from_utf8(out.stdout).expect("Invalid output in stdout")); + + // Only output git rev if successful + if let Some(h) = cmd { + println!("cargo:rustc-env=TC_GIT_REV={}", h); + } +} diff --git a/cli/src/invocation/cmd/version.rs b/cli/src/invocation/cmd/version.rs index baef94161..aebac3f5b 100644 --- a/cli/src/invocation/cmd/version.rs +++ b/cli/src/invocation/cmd/version.rs @@ -3,8 +3,13 @@ use termcolor::{ColorSpec, WriteColor}; pub(crate) fn execute(w: &mut W) -> anyhow::Result<()> { write!(w, "TaskChampion ")?; w.set_color(ColorSpec::new().set_bold(true))?; - writeln!(w, "{}", env!("CARGO_PKG_VERSION"))?; + write!(w, "{}", env!("CARGO_PKG_VERSION"))?; w.reset()?; + + if let Some(h) = option_env!("TC_GIT_REV") { + write!(w, " (git rev: {})", h)?; + } + writeln!(w)?; Ok(()) } From 8ba72d19dfde68043b614b7f5f897c9b463be159 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 21 May 2021 09:07:35 -0400 Subject: [PATCH 265/548] Add CODEOWNERS pointing to @dbr and me --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..f1bb9bff4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @dbr @djmitche From 45db886f2a1f86460f4088597242058fb2e9125b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 21 May 2021 22:39:10 -0400 Subject: [PATCH 266/548] add 'ta config path' --- cli/src/argparse/config.rs | 26 +++++++++++++++++--------- cli/src/invocation/cmd/config.rs | 7 +++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/cli/src/argparse/config.rs b/cli/src/argparse/config.rs index 924164564..209000a4e 100644 --- a/cli/src/argparse/config.rs +++ b/cli/src/argparse/config.rs @@ -1,13 +1,15 @@ use super::args::{any, arg_matching, literal}; use super::ArgList; use crate::usage; -use nom::{combinator::*, sequence::*, IResult}; +use nom::{branch::alt, combinator::*, sequence::*, IResult}; #[derive(Debug, PartialEq)] /// A config operation pub(crate) enum ConfigOperation { /// Set a configuration value Set(String, String), + /// Show configuration path + Path, } impl ConfigOperation { @@ -15,14 +17,20 @@ impl ConfigOperation { fn set_to_op(input: (&str, &str, &str)) -> Result { Ok(ConfigOperation::Set(input.1.to_owned(), input.2.to_owned())) } - map_res( - tuple(( - arg_matching(literal("set")), - arg_matching(any), - arg_matching(any), - )), - set_to_op, - )(input) + fn path_to_op(_: &str) -> Result { + Ok(ConfigOperation::Path) + } + alt(( + map_res( + tuple(( + arg_matching(literal("set")), + arg_matching(any), + arg_matching(any), + )), + set_to_op, + ), + map_res(arg_matching(literal("path")), path_to_op), + ))(input) } pub(super) fn get_usage(u: &mut usage::Usage) { diff --git a/cli/src/invocation/cmd/config.rs b/cli/src/invocation/cmd/config.rs index 2b57aa52d..fc8aa6a3f 100644 --- a/cli/src/invocation/cmd/config.rs +++ b/cli/src/invocation/cmd/config.rs @@ -19,6 +19,13 @@ pub(crate) fn execute( writeln!(w, "{:?}.", filename)?; w.set_color(ColorSpec::new().set_bold(false))?; } + ConfigOperation::Path => { + if let Some(ref filename) = settings.filename { + writeln!(w, "{}", filename.to_string_lossy())?; + } else { + return Err(anyhow::anyhow!("No configuration filename found").into()); + } + } } Ok(()) } From b944e278808c4cd8281c3cf90a3b0ab94c94b223 Mon Sep 17 00:00:00 2001 From: dbr Date: Mon, 24 May 2021 19:44:32 +1000 Subject: [PATCH 267/548] Make 'cargo run' run ta binary --- cli/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 07b0e6aca..1c0e2822d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -6,6 +6,9 @@ version = "0.3.0" build = "build.rs" +# Run 'ta' when doing 'cargo run' at repo root +default-run = "ta" + [dependencies] dirs-next = "^2.0.0" env_logger = "^0.8.3" From 09558f9329359eda4fc809d2cc4cb4afb6d42538 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 11 May 2021 21:45:32 +0000 Subject: [PATCH 268/548] Substitute usage information into the documentation This will simplify keeping documentation in sync with the code. --- .github/workflows/publish-docs.yml | 20 + .github/workflows/rust-tests.yml | 20 + Cargo.lock | 844 ++++++++++++++++++++++++++++- README.md | 5 + RELEASING.md | 2 +- build-docs.sh | 31 ++ cli/Cargo.toml | 15 + cli/src/argparse/modification.rs | 4 +- cli/src/bin/usage-docs.rs | 50 ++ cli/src/lib.rs | 3 + cli/src/settings/mod.rs | 2 +- cli/src/settings/report.rs | 31 ++ cli/src/usage.rs | 136 ++++- docs/README.md | 7 + docs/book.toml | 3 + docs/build.sh | 23 - docs/src/SUMMARY.md | 4 +- docs/src/filters.md | 9 + docs/src/modifications.md | 5 + docs/src/reports.md | 9 +- docs/src/using-task-command.md | 9 +- 21 files changed, 1177 insertions(+), 55 deletions(-) create mode 100755 build-docs.sh create mode 100644 cli/src/bin/usage-docs.rs delete mode 100755 docs/build.sh create mode 100644 docs/src/filters.md create mode 100644 docs/src/modifications.md diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index a9fed18ba..27140a996 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -17,6 +17,26 @@ jobs: with: mdbook-version: 'latest' + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Create usage-docs plugin + run: cargo build -p taskchampion-cli --features usage-docs --bin usage-docs + - run: mdbook build docs - name: Deploy diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index 8d9eba149..ab56e3c39 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -75,5 +75,25 @@ jobs: with: mdbook-version: 'latest' + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Create usage-docs plugin + run: cargo build -p taskchampion-cli --features usage-docs --bin usage-docs + - run: mdbook test docs - run: mdbook build docs diff --git a/Cargo.lock b/Cargo.lock index 6853f373a..144f8e56f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,7 +47,7 @@ dependencies = [ "actix-service", "actix-threadpool", "actix-utils", - "base64", + "base64 0.13.0", "bitflags", "brotli2", "bytes 0.5.6", @@ -76,8 +76,8 @@ dependencies = [ "regex", "serde", "serde_json", - "serde_urlencoded", - "sha-1", + "serde_urlencoded 0.7.0", + "sha-1 0.9.4", "slab", "time 0.2.26", ] @@ -264,7 +264,7 @@ dependencies = [ "regex", "serde", "serde_json", - "serde_urlencoded", + "serde_urlencoded 0.7.0", "socket2", "time 0.2.26", "tinyvec", @@ -297,6 +297,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "ammonia" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee7d6eb157f337c5cedc95ddf17f0cbc36d36eb7763c8e0d1c1aeb3722f6279" +dependencies = [ + "html5ever", + "lazy_static", + "maplit", + "markup5ever_rcdom", + "matches", + "tendril", + "url", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -382,7 +397,7 @@ dependencies = [ "actix-http", "actix-rt 1.1.1", "actix-service", - "base64", + "base64 0.13.0", "bytes 0.5.6", "cfg-if 1.0.0", "derive_more", @@ -393,7 +408,7 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", - "serde_urlencoded", + "serde_urlencoded 0.7.0", ] [[package]] @@ -402,6 +417,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "base64" version = "0.13.0" @@ -452,13 +473,34 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", ] [[package]] @@ -499,6 +541,12 @@ version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.4.3" @@ -693,13 +741,22 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.4", ] [[package]] @@ -746,12 +803,33 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "elasticlunr-rs" +version = "2.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "959fbc9a6ebced545cbe365fdce5e25c6ab7683f2ca4ecc9fb9d0db663bf73d5" +dependencies = [ + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "strum", + "strum_macros", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -779,6 +857,19 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime 1.3.0", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.8.3" @@ -786,12 +877,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" dependencies = [ "atty", - "humantime", + "humantime 2.1.0", "log", "regex", "termcolor", ] +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "filetime" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.6", + "winapi 0.3.9", +] + [[package]] name = "flate2" version = "1.0.20" @@ -829,6 +938,25 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +dependencies = [ + "bitflags", + "fsevent-sys", +] + +[[package]] +name = "fsevent-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" +dependencies = [ + "libc", +] + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -851,6 +979,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" +[[package]] +name = "futf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.14" @@ -952,6 +1090,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.4" @@ -962,6 +1109,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -984,6 +1140,21 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] +[[package]] +name = "gitignore" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78aa90e4620c1498ac434c06ba6e521b525794bbdacf085d490cc794b4a2f9a4" +dependencies = [ + "glob", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "h2" version = "0.2.7" @@ -1004,12 +1175,51 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error 2.0.0", + "serde", + "serde_json", +] + [[package]] name = "hashbrown" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +[[package]] +name = "headers" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b7591fb62902706ae8e7aaff416b1b0fa2c0fd0878b46dc13baa3712d8a855" +dependencies = [ + "base64 0.13.0", + "bitflags", + "bytes 1.0.1", + "headers-core", + "http", + "mime", + "sha-1 0.9.4", + "time 0.1.43", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.3.2" @@ -1039,6 +1249,20 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "html5ever" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "http" version = "0.2.4" @@ -1050,18 +1274,67 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes 0.5.6", + "http", +] + [[package]] name = "httparse" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437" +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error 1.2.3", +] + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.13.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a6f157065790a3ed2f88679250419b5cdd96e714a0d65f7797fd337186e96bb" +dependencies = [ + "bytes 0.5.6", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project 1.0.7", + "socket2", + "tokio 0.2.25", + "tower-service", + "tracing", + "want", +] + [[package]] name = "idna" version = "0.2.3" @@ -1083,6 +1356,35 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inotify" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "input_buffer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +dependencies = [ + "bytes 0.5.6", +] + [[package]] name = "instant" version = "0.1.9" @@ -1163,6 +1465,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lexical-core" version = "0.7.5" @@ -1261,6 +1569,44 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -1273,6 +1619,38 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "mdbook" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed4060ccf332a0479df37e84c8435ad20be737d5337c3a90fa1b3b0d480a3a0" +dependencies = [ + "ammonia", + "anyhow", + "chrono", + "clap", + "elasticlunr-rs", + "env_logger 0.7.1", + "futures-util", + "gitignore", + "handlebars", + "lazy_static", + "log", + "memchr", + "notify", + "open", + "pulldown-cmark", + "regex", + "serde", + "serde_derive", + "serde_json", + "shlex", + "tempfile", + "tokio 0.2.25", + "toml", + "warp", +] + [[package]] name = "memchr" version = "2.3.4" @@ -1285,6 +1663,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1327,6 +1715,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio 0.6.23", + "slab", +] + [[package]] name = "mio-uds" version = "0.6.8" @@ -1370,6 +1770,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nom" version = "6.1.2" @@ -1389,6 +1795,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "notify" +version = "4.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" +dependencies = [ + "bitflags", + "filetime", + "fsevent", + "fsevent-sys", + "inotify", + "libc", + "mio 0.6.23", + "mio-extras", + "walkdir", + "winapi 0.3.9", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -1433,12 +1857,28 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "open" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1711eb4b31ce4ad35b0f316d8dfba4fe5c7ad601c448446d84aae7a896627b20" +dependencies = [ + "which", + "winapi 0.3.9", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -1470,6 +1910,87 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1 0.8.2", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "0.4.28" @@ -1540,6 +2061,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "predicates" version = "1.0.7" @@ -1630,6 +2157,18 @@ version = "2.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b7f4a129bb3754c25a4e04032a90173c68f85168f77118ac4cb4936e7f06f92" +[[package]] +name = "pulldown-cmark" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca36dea94d187597e104a5c8e4b07576a8a45aa5db48a65e12940d3eb7461f55" +dependencies = [ + "bitflags", + "getopts", + "memchr", + "unicase", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -1668,6 +2207,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", + "rand_pcg", ] [[package]] @@ -1738,6 +2278,15 @@ dependencies = [ "rand_core 0.6.2", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_xorshift" version = "0.3.0" @@ -1870,7 +2419,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" dependencies = [ - "base64", + "base64 0.13.0", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -1891,7 +2440,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" dependencies = [ - "base64", + "base64 0.13.0", "log", "ring", "sct", @@ -1916,6 +2465,21 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1978,6 +2542,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +dependencies = [ + "dtoa", + "itoa", + "serde", + "url", +] + [[package]] name = "serde_urlencoded" version = "0.7.0" @@ -1990,17 +2566,29 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + [[package]] name = "sha-1" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if 1.0.0", "cpuid-bool", - "digest", - "opaque-debug", + "digest 0.9.0", + "opaque-debug 0.3.0", ] [[package]] @@ -2009,6 +2597,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +[[package]] +name = "shlex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" + [[package]] name = "signal-hook-registry" version = "1.3.0" @@ -2018,6 +2612,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" + [[package]] name = "slab" version = "0.4.2" @@ -2117,12 +2717,55 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "string_cache" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strum" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" + +[[package]] +name = "strum_macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "1.0.69" @@ -2167,11 +2810,13 @@ dependencies = [ "assert_cmd", "atty", "dirs-next", - "env_logger", + "env_logger 0.8.3", "log", + "mdbook", "nom", "predicates", "prettytable-rs", + "serde_json", "taskchampion", "tempfile", "termcolor", @@ -2189,7 +2834,7 @@ dependencies = [ "actix-web", "anyhow", "clap", - "env_logger", + "env_logger 0.8.3", "futures", "kv", "log", @@ -2212,6 +2857,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tendril" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "term" version = "0.5.2" @@ -2373,6 +3029,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" dependencies = [ "bytes 0.5.6", + "fnv", "futures-core", "iovec", "lazy_static", @@ -2383,6 +3040,7 @@ dependencies = [ "pin-project-lite 0.1.12", "signal-hook-registry", "slab", + "tokio-macros", "winapi 0.3.9", ] @@ -2402,6 +3060,30 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tokio-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e878ad426ca286e4dcae09cbd4e1973a7f8987d97570e2469703dd7f5720c" +dependencies = [ + "futures-util", + "log", + "pin-project 0.4.28", + "tokio 0.2.25", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.3.1" @@ -2436,6 +3118,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + [[package]] name = "tracing" version = "0.1.25" @@ -2512,12 +3200,52 @@ dependencies = [ "trust-dns-proto", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" +dependencies = [ + "base64 0.12.3", + "byteorder", + "bytes 0.5.6", + "http", + "httparse", + "input_buffer", + "log", + "rand 0.7.3", + "sha-1 0.9.4", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.5" @@ -2575,7 +3303,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fbeb1aabb07378cf0e084971a74f24241273304653184f54cdce113c0d7df1b" dependencies = [ - "base64", + "base64 0.13.0", "chunked_transfer", "log", "once_cell", @@ -2597,6 +3325,18 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a1f0175e03a0973cf4afd476bef05c26e228520400eb1fd473ad417b1c00ffb" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "0.8.2" @@ -2634,6 +3374,54 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi 0.3.9", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41be6df54c97904af01aa23e613d4521eed7ab23537cede692d4058f6449407" +dependencies = [ + "bytes 0.5.6", + "futures", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "pin-project 0.4.28", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded 0.6.1", + "tokio 0.2.25", + "tokio-tungstenite", + "tower-service", + "tracing", + "tracing-futures", + "urlencoding", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -2729,6 +3517,16 @@ dependencies = [ "webpki", ] +[[package]] +name = "which" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" +dependencies = [ + "either", + "libc", +] + [[package]] name = "widestring" version = "0.4.3" @@ -2802,3 +3600,15 @@ name = "wyz" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + +[[package]] +name = "xml5ever" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59" +dependencies = [ + "log", + "mac", + "markup5ever", + "time 0.1.43", +] diff --git a/README.md b/README.md index 6a0715ccb..1b4e1d664 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,8 @@ There are three crates here: * [taskchampion-cli](./cli) - the command-line binary * [taskchampion-sync-server](./sync-server) - the server against which `task sync` operates +## Documentation Generation + +The `mdbook` configuration contains a "preprocessor" implemented in the `taskchampion-cli` crate in order to reflect CLI usage information into the generated book. +Tihs preprocessor is not built by default. +To (re)build it, run `cargo build -p taskchampion-cli --features usage-docs --bin usage-docs`. diff --git a/RELEASING.md b/RELEASING.md index 160f2c230..e58e51da7 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -10,7 +10,7 @@ 1. Run `git tag vX.Y.Z` 1. Run `git push upstream` 1. Run `git push --tags upstream` -1. Run `( cd docs; ./build.sh )` +1. Run `( ./build-docs.sh )` 1. Run `(cd taskchampion; cargo publish)` (note that the other crates do not get published) 1. Navigate to the tag in the GitHub releases UI and create a release with general comments about the changes in the release 1. Upload `./target/release/task` and `./target/release/task-sync-server` to the release diff --git a/build-docs.sh b/build-docs.sh new file mode 100755 index 000000000..b03c22ab9 --- /dev/null +++ b/build-docs.sh @@ -0,0 +1,31 @@ +#! /bin/bash + +REMOTE=origin + +set -e + +if ! [ -f "docs/src/SUMMARY.md" ]; then + echo "Run this from the root of the repo" + exit 1 +fi + +# build the latest version of the mdbook plugin +cargo build -p taskchampion-cli --features usage-docs --bin usage-docs + +# create a worktree of this repo, with the `gh-pages` branch checked out +if ! [ -d ./docs/tmp ]; then + git worktree add docs/tmp gh-pages +fi + +# update the wortree +(cd docs/tmp && git pull $REMOTE gh-pages) + +# remove all files in the worktree and regenerate the book there +rm -rf docs/tmp/* +mdbook build docs +cp -rp docs/book/* docs/tmp + +# add everything in the worktree, commit, and push +(cd docs/tmp && git add -A) +(cd docs/tmp && git commit -am "update docs") +(cd docs/tmp && git push $REMOTE gh-pages:gh-pages) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 07b0e6aca..868565a88 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -20,6 +20,10 @@ atty = "^0.2.14" toml = "^0.5.8" toml_edit = "^0.2.0" +# only needed for usage-docs +mdbook = { version = "0.4", optional = true } +serde_json = { version = "*", optional = true } + [dependencies.taskchampion] path = "../taskchampion" @@ -27,3 +31,14 @@ path = "../taskchampion" assert_cmd = "^1.0.3" predicates = "^1.0.7" tempfile = "3" + +[features] +usage-docs = [ "mdbook", "serde_json" ] + +[[bin]] +name = "ta" + +[[bin]] +# this is an mdbook plugin and only needed when running `mdbook` +name = "usage-docs" +required-features = [ "usage-docs" ] diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index d449f1ef6..9628f2352 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -115,7 +115,9 @@ impl Modification { summary: "Set description", description: " Set the task description. Multiple arguments are combined into a single - space-separated description.", + space-separated description. To avoid surprises from shell quoting, prefer + to use a single quoted argument, for example `ta 19 modify \"return library + books\"`", }); u.modifications.push(usage::Modification { syntax: "+TAG", diff --git a/cli/src/bin/usage-docs.rs b/cli/src/bin/usage-docs.rs new file mode 100644 index 000000000..f78179c71 --- /dev/null +++ b/cli/src/bin/usage-docs.rs @@ -0,0 +1,50 @@ +use mdbook::book::{Book, BookItem}; +use mdbook::errors::Error; +use mdbook::preprocess::{CmdPreprocessor, PreprocessorContext}; +use std::io; +use std::process; +use taskchampion_cli::Usage; + +/// This is a simple mdbook preprocessor designed to substitute information from the usage +/// into the documentation. +fn main() -> anyhow::Result<()> { + // cheap way to detect the "supports" arg + if std::env::args().len() > 1 { + // sure, whatever, we support it all + process::exit(0); + } + + let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; + + if ctx.mdbook_version != mdbook::MDBOOK_VERSION { + eprintln!( + "Warning: This mdbook preprocessor was built against version {} of mdbook, \ + but we're being called from version {}", + mdbook::MDBOOK_VERSION, + ctx.mdbook_version + ); + } + + let processed_book = process(&ctx, book)?; + serde_json::to_writer(io::stdout(), &processed_book)?; + + Ok(()) +} + +fn process(_ctx: &PreprocessorContext, mut book: Book) -> Result { + let usage = Usage::new(); + + book.for_each_mut(|sect| { + if let BookItem::Chapter(ref mut chapter) = sect { + let new_content = usage.substitute_docs(&chapter.content).unwrap(); + if new_content != chapter.content { + eprintln!( + "Substituting usage in {:?}", + chapter.source_path.as_ref().unwrap() + ); + } + chapter.content = new_content; + } + }); + Ok(book) +} diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 6996e55f6..ba56704f0 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -47,6 +47,9 @@ mod usage; pub(crate) use errors::Error; use settings::Settings; +// used by the `generate` command +pub use usage::Usage; + /// The main entry point for the command-line interface. This builds an Invocation /// from the particulars of the operating-system interface, and then executes it. pub fn main() -> Result<(), Error> { diff --git a/cli/src/settings/mod.rs b/cli/src/settings/mod.rs index 896de38b8..c6a6ddd2f 100644 --- a/cli/src/settings/mod.rs +++ b/cli/src/settings/mod.rs @@ -7,5 +7,5 @@ mod report; mod settings; mod util; -pub(crate) use report::{Column, Property, Report, Sort, SortBy}; +pub(crate) use report::{get_usage, Column, Property, Report, Sort, SortBy}; pub(crate) use settings::Settings; diff --git a/cli/src/settings/report.rs b/cli/src/settings/report.rs index 959494e5d..f73dfe58d 100644 --- a/cli/src/settings/report.rs +++ b/cli/src/settings/report.rs @@ -2,6 +2,7 @@ use crate::argparse::{Condition, Filter}; use crate::settings::util::table_with_keys; +use crate::usage::{self, Usage}; use anyhow::{anyhow, bail, Result}; use std::convert::{TryFrom, TryInto}; @@ -30,6 +31,7 @@ pub(crate) struct Column { /// Task property to display in a report #[derive(Clone, Debug, PartialEq)] pub(crate) enum Property { + // NOTE: when adding a property here, add it to get_usage, below, as well. /// The task's ID, either working-set index or Uuid if not in the working set Id, @@ -59,6 +61,7 @@ pub(crate) struct Sort { /// Task property to sort by #[derive(Clone, Debug, PartialEq)] pub(crate) enum SortBy { + // NOTE: when adding a property here, add it to get_usage, below, as well. /// The task's ID, either working-set index or a UUID prefix; working /// set tasks sort before others. Id, @@ -212,6 +215,34 @@ impl TryFrom<&toml::Value> for SortBy { } } +pub(crate) fn get_usage(u: &mut Usage) { + u.report_properties.push(usage::ReportProperty { + name: "id", + as_sort_by: Some("Sort by the task's shorthand ID"), + as_column: Some("The task's shorthand ID"), + }); + u.report_properties.push(usage::ReportProperty { + name: "uuid", + as_sort_by: Some("Sort by the task's full UUID"), + as_column: Some("The task's full UUID"), + }); + u.report_properties.push(usage::ReportProperty { + name: "active", + as_sort_by: None, + as_column: Some("`*` if the task is active (started)"), + }); + u.report_properties.push(usage::ReportProperty { + name: "description", + as_sort_by: Some("Sort by the task's description"), + as_column: Some("The task's description"), + }); + u.report_properties.push(usage::ReportProperty { + name: "tags", + as_sort_by: None, + as_column: Some("The task's tags"), + }); +} + #[cfg(test)] mod test { use super::*; diff --git a/cli/src/usage.rs b/cli/src/usage.rs index f1bacf674..59a2ba982 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -2,26 +2,31 @@ //! a way that puts the source of that documentation near its implementation. use crate::argparse; -use std::io::{Result, Write}; +use crate::settings; +use anyhow::Result; +use std::io::Write; + +#[cfg(feature = "usage-docs")] +use std::fmt::Write as FmtWrite; /// A top-level structure containing usage/help information for the entire CLI. #[derive(Debug, Default)] -pub(crate) struct Usage { +pub struct Usage { pub(crate) subcommands: Vec, pub(crate) filters: Vec, pub(crate) modifications: Vec, + pub(crate) report_properties: Vec, } impl Usage { /// Get a new, completely-filled-out usage object - pub(crate) fn new() -> Self { + pub fn new() -> Self { let mut rv = Self { ..Default::default() }; argparse::get_usage(&mut rv); - - // TODO: sort subcommands + settings::get_usage(&mut rv); rv } @@ -77,6 +82,62 @@ impl Usage { } Ok(()) } + + #[cfg(feature = "usage-docs")] + /// Substitute strings matching + /// + /// ```text + /// + /// ``` + /// + /// With the appropriate documentation. + pub fn substitute_docs(&self, content: &str) -> Result { + // this is not efficient, but it doesn't need to be + let mut lines = content.lines(); + let mut w = String::new(); + + const DOC_HEADER_PREFIX: &str = ""; + + for line in lines { + if line.starts_with(DOC_HEADER_PREFIX) && line.ends_with(DOC_HEADER_SUFFIX) { + let doc_type = &line[DOC_HEADER_PREFIX.len()..line.len() - DOC_HEADER_SUFFIX.len()]; + + match doc_type { + "subcommands" => { + for subcommand in self.subcommands.iter() { + subcommand.write_markdown(&mut w)?; + } + } + "filters" => { + for filter in self.filters.iter() { + filter.write_markdown(&mut w)?; + } + } + "modifications" => { + for modification in self.modifications.iter() { + modification.write_markdown(&mut w)?; + } + } + "report-columns" => { + for prop in self.report_properties.iter() { + prop.write_column_markdown(&mut w)?; + } + } + "report-sort-by" => { + for prop in self.report_properties.iter() { + prop.write_sort_by_markdown(&mut w)?; + } + } + _ => anyhow::bail!("Unkonwn doc type {}", doc_type), + } + } else { + writeln!(w, "{}", line)?; + } + } + + Ok(w) + } } /// wrap an indented string @@ -122,6 +183,15 @@ impl Subcommand { } Ok(()) } + + #[cfg(feature = "usage-docs")] + fn write_markdown(&self, mut w: W) -> Result<()> { + writeln!(w, "### `ta {}` - {}", self.name, self.summary)?; + writeln!(w, "```shell\nta {}\n```", self.syntax)?; + writeln!(w, "{}", indented(self.description, ""))?; + writeln!(w)?; + Ok(()) + } } /// Usage documentation for a filter argument @@ -152,6 +222,15 @@ impl Filter { } Ok(()) } + + #[cfg(feature = "usage-docs")] + fn write_markdown(&self, mut w: W) -> Result<()> { + writeln!(w, "* `{}` - {}", self.syntax, self.summary)?; + writeln!(w)?; + writeln!(w, "{}", indented(self.description, " "))?; + writeln!(w)?; + Ok(()) + } } /// Usage documentation for a modification argument @@ -182,4 +261,51 @@ impl Modification { } Ok(()) } + + #[cfg(feature = "usage-docs")] + fn write_markdown(&self, mut w: W) -> Result<()> { + writeln!(w, "* `{}` - {}", self.syntax, self.summary)?; + writeln!(w)?; + writeln!(w, "{}", indented(self.description, " "))?; + writeln!(w)?; + Ok(()) + } +} + +/// Usage documentation for a report property (which may be used for sorting, as a column, or +/// both). +#[derive(Debug, Default)] +pub(crate) struct ReportProperty { + /// Name of the property + pub(crate) name: &'static str, + + /// Usage description for sorting, if any + pub(crate) as_sort_by: Option<&'static str>, + + /// Usage description as a column, if any + pub(crate) as_column: Option<&'static str>, +} + +impl ReportProperty { + #[cfg(feature = "usage-docs")] + fn write_sort_by_markdown(&self, mut w: W) -> Result<()> { + if let Some(as_sort_by) = self.as_sort_by { + writeln!(w, "* `{}`", self.name)?; + writeln!(w)?; + writeln!(w, "{}", indented(as_sort_by, " "))?; + writeln!(w)?; + } + Ok(()) + } + + #[cfg(feature = "usage-docs")] + fn write_column_markdown(&self, mut w: W) -> Result<()> { + if let Some(as_column) = self.as_column { + writeln!(w, "* `{}`", self.name)?; + writeln!(w)?; + writeln!(w, "{}", indented(as_column, " "))?; + writeln!(w)?; + } + Ok(()) + } } diff --git a/docs/README.md b/docs/README.md index 7aaa35c16..586cf2663 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,10 @@ This is an [mdbook](https://rust-lang.github.io/mdBook/index.html) book. Minor modifications can be made without installing the mdbook tool, as the content is simple Markdown. Changes are verified on pull requests. + +To build the docs locally, you will need to build `usage-docs`: + +``` +cargo build -p taskchampion-cli --feature usage-docs --bin usage-docs +mdbook build docs/ +``` diff --git a/docs/book.toml b/docs/book.toml index 7e2fa9820..3ab678ad0 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -7,3 +7,6 @@ title = "TaskChampion" [output.html] default-theme = "ayu" + +[preprocessor.usage-docs] +command = "target/debug/usage-docs" diff --git a/docs/build.sh b/docs/build.sh deleted file mode 100755 index 06dc662c7..000000000 --- a/docs/build.sh +++ /dev/null @@ -1,23 +0,0 @@ -#! /bin/bash - -REMOTE=origin - -set -e - -if ! [ -f "./src/SUMMARY.md" ]; then - echo "Run this from the docs/ dir" - exit 1 -fi - -if ! [ -d ./tmp ]; then - git worktree add tmp gh-pages -fi - -(cd tmp && git pull $REMOTE gh-pages) - -rm -rf tmp/* -mdbook build -cp -rp book/* tmp -(cd tmp && git add -A) -(cd tmp && git commit -am "update docs") -(cd tmp && git push $REMOTE gh-pages:gh-pages) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index f69280b27..822dc4f7f 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -3,9 +3,11 @@ - [Welcome to TaskChampion](./welcome.md) * [Installation](./installation.md) * [Using the Task Command](./using-task-command.md) - * [Configuration](./config-file.md) * [Reports](./reports.md) * [Tags](./tags.md) + * [Filters](./filters.md) + * [Modifications](./modifications.md) + * [Configuration](./config-file.md) * [Environment](./environment.md) * [Synchronization](./task-sync.md) * [Running the Sync Server](./running-sync-server.md) diff --git a/docs/src/filters.md b/docs/src/filters.md new file mode 100644 index 000000000..e2be669d7 --- /dev/null +++ b/docs/src/filters.md @@ -0,0 +1,9 @@ +# Filters + +Filters are used to select specific tasks for reports or to specify tasks to be modified. +When more than one filter is given, only tasks which match all of the filters are selected. +When no filter is given, the command implicitly selects all tasks. + +Filters can have the following forms: + + diff --git a/docs/src/modifications.md b/docs/src/modifications.md new file mode 100644 index 000000000..017969951 --- /dev/null +++ b/docs/src/modifications.md @@ -0,0 +1,5 @@ +# Modifications + +Modifications can have the following forms: + + diff --git a/docs/src/reports.md b/docs/src/reports.md index 05026da6f..4f106e35b 100644 --- a/docs/src/reports.md +++ b/docs/src/reports.md @@ -49,9 +49,8 @@ columns = [ ] ``` -The filter is a list of filter arguments, just like those that can be used on the command line. -See the `ta help` output for more details on this syntax. -It will be merged with any filters provided on the command line, when the report is invoked. +The `filter` property is a list of [filters](./filters.md). +It will be merged with any filters provided on the command line when the report is invoked. The sort order is defined by an array of tables containing a `sort_by` property and an optional `ascending` property. Tasks are compared by the first criterion, and if that is equal by the second, and so on. @@ -70,11 +69,11 @@ sort = [ The available values of `sort_by` are -(TODO: generate automatically) + Finally, the `columns` configuration specifies the list of columns to display. Each element has a `label` and a `property`, as shown in the example above. The avaliable properties are: -(TODO: generate automatically) + diff --git a/docs/src/using-task-command.md b/docs/src/using-task-command.md index d2e7f0ca0..355dcc898 100644 --- a/docs/src/using-task-command.md +++ b/docs/src/using-task-command.md @@ -4,6 +4,13 @@ The main interface to your tasks is the `ta` command, which supports various sub Customizable [reports](./reports.md) are also available as subcommands, such as `next`. The command reads a [configuration file](./config-file.md) for its settings, including where to find the task database. And the `sync` subcommand [synchronizes tasks with a sync server](./task-sync.md). -You can find a list of all subcommands, as well as the built-in reports, with `ta help`. > NOTE: the `task` interface does not precisely match that of TaskWarrior. + +## Subcommands + +The sections below describe each subcommand of the `ta` command. +The syntax of `[filter]` is defined in [filters](./filters.md), and that of `[modification]` in [modifications](./modifications.md). +You can also find a summary of all subcommands, as well as filters, built-in reports, and so on, with `ta help`. + + From f91f7972445baec3c1033e458eae71153fb8cf98 Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 28 May 2021 11:38:46 +1000 Subject: [PATCH 269/548] Implement storage for local server --- taskchampion/src/server/local.rs | 67 +++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index aab3d86c1..47f8e5cc3 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -1,13 +1,13 @@ - use rusqlite::params; - use anyhow::Context; use crate::server::{ AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, }; +use anyhow::Context; +use rusqlite::params; +use rusqlite::types::{FromSql, ToSql}; +use rusqlite::OptionalExtension; use serde::{Deserialize, Serialize}; use std::path::Path; use uuid::Uuid; -use rusqlite::types::{FromSql, ToSql}; - use rusqlite::OptionalExtension; // FIXME: Duplicated /// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` @@ -30,6 +30,14 @@ impl ToSql for StoredUuid { } } +impl FromSql for Version { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let u = serde_json::from_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(u) + } +} + #[derive(Serialize, Deserialize, Debug)] struct Version { version_id: VersionId, @@ -49,32 +57,38 @@ impl LocalServer { /// A server which has no notion of clients, signatures, encryption, etc. pub fn new>(directory: P) -> anyhow::Result { - let db_file = directory.as_ref().join("taskchampion-local-sync-server.sqlite3"); + let db_file = directory + .as_ref() + .join("taskchampion-local-sync-server.sqlite3"); let con = rusqlite::Connection::open(&db_file)?; let queries = vec![ "CREATE TABLE IF NOT EXISTS data (key STRING PRIMARY KEY, value STRING);", + "CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, parent_version_id STRING, data STRING);", ]; for q in queries { con.execute(q, []).context("Creating table")?; } - Ok(LocalServer { - con, - }) + Ok(LocalServer { con }) } fn get_latest_version_id(&mut self) -> anyhow::Result { let t = self.txn()?; - let result: Option = t.query_row("SELECT value FROM data WHERE key = latest_version_id LIMIT 1", rusqlite::params![], |r| r.get(0)).optional()?; + let result: Option = t + .query_row( + "SELECT value FROM data WHERE key = 'latest_version_id' LIMIT 1", + rusqlite::params![], + |r| r.get(0), + ) + .optional()?; Ok(result.map(|x| x.0).unwrap_or(NO_VERSION_ID)) } fn set_latest_version_id(&mut self, version_id: VersionId) -> anyhow::Result<()> { - let t = self.txn()?; t.execute( - "INSERT OR REPLACE INTO tasks (uuid, data) VALUES (?, ?)", + "INSERT OR REPLACE INTO data (key, value) VALUES ('latest_version_id', ?)", params![&StoredUuid(version_id)], ) .context("Update task query")?; @@ -86,11 +100,38 @@ impl LocalServer { &mut self, parent_version_id: VersionId, ) -> anyhow::Result> { - todo!() + let t = self.txn()?; + let r = t.query_row( + "SELECT version_id, parent_version_id, data FROM versions WHERE parent_version_id = ?", + params![&StoredUuid(parent_version_id)], + |r| { + let version_id: StoredUuid = r.get("version_id")?; + let parent_version_id: StoredUuid = r.get("parent_version_id")?; + + Ok(Version{ + version_id: version_id.0, + parent_version_id: parent_version_id.0, + history_segment: r.get("data")?, + })} + ) + .optional() + .context("Get version query") + ?; + Ok(r) } fn add_version_by_parent_version_id(&mut self, version: Version) -> anyhow::Result<()> { - todo!() + let t = self.txn()?; + t.execute( + "INSERT INTO versions (version_id, parent_version_id, data) VALUES (?, ?, ?)", + params![ + StoredUuid(version.version_id), + StoredUuid(version.parent_version_id), + version.history_segment + ], + )?; + t.commit()?; + Ok(()) } } From 98f2ab51cb0c381f42698211d24682a06db57ea5 Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 28 May 2021 12:09:36 +1000 Subject: [PATCH 270/548] Functional sqlite backend ..but not too efficient, creating a new connection pretty much per-query --- sync-server/src/storage/sqlite.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index e974e33ef..bce885af1 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -86,7 +86,7 @@ impl SqliteStorage { let queries = vec![ "CREATE TABLE IF NOT EXISTS clients (client_key STRING PRIMARY KEY, latest_version_id STRING);", - "CREATE TABLE IF NOT EXISTS versions (id STRING PRIMARY KEY, client_key STRING, parent STRING, history_segment STRING);", + "CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, client_key STRING, parent_version_id STRING, history_segment STRING);", ]; for q in queries { txn.execute(q, []).context("Creating table")?; @@ -145,6 +145,7 @@ impl<'t> StorageTxn for Txn<'t> { params![&StoredUuid(client_key), &StoredUuid(latest_version_id)], ) .context("Create client query")?; + t.commit()?; Ok(()) } @@ -162,7 +163,24 @@ impl<'t> StorageTxn for Txn<'t> { client_key: Uuid, parent_version_id: Uuid, ) -> anyhow::Result> { - todo!() + let t = self.get_txn()?; + let r = t.query_row( + "SELECT version_id, parent_version_id, history_segment FROM versions WHERE parent_version_id = ? AND client_key = ?", + params![&StoredUuid(parent_version_id), &StoredUuid(client_key)], + |r| { + let version_id: StoredUuid = r.get("version_id")?; + let parent_version_id: StoredUuid = r.get("parent_version_id")?; + + Ok(Version{ + version_id: version_id.0, + parent_version_id: parent_version_id.0, + history_segment: r.get("history_segment")?, + })} + ) + .optional() + .context("Get version query") + ?; + Ok(r) } fn add_version( @@ -175,7 +193,7 @@ impl<'t> StorageTxn for Txn<'t> { let t = self.get_txn()?; t.execute( - "INSERT INTO versions (id, client_key, parent, history_segment) VALUES(?, ?, ?, ?)", + "INSERT INTO versions (version_id, client_key, parent_version_id, history_segment) VALUES(?, ?, ?, ?)", params![ StoredUuid(version_id), StoredUuid(client_key), @@ -184,7 +202,7 @@ impl<'t> StorageTxn for Txn<'t> { ], ) .context("Add version query")?; - + t.commit()?; Ok(()) } From baa6b59e398311c11afc8a3f4b33b592c2047055 Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 28 May 2021 12:10:18 +1000 Subject: [PATCH 271/548] Remove KvStorage --- Cargo.lock | 70 +--------- sync-server/Cargo.toml | 1 - sync-server/src/main.rs | 4 +- sync-server/src/storage/kv.rs | 241 --------------------------------- sync-server/src/storage/mod.rs | 3 - taskchampion/Cargo.toml | 1 - 6 files changed, 4 insertions(+), 316 deletions(-) delete mode 100644 sync-server/src/storage/kv.rs diff --git a/Cargo.lock b/Cargo.lock index 75eabf642..41b9a1ed3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "actix-codec" version = "0.3.0" @@ -1161,19 +1163,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "kv" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" -dependencies = [ - "lmdb-rkv", - "rmp-serde", - "serde", - "thiserror", - "toml", -] - [[package]] name = "language-tags" version = "0.2.2" @@ -1222,29 +1211,6 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" -[[package]] -name = "lmdb-rkv" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605061e5465304475be2041f19967a900175ea1b6d8f47fbab84a84fb8c48452" -dependencies = [ - "bitflags", - "byteorder", - "libc", - "lmdb-rkv-sys", -] - -[[package]] -name = "lmdb-rkv-sys" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7982ba0460e939e26a52ee12c8075deab0ebd44ed21881f656841b70e021b7c8" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "lock_api" version = "0.4.3" @@ -1865,27 +1831,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "rmp" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" -dependencies = [ - "byteorder", - "num-traits", -] - -[[package]] -name = "rmp-serde" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" -dependencies = [ - "byteorder", - "rmp", - "serde", -] - [[package]] name = "rusqlite" version = "0.25.1" @@ -2183,7 +2128,6 @@ version = "0.3.0" dependencies = [ "anyhow", "chrono", - "kv", "log", "proptest", "rusqlite", @@ -2226,7 +2170,6 @@ dependencies = [ "clap", "env_logger", "futures", - "kv", "log", "rusqlite", "serde", @@ -2454,15 +2397,6 @@ dependencies = [ "tokio 0.2.25", ] -[[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" -dependencies = [ - "serde", -] - [[package]] name = "tracing" version = "0.1.25" diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index c9d665ba0..6c66f6726 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -14,7 +14,6 @@ thiserror = "1.0" futures = "^0.3.8" serde = "^1.0.125" serde_json = "^1.0" -kv = {version = "^0.10.0", features = ["msgpack-value"]} clap = "^2.33.0" log = "^0.4.14" env_logger = "^0.8.3" diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 2ae65ae5c..394031738 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,6 +1,6 @@ #![deny(clippy::all)] -use crate::storage::{KvStorage, Storage}; +use crate::storage::{SqliteStorage, Storage}; use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder, Scope}; use api::{api_scope, ServerState}; use clap::Arg; @@ -56,7 +56,7 @@ async fn main() -> anyhow::Result<()> { let data_dir = matches.value_of("data-dir").unwrap(); let port = matches.value_of("port").unwrap(); - let server_box: Box = Box::new(KvStorage::new(data_dir)?); + let server_box: Box = Box::new(SqliteStorage::new(data_dir)?); let server_state = ServerState::new(server_box); log::warn!("Serving on port {}", port); diff --git a/sync-server/src/storage/kv.rs b/sync-server/src/storage/kv.rs deleted file mode 100644 index bd5fa3502..000000000 --- a/sync-server/src/storage/kv.rs +++ /dev/null @@ -1,241 +0,0 @@ -use super::{Client, Storage, StorageTxn, Uuid, Version}; -use kv::msgpack::Msgpack; -use kv::{Bucket, Config, Error, Serde, Store, ValueBuf}; -use std::path::Path; - -/// DB Key for versions: concatenation of client_key and parent_version_id -type VersionDbKey = [u8; 32]; - -fn version_db_key(client_key: Uuid, parent_version_id: Uuid) -> VersionDbKey { - let mut key = [0u8; 32]; - key[..16].clone_from_slice(client_key.as_bytes()); - key[16..].clone_from_slice(parent_version_id.as_bytes()); - key -} - -/// Key for clients: just the client_key -type ClientDbKey = [u8; 16]; - -fn client_db_key(client_key: Uuid) -> ClientDbKey { - *client_key.as_bytes() -} - -/// KvStorage is an on-disk storage backend which uses LMDB via the `kv` crate. -pub(crate) struct KvStorage<'t> { - store: Store, - clients_bucket: Bucket<'t, ClientDbKey, ValueBuf>>, - versions_bucket: Bucket<'t, VersionDbKey, ValueBuf>>, -} - -impl<'t> KvStorage<'t> { - pub fn new>(directory: P) -> anyhow::Result> { - let mut config = Config::default(directory); - config.bucket("clients", None); - config.bucket("versions", None); - - let store = Store::new(config)?; - - let clients_bucket = - store.bucket::>>(Some("clients"))?; - let versions_bucket = - store.bucket::>>(Some("versions"))?; - - Ok(KvStorage { - store, - clients_bucket, - versions_bucket, - }) - } -} - -impl<'t> Storage for KvStorage<'t> { - fn txn<'a>(&'a self) -> anyhow::Result> { - Ok(Box::new(Txn { - storage: self, - txn: Some(self.store.write_txn()?), - })) - } -} - -struct Txn<'t> { - storage: &'t KvStorage<'t>, - txn: Option>, -} - -impl<'t> Txn<'t> { - // get the underlying kv Txn - fn kvtxn(&mut self) -> &mut kv::Txn<'t> { - if let Some(ref mut txn) = self.txn { - txn - } else { - panic!("cannot use transaction after commit"); - } - } - - fn clients_bucket(&self) -> &'t Bucket<'t, ClientDbKey, ValueBuf>> { - &self.storage.clients_bucket - } - fn versions_bucket(&self) -> &'t Bucket<'t, VersionDbKey, ValueBuf>> { - &self.storage.versions_bucket - } -} - -impl<'t> StorageTxn for Txn<'t> { - fn get_client(&mut self, client_key: Uuid) -> anyhow::Result> { - let key = client_db_key(client_key); - let bucket = self.clients_bucket(); - let kvtxn = self.kvtxn(); - - let client = match kvtxn.get(&bucket, key) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(None), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(Some(client)) - } - - fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { - let key = client_db_key(client_key); - let bucket = self.clients_bucket(); - let kvtxn = self.kvtxn(); - let client = Client { latest_version_id }; - kvtxn.set(&bucket, key, Msgpack::to_value_buf(client)?)?; - Ok(()) - } - - fn set_client_latest_version_id( - &mut self, - client_key: Uuid, - latest_version_id: Uuid, - ) -> anyhow::Result<()> { - // implementation is the same as new_client.. - self.new_client(client_key, latest_version_id) - } - - fn get_version_by_parent( - &mut self, - client_key: Uuid, - parent_version_id: Uuid, - ) -> anyhow::Result> { - let key = version_db_key(client_key, parent_version_id); - let bucket = self.versions_bucket(); - let kvtxn = self.kvtxn(); - let version = match kvtxn.get(&bucket, key) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(None), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(Some(version)) - } - - fn add_version( - &mut self, - client_key: Uuid, - version_id: Uuid, - parent_version_id: Uuid, - history_segment: Vec, - ) -> anyhow::Result<()> { - let key = version_db_key(client_key, parent_version_id); - let bucket = self.versions_bucket(); - let kvtxn = self.kvtxn(); - let version = Version { - version_id, - parent_version_id, - history_segment, - }; - kvtxn.set(&bucket, key, Msgpack::to_value_buf(version)?)?; - Ok(()) - } - - fn commit(&mut self) -> anyhow::Result<()> { - if let Some(kvtxn) = self.txn.take() { - kvtxn.commit()?; - } else { - panic!("transaction already committed"); - } - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use tempfile::TempDir; - - #[test] - fn test_get_client_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let storage = KvStorage::new(&tmp_dir.path())?; - let mut txn = storage.txn()?; - let maybe_client = txn.get_client(Uuid::new_v4())?; - assert!(maybe_client.is_none()); - Ok(()) - } - - #[test] - fn test_client_storage() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let storage = KvStorage::new(&tmp_dir.path())?; - let mut txn = storage.txn()?; - - let client_key = Uuid::new_v4(); - let latest_version_id = Uuid::new_v4(); - txn.new_client(client_key, latest_version_id)?; - - let client = txn.get_client(client_key)?.unwrap(); - assert_eq!(client.latest_version_id, latest_version_id); - - let latest_version_id = Uuid::new_v4(); - txn.set_client_latest_version_id(client_key, latest_version_id)?; - - let client = txn.get_client(client_key)?.unwrap(); - assert_eq!(client.latest_version_id, latest_version_id); - - Ok(()) - } - - #[test] - fn test_gvbp_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let storage = KvStorage::new(&tmp_dir.path())?; - let mut txn = storage.txn()?; - let maybe_version = txn.get_version_by_parent(Uuid::new_v4(), Uuid::new_v4())?; - assert!(maybe_version.is_none()); - Ok(()) - } - - #[test] - fn test_add_version_and_gvbp() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let storage = KvStorage::new(&tmp_dir.path())?; - let mut txn = storage.txn()?; - - let client_key = Uuid::new_v4(); - let version_id = Uuid::new_v4(); - let parent_version_id = Uuid::new_v4(); - let history_segment = b"abc".to_vec(); - txn.add_version( - client_key, - version_id, - parent_version_id, - history_segment.clone(), - )?; - let version = txn - .get_version_by_parent(client_key, parent_version_id)? - .unwrap(); - - assert_eq!( - version, - Version { - version_id, - parent_version_id, - history_segment, - } - ); - Ok(()) - } -} diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 41fb400e9..f9a2fc699 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -9,9 +9,6 @@ pub(crate) use inmemory::InMemoryStorage; mod sqlite; pub(crate) use self::sqlite::SqliteStorage; -mod kv; -pub(crate) use self::kv::KvStorage; - #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub(crate) struct Client { pub(crate) latest_version_id: Uuid, diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 03a047c88..2231395fa 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -17,7 +17,6 @@ serde_json = "^1.0" chrono = { version = "^0.4.10", features = ["serde"] } anyhow = "1.0" thiserror = "1.0" -kv = {version = "^0.10.0", features = ["msgpack-value"]} ureq = "^2.1.0" log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } From 3c8c7d4888e8cf77ecfc809a8d16f55b627fa50f Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 28 May 2021 12:19:49 +1000 Subject: [PATCH 272/548] Updated Cargo.lock --- Cargo.lock | 2802 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2802 insertions(+) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..c65d01171 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2802 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "actix-codec" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570" +dependencies = [ + "bitflags", + "bytes 0.5.6", + "futures-core", + "futures-sink", + "log", + "pin-project 0.4.28", + "tokio 0.2.25", + "tokio-util", +] + +[[package]] +name = "actix-connect" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" +dependencies = [ + "actix-codec", + "actix-rt 1.1.1", + "actix-service", + "actix-utils", + "derive_more", + "either", + "futures-util", + "http", + "log", + "trust-dns-proto", + "trust-dns-resolver", +] + +[[package]] +name = "actix-http" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" +dependencies = [ + "actix-codec", + "actix-connect", + "actix-rt 1.1.1", + "actix-service", + "actix-threadpool", + "actix-utils", + "base64", + "bitflags", + "brotli2", + "bytes 0.5.6", + "cookie", + "copyless", + "derive_more", + "either", + "encoding_rs", + "flate2", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "h2", + "http", + "httparse", + "indexmap", + "itoa", + "language-tags", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project 1.0.7", + "rand 0.7.3", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "sha-1", + "slab", + "time 0.2.26", +] + +[[package]] +name = "actix-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb2b608f0accc2f5bcf3dd872194ce13d94ee45b571487035864cf966b04ef" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad299af73649e1fc893e333ccf86f377751eb95ff875d095131574c6f43452c" +dependencies = [ + "bytestring", + "http", + "log", + "regex", + "serde", +] + +[[package]] +name = "actix-rt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" +dependencies = [ + "actix-macros 0.1.3", + "actix-threadpool", + "copyless", + "futures-channel", + "futures-util", + "smallvec", + "tokio 0.2.25", +] + +[[package]] +name = "actix-rt" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d7cd957c9ed92288a7c3c96af81fa5291f65247a76a34dac7b6af74e52ba0" +dependencies = [ + "actix-macros 0.2.0", + "futures-core", + "tokio 1.6.0", +] + +[[package]] +name = "actix-server" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" +dependencies = [ + "actix-codec", + "actix-rt 1.1.1", + "actix-service", + "actix-utils", + "futures-channel", + "futures-util", + "log", + "mio 0.6.23", + "mio-uds", + "num_cpus", + "slab", + "socket2", +] + +[[package]] +name = "actix-service" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" +dependencies = [ + "futures-util", + "pin-project 0.4.28", +] + +[[package]] +name = "actix-testing" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" +dependencies = [ + "actix-macros 0.1.3", + "actix-rt 1.1.1", + "actix-server", + "actix-service", + "log", + "socket2", +] + +[[package]] +name = "actix-threadpool" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30" +dependencies = [ + "derive_more", + "futures-channel", + "lazy_static", + "log", + "num_cpus", + "parking_lot", + "threadpool", +] + +[[package]] +name = "actix-tls" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb" +dependencies = [ + "actix-codec", + "actix-service", + "actix-utils", + "futures-util", +] + +[[package]] +name = "actix-utils" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" +dependencies = [ + "actix-codec", + "actix-rt 1.1.1", + "actix-service", + "bitflags", + "bytes 0.5.6", + "either", + "futures-channel", + "futures-sink", + "futures-util", + "log", + "pin-project 0.4.28", + "slab", +] + +[[package]] +name = "actix-web" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros 0.1.3", + "actix-router", + "actix-rt 1.1.1", + "actix-server", + "actix-service", + "actix-testing", + "actix-threadpool", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "awc", + "bytes 0.5.6", + "derive_more", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "log", + "mime", + "pin-project 1.0.7", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "socket2", + "time 0.2.26", + "tinyvec", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98" +dependencies = [ + "getrandom 0.2.3", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "anyhow" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "ascii" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" + +[[package]] +name = "assert_cmd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f57fec1ac7e4de72dcc69811795f1a7172ed06012f80a5d1ee651b62484f588" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "async-trait" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "awc" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt 1.1.1", + "actix-service", + "base64", + "bytes 0.5.6", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "log", + "mime", + "percent-encoding", + "rand 0.7.3", + "serde", + "serde_json", + "serde_urlencoded", +] + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "brotli2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +dependencies = [ + "brotli-sys", + "libc", +] + +[[package]] +name = "bstr" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + +[[package]] +name = "bytestring" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" +dependencies = [ + "bytes 1.0.1", +] + +[[package]] +name = "cc" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "serde", + "time 0.1.43", + "winapi 0.3.9", +] + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap 0.11.0", + "unicode-width", + "vec_map", +] + +[[package]] +name = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] + +[[package]] +name = "const_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" +dependencies = [ + "percent-encoding", + "time 0.2.26", + "version_check", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "cpufeatures" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "derive_more" +version = "0.99.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +dependencies = [ + "libc", + "redox_users 0.3.5", + "winapi 0.3.9", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.0", + "winapi 0.3.9", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enum-as-inner" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "flate2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + +[[package]] +name = "futures" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" + +[[package]] +name = "futures-executor" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" + +[[package]] +name = "futures-macro" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" +dependencies = [ + "autocfg", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" + +[[package]] +name = "futures-task" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" + +[[package]] +name = "futures-util" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" +dependencies = [ + "autocfg", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite 0.2.6", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio 0.2.25", + "tokio-util", + "tracing", + "tracing-futures", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown 0.11.2", +] + +[[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", +] + +[[package]] +name = "http" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" +dependencies = [ + "bytes 1.0.1", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +dependencies = [ + "autocfg", + "hashbrown 0.9.1", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2", + "widestring", + "winapi 0.3.9", + "winreg", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "js-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" + +[[package]] +name = "libsqlite3-sys" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +dependencies = [ + "libc", + "log", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio 0.6.23", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall 0.2.8", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918192b5c59119d51e0cd221f4d49dde9112824ba717369e903c97d076083d0f" +dependencies = [ + "pin-project-internal 0.4.28", +] + +[[package]] +name = "pin-project" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" +dependencies = [ + "pin-project-internal 1.0.7", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be26700300be6d9d23264c73211d8190e755b6b5ca7a1b28230025511b52a5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "predicates" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" +dependencies = [ + "difference", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" + +[[package]] +name = "predicates-tree" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" +dependencies = [ + "predicates-core", + "treeline", +] + +[[package]] +name = "prettytable-rs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" +dependencies = [ + "atty", + "csv", + "encode_unicode", + "lazy_static", + "term", + "unicode-width", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "proptest" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error 2.0.1", + "rand 0.8.3", + "rand_chacha 0.3.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", +] + +[[package]] +name = "protobuf" +version = "2.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45604fc7a88158e7d514d8e22e14ac746081e7a70d7690074dd0029ee37458d6" + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha 0.3.0", + "rand_core 0.6.2", + "rand_hc 0.3.0", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom 0.2.3", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core 0.6.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_syscall" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom 0.1.16", + "redox_syscall 0.1.57", + "rust-argon2", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.3", + "redox_syscall 0.2.8", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error 1.2.3", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rusqlite" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57adcf67c8faaf96f3248c2a7b419a0dbc52ebe36ba83dd57fe83827c1ea4eb3" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "signal-hook-registry" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "taskchampion" +version = "0.3.0" +dependencies = [ + "anyhow", + "chrono", + "log", + "proptest", + "rusqlite", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tindercrypt", + "ureq", + "uuid", +] + +[[package]] +name = "taskchampion-cli" +version = "0.3.0" +dependencies = [ + "anyhow", + "assert_cmd", + "atty", + "dirs-next", + "env_logger", + "log", + "nom", + "predicates", + "prettytable-rs", + "taskchampion", + "tempfile", + "termcolor", + "textwrap 0.13.4", + "toml", + "toml_edit", +] + +[[package]] +name = "taskchampion-sync-server" +version = "0.3.0" +dependencies = [ + "actix-rt 2.2.0", + "actix-web", + "anyhow", + "clap", + "env_logger", + "futures", + "log", + "rusqlite", + "serde", + "serde_json", + "tempfile", + "thiserror", + "uuid", +] + +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand 0.8.3", + "redox_syscall 0.2.8", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "term" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" +dependencies = [ + "byteorder", + "dirs", + "winapi 0.3.9", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "textwrap" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835" +dependencies = [ + "smawk", + "terminal_size", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi 0.3.9", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tindercrypt" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34fd5cc8db265f27abf29e8a3cec5cc643ae9f3f9ae39f08a77dc4add375b6d" +dependencies = [ + "protobuf", + "rand 0.7.3", + "ring", + "thiserror", +] + +[[package]] +name = "tinyvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes 0.5.6", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio 0.6.23", + "mio-uds", + "pin-project-lite 0.1.12", + "signal-hook-registry", + "slab", + "winapi 0.3.9", +] + +[[package]] +name = "tokio" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd3076b5c8cc18138b8f8814895c11eb4de37114a5d127bafdc5e55798ceef37" +dependencies = [ + "autocfg", + "libc", + "mio 0.7.11", + "once_cell", + "parking_lot", + "pin-project-lite 0.2.6", + "signal-hook-registry", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes 0.5.6", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.1.12", + "tokio 0.2.25", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09391a441b373597cf0888d2b052dcf82c5be4fee05da3636ae30fb57aad8484" +dependencies = [ + "chrono", + "combine", + "linked-hash-map", +] + +[[package]] +name = "tracing" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite 0.2.6", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project 1.0.7", + "tracing", +] + +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + +[[package]] +name = "trust-dns-proto" +version = "0.19.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9" +dependencies = [ + "async-trait", + "cfg-if 1.0.0", + "enum-as-inner", + "futures", + "idna", + "lazy_static", + "log", + "rand 0.7.3", + "smallvec", + "thiserror", + "tokio 0.2.25", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.19.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb" +dependencies = [ + "cfg-if 0.1.10", + "futures", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "resolv-conf", + "smallvec", + "thiserror", + "tokio 0.2.25", + "trust-dns-proto", +] + +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2475a6781e9bc546e7b64f4013d2f4032c8c6a40fcffd7c6f4ee734a890972ab" +dependencies = [ + "base64", + "chunked_transfer", + "log", + "once_cell", + "rustls", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.3", + "serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "025ce40a007e1907e58d5bc1a594def78e5573bb0b1160bc389634e8f12e4faa" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" + +[[package]] +name = "web-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" From 0f6323e2dee4a545f7aefcec17f420fa930db4f1 Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 28 May 2021 20:35:57 +1000 Subject: [PATCH 273/548] Unused --- sync-server/src/storage/sqlite.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index bce885af1..a4b91134b 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -48,23 +48,6 @@ impl ToSql for Client { } } -/// DB Key for versions: concatenation of client_key and parent_version_id -type VersionDbKey = [u8; 32]; - -fn version_db_key(client_key: Uuid, parent_version_id: Uuid) -> VersionDbKey { - let mut key = [0u8; 32]; - key[..16].clone_from_slice(client_key.as_bytes()); - key[16..].clone_from_slice(parent_version_id.as_bytes()); - key -} - -/// Key for clients: just the client_key -type ClientDbKey = [u8; 16]; - -fn client_db_key(client_key: Uuid) -> ClientDbKey { - *client_key.as_bytes() -} - /// An on-disk storage backend which uses SQLite pub(crate) struct SqliteStorage { db_file: std::path::PathBuf, From 7c665c9a771048964fd73b5d67455ca32bf98f7e Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 28 May 2021 20:41:03 +1000 Subject: [PATCH 274/548] Clippy things --- sync-server/src/storage/sqlite.rs | 7 ++++++- taskchampion/src/server/local.rs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index a4b91134b..bfdf2a8b2 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -8,6 +8,8 @@ use std::path::Path; enum SqliteError { #[error("SQLite transaction already committted")] TransactionAlreadyCommitted, + #[error("Failed to create SQLite transaction")] + CreateTransactionFailed, } /// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` @@ -96,7 +98,10 @@ struct Txn<'t> { impl<'t> Txn<'t> { fn get_txn(&mut self) -> Result { - Ok(self.con.transaction().unwrap()) + Ok(self + .con + .transaction() + .map_err(|_e| SqliteError::CreateTransactionFailed)?) } } diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 47f8e5cc3..8b5dfa9a7 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -50,7 +50,7 @@ pub struct LocalServer { } impl LocalServer { - fn txn<'a>(&'a mut self) -> anyhow::Result { + fn txn(&mut self) -> anyhow::Result { let txn = self.con.transaction()?; Ok(txn) } From d4f669ed6e492585de63b200b3c503d73d298c7a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 30 May 2021 16:04:24 -0400 Subject: [PATCH 275/548] Define and test an MSRV --- .../workflows/{rust-tests.yml => checks.yml} | 31 ++----------- .github/workflows/tests.yml | 43 +++++++++++++++++++ taskchampion/src/lib.rs | 4 ++ 3 files changed, 50 insertions(+), 28 deletions(-) rename .github/workflows/{rust-tests.yml => checks.yml} (73%) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/checks.yml similarity index 73% rename from .github/workflows/rust-tests.yml rename to .github/workflows/checks.yml index ab56e3c39..ea268986d 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/checks.yml @@ -8,8 +8,9 @@ on: types: [opened, reopened, synchronize] jobs: - test: + clippy: runs-on: ubuntu-latest + name: "Clippy" steps: - uses: actions/checkout@v1 @@ -26,37 +27,10 @@ jobs: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - uses: actions-rs/cargo@v1.0.1 with: command: check - - name: test - run: cargo test - - clippy: - runs-on: ubuntu-latest - needs: test - - steps: - - uses: actions/checkout@v1 - - - name: Cache cargo registry - uses: actions/cache@v1 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo build - uses: actions/cache@v1 - with: - path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - run: rustup component add clippy - uses: actions-rs/clippy-check@v1 @@ -66,6 +40,7 @@ jobs: mdbook: runs-on: ubuntu-latest + name: "Documentation" steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..e61b5afdc --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,43 @@ +name: taskchampion + +on: + push: + branches: + - main + pull_request: + types: [opened, reopened, synchronize] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + rust: + - "1.47" # MSRV + - "stable" + + name: "Test - Rust ${{ matrix.rust }}" + + steps: + - uses: actions/checkout@v1 + + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions-rs/toolchain@v1 + with: + toolchain: "${{ matrix.rust }}" + override: true + + - name: test + run: cargo test diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index a05b1ab7b..665fe3a64 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -29,6 +29,10 @@ Users can define their own server impelementations. See the [TaskChampion Book](http://taskchampion.github.com/taskchampion) for more information about the design and usage of the tool. +# Minimum Supported Rust Version + +This crate supports Rust version 1.47 and higher. + */ mod errors; From 9e3646bf842b12015607da3268ace0714c0a8d2d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 30 May 2021 16:36:20 -0400 Subject: [PATCH 276/548] Summarize tasks nicely in console output --- cli/src/invocation/cmd/modify.rs | 10 +++++++--- cli/src/invocation/mod.rs | 13 ++++++++++++- cli/src/invocation/modify.rs | 6 +----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/cli/src/invocation/cmd/modify.rs b/cli/src/invocation/cmd/modify.rs index 6f17f0dba..c3fee9a40 100644 --- a/cli/src/invocation/cmd/modify.rs +++ b/cli/src/invocation/cmd/modify.rs @@ -1,5 +1,5 @@ use crate::argparse::{Filter, Modification}; -use crate::invocation::{apply_modification, filtered_tasks}; +use crate::invocation::{apply_modification, filtered_tasks, summarize_task}; use taskchampion::Replica; use termcolor::WriteColor; @@ -12,7 +12,11 @@ pub(crate) fn execute( for task in filtered_tasks(replica, &filter)? { let mut task = task.into_mut(replica); - apply_modification(w, &mut task, &modification)?; + apply_modification(&mut task, &modification)?; + + let task = task.into_immut(); + let summary = summarize_task(replica, &task)?; + writeln!(w, "modified task {}", summary)?; } Ok(()) @@ -51,7 +55,7 @@ mod test { assert_eq!( w.into_string(), - format!("modified task {}\n", task.get_uuid()) + format!("modified task 1 - new description\n") ); } } diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 985c4d99e..80c49db75 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -2,7 +2,7 @@ use crate::argparse::{Command, Subcommand}; use crate::settings::Settings; -use taskchampion::{Replica, Server, ServerConfig, StorageConfig, Uuid}; +use taskchampion::{Replica, Server, ServerConfig, StorageConfig, Task, Uuid}; use termcolor::{ColorChoice, StandardStream}; mod cmd; @@ -149,3 +149,14 @@ fn get_writer() -> StandardStream { ColorChoice::Never }) } + +/// Summarize a task in a single line +fn summarize_task(replica: &mut Replica, task: &Task) -> anyhow::Result { + let ws = replica.working_set()?; + let uuid = task.get_uuid(); + if let Some(id) = ws.by_uuid(uuid) { + Ok(format!("{} - {}", id, task.get_description())) + } else { + Ok(format!("{} - {}", uuid, task.get_description())) + } +} diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index b67620788..cbbb65e1c 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,11 +1,9 @@ use crate::argparse::{DescriptionMod, Modification}; use std::convert::TryInto; use taskchampion::TaskMut; -use termcolor::WriteColor; /// Apply the given modification -pub(super) fn apply_modification( - w: &mut W, +pub(super) fn apply_modification( task: &mut TaskMut, modification: &Modification, ) -> anyhow::Result<()> { @@ -42,7 +40,5 @@ pub(super) fn apply_modification( task.remove_tag(&tag)?; } - writeln!(w, "modified task {}", task.get_uuid())?; - Ok(()) } From e81a078506110ef69a10df5416b095855d71a14b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 30 May 2021 19:01:16 -0400 Subject: [PATCH 277/548] Create SECURITY.md based on POLICY.md --- POLICY.md | 10 +--------- SECURITY.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 SECURITY.md diff --git a/POLICY.md b/POLICY.md index fb673155d..3d84cbb82 100644 --- a/POLICY.md +++ b/POLICY.md @@ -42,12 +42,4 @@ Considered to be part of the API policy. # Security -To report a vulnerability, please contact [dustin@cs.uchicago.edu](dustin@cs.uchicago.edu), you may use GPG public-key `D8097934A92E4B4210368102FF8B7AC6154E3226` which is available [here](https://keybase.io/djmitche/pgp_keys.asc?fingerprint=d8097934a92e4b4210368102ff8b7ac6154e3226). Initial response is expected within ~48h. - -We kinldy ask to follow the responsible disclosure model and refrain from sharing information until: -1. Vulnerabilities are patched in TaskChampion + 60 days to coordinate with distributions. -2. 90 days since the vulnerability is disclosed to us. - -We recognise the legitimacy of public interest and accept that security researchers can publish information after 90-days deadline unilaterally. - -We will assist with obtaining CVE and acknowledge the vulnerabilites reported. +See [SECURITY.md](./SECURITY.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..9d8d975d9 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security + +To report a vulnerability, please contact [dustin@cs.uchicago.edu](dustin@cs.uchicago.edu), you may use GPG public-key `D8097934A92E4B4210368102FF8B7AC6154E3226` which is available [here](https://keybase.io/djmitche/pgp_keys.asc?fingerprint=d8097934a92e4b4210368102ff8b7ac6154e3226). Initial response is expected within ~48h. + +We kindly ask to follow the responsible disclosure model and refrain from sharing information until: +1. Vulnerabilities are patched in TaskChampion + 60 days to coordinate with distributions. +2. 90 days since the vulnerability is disclosed to us. + +We recognise the legitimacy of public interest and accept that security researchers can publish information after 90-days deadline unilaterally. + +We will assist with obtaining CVE and acknowledge the vulnerabilites reported. From e977fb294c0b14cf6e65e289cda367caef560b39 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 May 2021 18:16:11 -0400 Subject: [PATCH 278/548] Implement modifying tasks' "wait" value --- Cargo.lock | 2 ++ cli/Cargo.toml | 2 ++ cli/src/argparse/args.rs | 37 +++++++++++++++++++ cli/src/argparse/mod.rs | 7 ++++ cli/src/argparse/modification.rs | 62 +++++++++++++++++++++++++++++--- cli/src/invocation/modify.rs | 4 +++ docs/src/tasks.md | 1 + taskchampion/src/task.rs | 55 ++++++++++++++++++++++++++++ 8 files changed, 166 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 144f8e56f..6499dfbb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2809,8 +2809,10 @@ dependencies = [ "anyhow", "assert_cmd", "atty", + "chrono", "dirs-next", "env_logger 0.8.3", + "lazy_static", "log", "mdbook", "nom", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 591d068a9..53c26dd2b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -22,6 +22,8 @@ termcolor = "^1.1.2" atty = "^0.2.14" toml = "^0.5.8" toml_edit = "^0.2.0" +chrono = "*" +lazy_static = "1" # only needed for usage-docs mdbook = { version = "0.4", optional = true } diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs index a902fd690..fceea572a 100644 --- a/cli/src/argparse/args.rs +++ b/cli/src/argparse/args.rs @@ -1,5 +1,7 @@ //! Parsers for argument lists -- arrays of strings use super::ArgList; +use super::NOW; +use chrono::prelude::*; use nom::bytes::complete::tag as nomtag; use nom::{ branch::*, @@ -67,6 +69,30 @@ pub(super) fn status_colon(input: &str) -> IResult<&str, Status> { map_res(colon_prefixed("status"), to_status)(input) } +/// Recognizes timestamps +pub(super) fn timestamp(input: &str) -> IResult<&str, DateTime> { + // TODO: full relative date language supported by TW + fn nn_d_to_timestamp(input: &str) -> Result, ()> { + // TODO: don't unwrap + Ok(*NOW + chrono::Duration::days(input.parse().unwrap())) + } + map_res(terminated(digit1, char('d')), nn_d_to_timestamp)(input) +} + +/// Recognizes `wait:` to None and `wait:` to `Some(ts)` +pub(super) fn wait_colon(input: &str) -> IResult<&str, Option>> { + fn to_wait(input: DateTime) -> Result>, ()> { + Ok(Some(input)) + } + fn to_none(_: &str) -> Result>, ()> { + Ok(None) + } + preceded( + nomtag("wait:"), + alt((map_res(timestamp, to_wait), map_res(nomtag(""), to_none))), + )(input) +} + /// Recognizes a comma-separated list of TaskIds pub(super) fn id_list(input: &str) -> IResult<&str, Vec> { fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> { @@ -237,6 +263,17 @@ mod test { assert!(minus_tag("-1abc").is_err()); } + #[test] + fn test_wait() { + assert_eq!(wait_colon("wait:").unwrap(), ("", None)); + + let one_day = *NOW + chrono::Duration::days(1); + assert_eq!(wait_colon("wait:1d").unwrap(), ("", Some(one_day))); + + let one_day = *NOW + chrono::Duration::days(1); + assert_eq!(wait_colon("wait:1d2").unwrap(), ("2", Some(one_day))); + } + #[test] fn test_literal() { assert_eq!(literal("list")("list").unwrap().1, "list"); diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index 88de59046..f837b0ecf 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -31,9 +31,16 @@ pub(crate) use modification::{DescriptionMod, Modification}; pub(crate) use subcommand::Subcommand; use crate::usage::Usage; +use chrono::prelude::*; +use lazy_static::lazy_static; type ArgList<'a> = &'a [&'a str]; +lazy_static! { + // A static value of NOW to make tests easier + pub(super) static ref NOW: DateTime = Utc::now(); +} + pub(crate) fn get_usage(usage: &mut Usage) { Subcommand::get_usage(usage); Filter::get_usage(usage); diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 9628f2352..5b3cac3df 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -1,6 +1,7 @@ -use super::args::{any, arg_matching, minus_tag, plus_tag}; +use super::args::{any, arg_matching, minus_tag, plus_tag, wait_colon}; use super::ArgList; use crate::usage; +use chrono::prelude::*; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use std::collections::HashSet; use taskchampion::Status; @@ -36,6 +37,9 @@ pub struct Modification { /// Set the status pub status: Option, + /// Set (or, with `Some(None)`, clear) the wait timestamp + pub wait: Option>>, + /// Set the "active" state, that is, start (true) or stop (false) the task. pub active: Option, @@ -51,6 +55,7 @@ enum ModArg<'a> { Description(&'a str), PlusTag(&'a str), MinusTag(&'a str), + Wait(Option>), } impl Modification { @@ -71,6 +76,9 @@ impl Modification { ModArg::MinusTag(tag) => { acc.remove_tags.insert(tag.to_owned()); } + ModArg::Wait(wait) => { + acc.wait = Some(wait); + } } acc } @@ -78,6 +86,7 @@ impl Modification { alt(( Self::plus_tag, Self::minus_tag, + Self::wait, // this must come last Self::description, )), @@ -109,6 +118,13 @@ impl Modification { map_res(arg_matching(minus_tag), to_modarg)(input) } + fn wait(input: ArgList) -> IResult { + fn to_modarg(input: Option>) -> Result, ()> { + Ok(ModArg::Wait(input)) + } + map_res(arg_matching(wait_colon), to_modarg)(input) + } + pub(super) fn get_usage(u: &mut usage::Usage) { u.modifications.push(usage::Modification { syntax: "DESCRIPTION", @@ -122,14 +138,25 @@ impl Modification { u.modifications.push(usage::Modification { syntax: "+TAG", summary: "Tag task", - description: " - Add the given tag to the task.", + description: "Add the given tag to the task.", }); u.modifications.push(usage::Modification { syntax: "-TAG", summary: "Un-tag task", + description: "Remove the given tag from the task.", + }); + u.modifications.push(usage::Modification { + syntax: "status:{pending,completed,deleted}", + summary: "Set the task's status", + description: "Set the status of the task explicitly.", + }); + u.modifications.push(usage::Modification { + syntax: "wait:", + summary: "Set or unset the task's wait time", description: " - Remove the given tag from the task.", + Set the time before which the task is not actionable and + should not be shown in reports. With `wait:`, the time + is un-set.", }); } } @@ -137,6 +164,7 @@ impl Modification { #[cfg(test)] mod test { use super::*; + use crate::argparse::NOW; #[test] fn test_empty() { @@ -176,6 +204,32 @@ mod test { ); } + #[test] + fn test_set_wait() { + let (input, modification) = Modification::parse(argv!["wait:2d"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + modification, + Modification { + wait: Some(Some(*NOW + chrono::Duration::days(2))), + ..Default::default() + } + ); + } + + #[test] + fn test_unset_wait() { + let (input, modification) = Modification::parse(argv!["wait:"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + modification, + Modification { + wait: Some(None), + ..Default::default() + } + ); + } + #[test] fn test_multi_arg_description() { let (input, modification) = Modification::parse(argv!["new", "desc", "fun"]).unwrap(); diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index cbbb65e1c..7ef6d758c 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -40,5 +40,9 @@ pub(super) fn apply_modification( task.remove_tag(&tag)?; } + if let Some(wait) = modification.wait { + task.set_wait(wait)?; + } + Ok(()) } diff --git a/docs/src/tasks.md b/docs/src/tasks.md index 8bdf8fa72..ae354c62c 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -32,6 +32,7 @@ The following keys, and key formats, are defined: * `modified` - the time of the last modification of this task * `start.` - either an empty string (representing work on the task to the task that has not been stopped) or a timestamp (representing the time that work stopped) * `tag.` - indicates this task has tag `` (value is an empty string) +* `wait` - indicates the time before which this task should be hidden, as it is not actionable The following are not yet implemented: diff --git a/taskchampion/src/task.rs b/taskchampion/src/task.rs index 07bb19909..2cebf11ae 100644 --- a/taskchampion/src/task.rs +++ b/taskchampion/src/task.rs @@ -211,6 +211,20 @@ impl Task { .unwrap_or("") } + /// Get the wait time. If this value is set, it will be returned, even + /// if it is in the past. + pub fn get_wait(&self) -> Option> { + self.get_timestamp("wait") + } + + /// Determine whether this task is waiting now. + pub fn is_waiting(&self) -> bool { + if let Some(ts) = self.get_wait() { + return ts > Utc::now(); + } + false + } + /// Determine whether this task is active -- that is, that it has been started /// and not stopped. pub fn is_active(&self) -> bool { @@ -275,6 +289,10 @@ impl<'r> TaskMut<'r> { self.set_string("description", Some(description)) } + pub fn set_wait(&mut self, wait: Option>) -> anyhow::Result<()> { + self.set_timestamp("wait", wait) + } + pub fn set_modified(&mut self, modified: DateTime) -> anyhow::Result<()> { self.set_timestamp("modified", Some(modified)) } @@ -452,6 +470,43 @@ mod test { assert!(!task.is_active()); } + #[test] + fn test_wait_not_set() { + let task = Task::new(Uuid::new_v4(), TaskMap::new()); + + assert!(!task.is_waiting()); + assert_eq!(task.get_wait(), None); + } + + #[test] + fn test_wait_in_past() { + let ts = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); + let task = Task::new( + Uuid::new_v4(), + vec![(String::from("wait"), format!("{}", ts.timestamp()))] + .drain(..) + .collect(), + ); + dbg!(&task); + + assert!(!task.is_waiting()); + assert_eq!(task.get_wait(), Some(ts)); + } + + #[test] + fn test_wait_in_future() { + let ts = Utc.ymd(3000, 1, 1).and_hms(0, 0, 0); + let task = Task::new( + Uuid::new_v4(), + vec![(String::from("wait"), format!("{}", ts.timestamp()))] + .drain(..) + .collect(), + ); + + assert!(task.is_waiting()); + assert_eq!(task.get_wait(), Some(ts)); + } + #[test] fn test_has_tag() { let task = Task::new( From cf078e123390dc02481b83993ea1b83eb2f9e4d9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 May 2021 18:23:37 -0400 Subject: [PATCH 279/548] add 'wait' to the info output --- cli/src/invocation/cmd/info.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index 5d26213d1..f450b7a6f 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -36,6 +36,9 @@ pub(crate) fn execute( tags.sort(); t.add_row(row![b->"Tags", tags.join(" ")]); } + if task.is_waiting() { + t.add_row(row![b->"Wait", task.get_wait().unwrap()]); + } } t.print(w)?; } From 1aae7e059d4875a08ec28416f9daff6fd7267e97 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 May 2021 18:36:45 -0400 Subject: [PATCH 280/548] Add wait to reports, for display and sorting --- cli/src/invocation/report.rs | 53 ++++++++++++++++++++++++++++++++++++ cli/src/settings/report.rs | 13 +++++++++ cli/src/settings/settings.rs | 4 +++ 3 files changed, 70 insertions(+) diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 512866381..cf4008562 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -27,6 +27,7 @@ fn sort_tasks(tasks: &mut Vec, report: &Report, working_set: &WorkingSet) } SortBy::Uuid => a.get_uuid().cmp(&b.get_uuid()), SortBy::Description => a.get_description().cmp(b.get_description()), + SortBy::Wait => a.get_wait().cmp(&b.get_wait()), }; // If this sort property is equal, go on to the next.. if ord == Ordering::Equal { @@ -71,6 +72,13 @@ fn task_column(task: &Task, column: &Column, working_set: &WorkingSet) -> String tags.sort(); tags.join(" ") } + Property::Wait => { + if task.is_waiting() { + task.get_wait().unwrap().format("%Y-%m-%d").to_string() + } else { + "".to_owned() + } + } } } @@ -124,6 +132,7 @@ mod test { use super::*; use crate::invocation::test::*; use crate::settings::Sort; + use chrono::prelude::*; use std::convert::TryInto; use taskchampion::{Status, Uuid}; @@ -217,6 +226,50 @@ mod test { assert_eq!(got_uuids, exp_uuids); } + #[test] + fn sorting_by_wait() { + let mut replica = test_replica(); + let uuids = create_tasks(&mut replica); + + replica + .get_task(uuids[0]) + .unwrap() + .unwrap() + .into_mut(&mut replica) + .set_wait(Some(Utc::now() + chrono::Duration::days(2))) + .unwrap(); + + replica + .get_task(uuids[1]) + .unwrap() + .unwrap() + .into_mut(&mut replica) + .set_wait(Some(Utc::now() + chrono::Duration::days(3))) + .unwrap(); + + let working_set = replica.working_set().unwrap(); + + let report = Report { + sort: vec![Sort { + ascending: true, + sort_by: SortBy::Wait, + }], + ..Default::default() + }; + + let mut tasks: Vec<_> = replica.all_tasks().unwrap().values().cloned().collect(); + sort_tasks(&mut tasks, &report, &working_set); + let got_uuids: Vec<_> = tasks.iter().map(|t| t.get_uuid()).collect(); + + let exp_uuids = vec![ + uuids[2], // no wait + uuids[0], // wait:2d + uuids[1], // wait:3d + ]; + + assert_eq!(got_uuids, exp_uuids); + } + #[test] fn sorting_by_multiple() { let mut replica = test_replica(); diff --git a/cli/src/settings/report.rs b/cli/src/settings/report.rs index f73dfe58d..1911bf84e 100644 --- a/cli/src/settings/report.rs +++ b/cli/src/settings/report.rs @@ -46,6 +46,9 @@ pub(crate) enum Property { /// The task's tags Tags, + + /// The task's wait date + Wait, } /// A sorting criterion for a sort operation. @@ -71,6 +74,9 @@ pub(crate) enum SortBy { /// The task's description Description, + + /// The task's wait date + Wait, } // Conversions from settings::Settings. @@ -174,6 +180,7 @@ impl TryFrom<&toml::Value> for Property { "active" => Property::Active, "description" => Property::Description, "tags" => Property::Tags, + "wait" => Property::Wait, _ => bail!(": unknown property {}", s), }) } @@ -210,6 +217,7 @@ impl TryFrom<&toml::Value> for SortBy { "id" => SortBy::Id, "uuid" => SortBy::Uuid, "description" => SortBy::Description, + "wait" => SortBy::Wait, _ => bail!(": unknown sort_by value `{}`", s), }) } @@ -231,6 +239,11 @@ pub(crate) fn get_usage(u: &mut Usage) { as_sort_by: None, as_column: Some("`*` if the task is active (started)"), }); + u.report_properties.push(usage::ReportProperty { + name: "wait", + as_sort_by: Some("Sort by the task's wait date, with non-waiting tasks first"), + as_column: Some("Wait date of the task"), + }); u.report_properties.push(usage::ReportProperty { name: "description", as_sort_by: Some("Sort by the task's description"), diff --git a/cli/src/settings/settings.rs b/cli/src/settings/settings.rs index b17750fcb..955520299 100644 --- a/cli/src/settings/settings.rs +++ b/cli/src/settings/settings.rs @@ -218,6 +218,10 @@ impl Default for Settings { label: "tags".to_owned(), property: Property::Tags, }, + Column { + label: "wait".to_owned(), + property: Property::Wait, + }, ], filter: Default::default(), }, From 288f29d9d55865436ddbf7ed8adc12b755dd9790 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 29 May 2021 13:50:56 -0400 Subject: [PATCH 281/548] refactor argparse::args into submodules --- cli/src/argparse/args.rs | 346 -------------------------- cli/src/argparse/args/arg_matching.rs | 51 ++++ cli/src/argparse/args/colon.rs | 92 +++++++ cli/src/argparse/args/idlist.rs | 139 +++++++++++ cli/src/argparse/args/misc.rs | 41 +++ cli/src/argparse/args/mod.rs | 13 + cli/src/argparse/args/tags.rs | 56 +++++ 7 files changed, 392 insertions(+), 346 deletions(-) delete mode 100644 cli/src/argparse/args.rs create mode 100644 cli/src/argparse/args/arg_matching.rs create mode 100644 cli/src/argparse/args/colon.rs create mode 100644 cli/src/argparse/args/idlist.rs create mode 100644 cli/src/argparse/args/misc.rs create mode 100644 cli/src/argparse/args/mod.rs create mode 100644 cli/src/argparse/args/tags.rs diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs deleted file mode 100644 index fceea572a..000000000 --- a/cli/src/argparse/args.rs +++ /dev/null @@ -1,346 +0,0 @@ -//! Parsers for argument lists -- arrays of strings -use super::ArgList; -use super::NOW; -use chrono::prelude::*; -use nom::bytes::complete::tag as nomtag; -use nom::{ - branch::*, - character::complete::*, - combinator::*, - error::{Error, ErrorKind}, - multi::*, - sequence::*, - Err, IResult, -}; -use std::convert::TryFrom; -use taskchampion::{Status, Tag, Uuid}; - -/// A task identifier, as given in a filter command-line expression -#[derive(Debug, PartialEq, Clone)] -pub(crate) enum TaskId { - /// A small integer identifying a working-set task - WorkingSetId(usize), - - /// A full Uuid specifically identifying a task - Uuid(Uuid), - - /// A prefix of a Uuid - PartialUuid(String), -} - -/// Recognizes any argument -pub(super) fn any(input: &str) -> IResult<&str, &str> { - rest(input) -} - -/// Recognizes a report name -pub(super) fn report_name(input: &str) -> IResult<&str, &str> { - all_consuming(recognize(pair(alpha1, alphanumeric0)))(input) -} - -/// Recognizes a literal string -pub(super) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, &str> { - move |input: &str| all_consuming(nomtag(literal))(input) -} - -/// Recognizes a colon-prefixed pair -pub(super) fn colon_prefixed(prefix: &'static str) -> impl Fn(&str) -> IResult<&str, &str> { - fn to_suffix<'a>(input: (&'a str, char, &'a str)) -> Result<&'a str, ()> { - Ok(input.2) - } - move |input: &str| { - map_res( - all_consuming(tuple((nomtag(prefix), char(':'), any))), - to_suffix, - )(input) - } -} - -/// Recognizes `status:{pending,completed,deleted}` -pub(super) fn status_colon(input: &str) -> IResult<&str, Status> { - fn to_status(input: &str) -> Result { - match input { - "pending" => Ok(Status::Pending), - "completed" => Ok(Status::Completed), - "deleted" => Ok(Status::Deleted), - _ => Err(()), - } - } - map_res(colon_prefixed("status"), to_status)(input) -} - -/// Recognizes timestamps -pub(super) fn timestamp(input: &str) -> IResult<&str, DateTime> { - // TODO: full relative date language supported by TW - fn nn_d_to_timestamp(input: &str) -> Result, ()> { - // TODO: don't unwrap - Ok(*NOW + chrono::Duration::days(input.parse().unwrap())) - } - map_res(terminated(digit1, char('d')), nn_d_to_timestamp)(input) -} - -/// Recognizes `wait:` to None and `wait:` to `Some(ts)` -pub(super) fn wait_colon(input: &str) -> IResult<&str, Option>> { - fn to_wait(input: DateTime) -> Result>, ()> { - Ok(Some(input)) - } - fn to_none(_: &str) -> Result>, ()> { - Ok(None) - } - preceded( - nomtag("wait:"), - alt((map_res(timestamp, to_wait), map_res(nomtag(""), to_none))), - )(input) -} - -/// Recognizes a comma-separated list of TaskIds -pub(super) fn id_list(input: &str) -> IResult<&str, Vec> { - fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> { - move |input: &str| recognize(many_m_n(n, n, one_of(&b"0123456789abcdefABCDEF"[..])))(input) - } - fn uuid(input: &str) -> Result { - Ok(TaskId::Uuid(Uuid::parse_str(input).map_err(|_| ())?)) - } - fn partial_uuid(input: &str) -> Result { - Ok(TaskId::PartialUuid(input.to_owned())) - } - fn working_set_id(input: &str) -> Result { - Ok(TaskId::WorkingSetId(input.parse().map_err(|_| ())?)) - } - all_consuming(separated_list1( - char(','), - alt(( - map_res( - recognize(tuple(( - hex_n(8), - char('-'), - hex_n(4), - char('-'), - hex_n(4), - char('-'), - hex_n(4), - char('-'), - hex_n(12), - ))), - uuid, - ), - map_res( - recognize(tuple(( - hex_n(8), - char('-'), - hex_n(4), - char('-'), - hex_n(4), - char('-'), - hex_n(4), - ))), - partial_uuid, - ), - map_res( - recognize(tuple((hex_n(8), char('-'), hex_n(4), char('-'), hex_n(4)))), - partial_uuid, - ), - map_res( - recognize(tuple((hex_n(8), char('-'), hex_n(4)))), - partial_uuid, - ), - map_res(hex_n(8), partial_uuid), - // note that an 8-decimal-digit value will be treated as a UUID - map_res(digit1, working_set_id), - )), - ))(input) -} - -/// Recognizes a tag prefixed with `+` and returns the tag value -pub(super) fn plus_tag(input: &str) -> IResult<&str, &str> { - fn to_tag(input: (char, &str)) -> Result<&str, ()> { - Ok(input.1) - } - map_res( - all_consuming(tuple(( - char('+'), - recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), - ))), - to_tag, - )(input) -} - -/// Recognizes a tag prefixed with `-` and returns the tag value -pub(super) fn minus_tag(input: &str) -> IResult<&str, &str> { - fn to_tag(input: (char, &str)) -> Result<&str, ()> { - Ok(input.1) - } - map_res( - all_consuming(tuple(( - char('-'), - recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), - ))), - to_tag, - )(input) -} - -/// Consume a single argument from an argument list that matches the given string parser (one -/// of the other functions in this module). The given parser must consume the entire input. -pub(super) fn arg_matching<'a, O, F>(f: F) -> impl Fn(ArgList<'a>) -> IResult -where - F: Fn(&'a str) -> IResult<&'a str, O>, -{ - move |input: ArgList<'a>| { - if let Some(arg) = input.get(0) { - return match f(arg) { - Ok(("", rv)) => Ok((&input[1..], rv)), - // single-arg parsers must consume the entire arg - Ok((unconsumed, _)) => panic!("unconsumed argument input {}", unconsumed), - // single-arg parsers are all complete parsers - Err(Err::Incomplete(_)) => unreachable!(), - // for error and failure, rewrite to an error at this position in the arugment list - Err(Err::Error(Error { input: _, code })) => Err(Err::Error(Error { input, code })), - Err(Err::Failure(Error { input: _, code })) => { - Err(Err::Failure(Error { input, code })) - } - }; - } - - Err(Err::Error(Error { - input, - // since we're using nom's built-in Error, our choices here are limited, but tihs - // occurs when there's no argument where one is expected, so Eof seems appropriate - code: ErrorKind::Eof, - })) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_arg_matching() { - assert_eq!( - arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), - (argv!["bar"], "foo") - ); - assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); - } - - #[test] - fn test_colon_prefixed() { - assert_eq!(colon_prefixed("foo")("foo:abc").unwrap().1, "abc"); - assert_eq!(colon_prefixed("foo")("foo:").unwrap().1, ""); - assert!(colon_prefixed("foo")("foo").is_err()); - } - - #[test] - fn test_status_colon() { - assert_eq!(status_colon("status:pending").unwrap().1, Status::Pending); - assert_eq!( - status_colon("status:completed").unwrap().1, - Status::Completed - ); - assert_eq!(status_colon("status:deleted").unwrap().1, Status::Deleted); - assert!(status_colon("status:foo").is_err()); - assert!(status_colon("status:complete").is_err()); - assert!(status_colon("status").is_err()); - } - - #[test] - fn test_plus_tag() { - assert_eq!(plus_tag("+abc").unwrap().1, "abc"); - assert_eq!(plus_tag("+abc123").unwrap().1, "abc123"); - assert!(plus_tag("-abc123").is_err()); - assert!(plus_tag("+abc123 ").is_err()); - assert!(plus_tag(" +abc123").is_err()); - assert!(plus_tag("+1abc").is_err()); - } - - #[test] - fn test_minus_tag() { - assert_eq!(minus_tag("-abc").unwrap().1, "abc"); - assert_eq!(minus_tag("-abc123").unwrap().1, "abc123"); - assert!(minus_tag("+abc123").is_err()); - assert!(minus_tag("-abc123 ").is_err()); - assert!(minus_tag(" -abc123").is_err()); - assert!(minus_tag("-1abc").is_err()); - } - - #[test] - fn test_wait() { - assert_eq!(wait_colon("wait:").unwrap(), ("", None)); - - let one_day = *NOW + chrono::Duration::days(1); - assert_eq!(wait_colon("wait:1d").unwrap(), ("", Some(one_day))); - - let one_day = *NOW + chrono::Duration::days(1); - assert_eq!(wait_colon("wait:1d2").unwrap(), ("2", Some(one_day))); - } - - #[test] - fn test_literal() { - assert_eq!(literal("list")("list").unwrap().1, "list"); - assert!(literal("list")("listicle").is_err()); - assert!(literal("list")(" list ").is_err()); - assert!(literal("list")("LiSt").is_err()); - assert!(literal("list")("denylist").is_err()); - } - - #[test] - fn test_id_list_single() { - assert_eq!(id_list("123").unwrap().1, vec![TaskId::WorkingSetId(123)]); - } - - #[test] - fn test_id_list_uuids() { - assert_eq!( - id_list("12341234").unwrap().1, - vec![TaskId::PartialUuid(s!("12341234"))] - ); - assert_eq!( - id_list("1234abcd").unwrap().1, - vec![TaskId::PartialUuid(s!("1234abcd"))] - ); - assert_eq!( - id_list("abcd1234").unwrap().1, - vec![TaskId::PartialUuid(s!("abcd1234"))] - ); - assert_eq!( - id_list("abcd1234-1234").unwrap().1, - vec![TaskId::PartialUuid(s!("abcd1234-1234"))] - ); - assert_eq!( - id_list("abcd1234-1234-2345").unwrap().1, - vec![TaskId::PartialUuid(s!("abcd1234-1234-2345"))] - ); - assert_eq!( - id_list("abcd1234-1234-2345-3456").unwrap().1, - vec![TaskId::PartialUuid(s!("abcd1234-1234-2345-3456"))] - ); - assert_eq!( - id_list("abcd1234-1234-2345-3456-0123456789ab").unwrap().1, - vec![TaskId::Uuid( - Uuid::parse_str("abcd1234-1234-2345-3456-0123456789ab").unwrap() - )] - ); - } - - #[test] - fn test_id_list_invalid_partial_uuids() { - assert!(id_list("abcd123").is_err()); - assert!(id_list("abcd12345").is_err()); - assert!(id_list("abcd1234-").is_err()); - assert!(id_list("abcd1234-123").is_err()); - assert!(id_list("abcd1234-1234-").is_err()); - assert!(id_list("abcd1234-12345-").is_err()); - assert!(id_list("abcd1234-1234-2345-3456-0123456789ab-").is_err()); - } - - #[test] - fn test_id_list_uuids_mixed() { - assert_eq!(id_list("abcd1234,abcd1234-1234,abcd1234-1234-2345,abcd1234-1234-2345-3456,abcd1234-1234-2345-3456-0123456789ab").unwrap().1, - vec![TaskId::PartialUuid(s!("abcd1234")), - TaskId::PartialUuid(s!("abcd1234-1234")), - TaskId::PartialUuid(s!("abcd1234-1234-2345")), - TaskId::PartialUuid(s!("abcd1234-1234-2345-3456")), - TaskId::Uuid(Uuid::parse_str("abcd1234-1234-2345-3456-0123456789ab").unwrap()), - ]); - } -} diff --git a/cli/src/argparse/args/arg_matching.rs b/cli/src/argparse/args/arg_matching.rs new file mode 100644 index 000000000..e1161738e --- /dev/null +++ b/cli/src/argparse/args/arg_matching.rs @@ -0,0 +1,51 @@ +use crate::argparse::ArgList; +use nom::{ + error::{Error, ErrorKind}, + Err, IResult, +}; + +/// Consume a single argument from an argument list that matches the given string parser (one +/// of the other functions in this module). The given parser must consume the entire input. +pub(crate) fn arg_matching<'a, O, F>(f: F) -> impl Fn(ArgList<'a>) -> IResult +where + F: Fn(&'a str) -> IResult<&'a str, O>, +{ + move |input: ArgList<'a>| { + if let Some(arg) = input.get(0) { + return match f(arg) { + Ok(("", rv)) => Ok((&input[1..], rv)), + // single-arg parsers must consume the entire arg + Ok((unconsumed, _)) => panic!("unconsumed argument input {}", unconsumed), + // single-arg parsers are all complete parsers + Err(Err::Incomplete(_)) => unreachable!(), + // for error and failure, rewrite to an error at this position in the arugment list + Err(Err::Error(Error { input: _, code })) => Err(Err::Error(Error { input, code })), + Err(Err::Failure(Error { input: _, code })) => { + Err(Err::Failure(Error { input, code })) + } + }; + } + + Err(Err::Error(Error { + input, + // since we're using nom's built-in Error, our choices here are limited, but tihs + // occurs when there's no argument where one is expected, so Eof seems appropriate + code: ErrorKind::Eof, + })) + } +} + +#[cfg(test)] +mod test { + use super::super::*; + use super::*; + + #[test] + fn test_arg_matching() { + assert_eq!( + arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), + (argv!["bar"], "foo") + ); + assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); + } +} diff --git a/cli/src/argparse/args/colon.rs b/cli/src/argparse/args/colon.rs new file mode 100644 index 000000000..8dde7c74c --- /dev/null +++ b/cli/src/argparse/args/colon.rs @@ -0,0 +1,92 @@ +use super::any; +use crate::argparse::NOW; +use chrono::prelude::*; +use nom::bytes::complete::tag as nomtag; +use nom::{branch::*, character::complete::*, combinator::*, sequence::*, IResult}; +use taskchampion::Status; + +/// Recognizes a colon-prefixed pair +fn colon_prefixed(prefix: &'static str) -> impl Fn(&str) -> IResult<&str, &str> { + fn to_suffix<'a>(input: (&'a str, char, &'a str)) -> Result<&'a str, ()> { + Ok(input.2) + } + move |input: &str| { + map_res( + all_consuming(tuple((nomtag(prefix), char(':'), any))), + to_suffix, + )(input) + } +} + +/// Recognizes `status:{pending,completed,deleted}` +pub(crate) fn status_colon(input: &str) -> IResult<&str, Status> { + fn to_status(input: &str) -> Result { + match input { + "pending" => Ok(Status::Pending), + "completed" => Ok(Status::Completed), + "deleted" => Ok(Status::Deleted), + _ => Err(()), + } + } + map_res(colon_prefixed("status"), to_status)(input) +} + +/// Recognizes timestamps +pub(crate) fn timestamp(input: &str) -> IResult<&str, DateTime> { + // TODO: full relative date language supported by TW + fn nn_d_to_timestamp(input: &str) -> Result, ()> { + // TODO: don't unwrap + Ok(*NOW + chrono::Duration::days(input.parse().unwrap())) + } + map_res(terminated(digit1, char('d')), nn_d_to_timestamp)(input) +} + +/// Recognizes `wait:` to None and `wait:` to `Some(ts)` +pub(crate) fn wait_colon(input: &str) -> IResult<&str, Option>> { + fn to_wait(input: DateTime) -> Result>, ()> { + Ok(Some(input)) + } + fn to_none(_: &str) -> Result>, ()> { + Ok(None) + } + preceded( + nomtag("wait:"), + alt((map_res(timestamp, to_wait), map_res(nomtag(""), to_none))), + )(input) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_colon_prefixed() { + assert_eq!(colon_prefixed("foo")("foo:abc").unwrap().1, "abc"); + assert_eq!(colon_prefixed("foo")("foo:").unwrap().1, ""); + assert!(colon_prefixed("foo")("foo").is_err()); + } + + #[test] + fn test_status_colon() { + assert_eq!(status_colon("status:pending").unwrap().1, Status::Pending); + assert_eq!( + status_colon("status:completed").unwrap().1, + Status::Completed + ); + assert_eq!(status_colon("status:deleted").unwrap().1, Status::Deleted); + assert!(status_colon("status:foo").is_err()); + assert!(status_colon("status:complete").is_err()); + assert!(status_colon("status").is_err()); + } + + #[test] + fn test_wait() { + assert_eq!(wait_colon("wait:").unwrap(), ("", None)); + + let one_day = *NOW + chrono::Duration::days(1); + assert_eq!(wait_colon("wait:1d").unwrap(), ("", Some(one_day))); + + let one_day = *NOW + chrono::Duration::days(1); + assert_eq!(wait_colon("wait:1d2").unwrap(), ("2", Some(one_day))); + } +} diff --git a/cli/src/argparse/args/idlist.rs b/cli/src/argparse/args/idlist.rs new file mode 100644 index 000000000..f8c09ae04 --- /dev/null +++ b/cli/src/argparse/args/idlist.rs @@ -0,0 +1,139 @@ +use nom::{branch::*, character::complete::*, combinator::*, multi::*, sequence::*, IResult}; +use taskchampion::Uuid; + +/// A task identifier, as given in a filter command-line expression +#[derive(Debug, PartialEq, Clone)] +pub(crate) enum TaskId { + /// A small integer identifying a working-set task + WorkingSetId(usize), + + /// A full Uuid specifically identifying a task + Uuid(Uuid), + + /// A prefix of a Uuid + PartialUuid(String), +} + +/// Recognizes a comma-separated list of TaskIds +pub(crate) fn id_list(input: &str) -> IResult<&str, Vec> { + fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> { + move |input: &str| recognize(many_m_n(n, n, one_of(&b"0123456789abcdefABCDEF"[..])))(input) + } + fn uuid(input: &str) -> Result { + Ok(TaskId::Uuid(Uuid::parse_str(input).map_err(|_| ())?)) + } + fn partial_uuid(input: &str) -> Result { + Ok(TaskId::PartialUuid(input.to_owned())) + } + fn working_set_id(input: &str) -> Result { + Ok(TaskId::WorkingSetId(input.parse().map_err(|_| ())?)) + } + all_consuming(separated_list1( + char(','), + alt(( + map_res( + recognize(tuple(( + hex_n(8), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + char('-'), + hex_n(12), + ))), + uuid, + ), + map_res( + recognize(tuple(( + hex_n(8), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + char('-'), + hex_n(4), + ))), + partial_uuid, + ), + map_res( + recognize(tuple((hex_n(8), char('-'), hex_n(4), char('-'), hex_n(4)))), + partial_uuid, + ), + map_res( + recognize(tuple((hex_n(8), char('-'), hex_n(4)))), + partial_uuid, + ), + map_res(hex_n(8), partial_uuid), + // note that an 8-decimal-digit value will be treated as a UUID + map_res(digit1, working_set_id), + )), + ))(input) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_id_list_single() { + assert_eq!(id_list("123").unwrap().1, vec![TaskId::WorkingSetId(123)]); + } + + #[test] + fn test_id_list_uuids() { + assert_eq!( + id_list("12341234").unwrap().1, + vec![TaskId::PartialUuid(s!("12341234"))] + ); + assert_eq!( + id_list("1234abcd").unwrap().1, + vec![TaskId::PartialUuid(s!("1234abcd"))] + ); + assert_eq!( + id_list("abcd1234").unwrap().1, + vec![TaskId::PartialUuid(s!("abcd1234"))] + ); + assert_eq!( + id_list("abcd1234-1234").unwrap().1, + vec![TaskId::PartialUuid(s!("abcd1234-1234"))] + ); + assert_eq!( + id_list("abcd1234-1234-2345").unwrap().1, + vec![TaskId::PartialUuid(s!("abcd1234-1234-2345"))] + ); + assert_eq!( + id_list("abcd1234-1234-2345-3456").unwrap().1, + vec![TaskId::PartialUuid(s!("abcd1234-1234-2345-3456"))] + ); + assert_eq!( + id_list("abcd1234-1234-2345-3456-0123456789ab").unwrap().1, + vec![TaskId::Uuid( + Uuid::parse_str("abcd1234-1234-2345-3456-0123456789ab").unwrap() + )] + ); + } + + #[test] + fn test_id_list_invalid_partial_uuids() { + assert!(id_list("abcd123").is_err()); + assert!(id_list("abcd12345").is_err()); + assert!(id_list("abcd1234-").is_err()); + assert!(id_list("abcd1234-123").is_err()); + assert!(id_list("abcd1234-1234-").is_err()); + assert!(id_list("abcd1234-12345-").is_err()); + assert!(id_list("abcd1234-1234-2345-3456-0123456789ab-").is_err()); + } + + #[test] + fn test_id_list_uuids_mixed() { + assert_eq!(id_list("abcd1234,abcd1234-1234,abcd1234-1234-2345,abcd1234-1234-2345-3456,abcd1234-1234-2345-3456-0123456789ab").unwrap().1, + vec![TaskId::PartialUuid(s!("abcd1234")), + TaskId::PartialUuid(s!("abcd1234-1234")), + TaskId::PartialUuid(s!("abcd1234-1234-2345")), + TaskId::PartialUuid(s!("abcd1234-1234-2345-3456")), + TaskId::Uuid(Uuid::parse_str("abcd1234-1234-2345-3456-0123456789ab").unwrap()), + ]); + } +} diff --git a/cli/src/argparse/args/misc.rs b/cli/src/argparse/args/misc.rs new file mode 100644 index 000000000..006a0b939 --- /dev/null +++ b/cli/src/argparse/args/misc.rs @@ -0,0 +1,41 @@ +use nom::bytes::complete::tag as nomtag; +use nom::{character::complete::*, combinator::*, sequence::*, IResult}; + +/// Recognizes any argument +pub(crate) fn any(input: &str) -> IResult<&str, &str> { + rest(input) +} + +/// Recognizes a report name +pub(crate) fn report_name(input: &str) -> IResult<&str, &str> { + all_consuming(recognize(pair(alpha1, alphanumeric0)))(input) +} + +/// Recognizes a literal string +pub(crate) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, &str> { + move |input: &str| all_consuming(nomtag(literal))(input) +} + +#[cfg(test)] +mod test { + use super::super::*; + use super::*; + + #[test] + fn test_arg_matching() { + assert_eq!( + arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), + (argv!["bar"], "foo") + ); + assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); + } + + #[test] + fn test_literal() { + assert_eq!(literal("list")("list").unwrap().1, "list"); + assert!(literal("list")("listicle").is_err()); + assert!(literal("list")(" list ").is_err()); + assert!(literal("list")("LiSt").is_err()); + assert!(literal("list")("denylist").is_err()); + } +} diff --git a/cli/src/argparse/args/mod.rs b/cli/src/argparse/args/mod.rs new file mode 100644 index 000000000..8beaf08c1 --- /dev/null +++ b/cli/src/argparse/args/mod.rs @@ -0,0 +1,13 @@ +//! Parsers for single arguments (strings) + +mod arg_matching; +mod colon; +mod idlist; +mod misc; +mod tags; + +pub(crate) use arg_matching::arg_matching; +pub(crate) use colon::{status_colon, wait_colon}; +pub(crate) use idlist::{id_list, TaskId}; +pub(crate) use misc::{any, literal, report_name}; +pub(crate) use tags::{minus_tag, plus_tag}; diff --git a/cli/src/argparse/args/tags.rs b/cli/src/argparse/args/tags.rs new file mode 100644 index 000000000..8c2cbd9c1 --- /dev/null +++ b/cli/src/argparse/args/tags.rs @@ -0,0 +1,56 @@ +use nom::{character::complete::*, combinator::*, sequence::*, IResult}; +use std::convert::TryFrom; +use taskchampion::Tag; + +/// Recognizes a tag prefixed with `+` and returns the tag value +pub(crate) fn plus_tag(input: &str) -> IResult<&str, &str> { + fn to_tag(input: (char, &str)) -> Result<&str, ()> { + Ok(input.1) + } + map_res( + all_consuming(tuple(( + char('+'), + recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), + ))), + to_tag, + )(input) +} + +/// Recognizes a tag prefixed with `-` and returns the tag value +pub(crate) fn minus_tag(input: &str) -> IResult<&str, &str> { + fn to_tag(input: (char, &str)) -> Result<&str, ()> { + Ok(input.1) + } + map_res( + all_consuming(tuple(( + char('-'), + recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), + ))), + to_tag, + )(input) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_plus_tag() { + assert_eq!(plus_tag("+abc").unwrap().1, "abc"); + assert_eq!(plus_tag("+abc123").unwrap().1, "abc123"); + assert!(plus_tag("-abc123").is_err()); + assert!(plus_tag("+abc123 ").is_err()); + assert!(plus_tag(" +abc123").is_err()); + assert!(plus_tag("+1abc").is_err()); + } + + #[test] + fn test_minus_tag() { + assert_eq!(minus_tag("-abc").unwrap().1, "abc"); + assert_eq!(minus_tag("-abc123").unwrap().1, "abc123"); + assert!(minus_tag("+abc123").is_err()); + assert!(minus_tag("-abc123 ").is_err()); + assert!(minus_tag(" -abc123").is_err()); + assert!(minus_tag("-1abc").is_err()); + } +} From 0259a5e2e223a9e20090708d45821576cb19137e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 29 May 2021 18:39:13 -0400 Subject: [PATCH 282/548] parse durations and timestamps --- Cargo.lock | 74 ++++- cli/Cargo.toml | 4 +- cli/src/argparse/args/colon.rs | 17 +- cli/src/argparse/args/mod.rs | 3 + cli/src/argparse/args/time.rs | 529 +++++++++++++++++++++++++++++++ cli/src/argparse/mod.rs | 6 +- cli/src/argparse/modification.rs | 2 +- docs/src/SUMMARY.md | 1 + docs/src/time.md | 36 +++ 9 files changed, 649 insertions(+), 23 deletions(-) create mode 100644 cli/src/argparse/args/time.rs create mode 100644 docs/src/time.md diff --git a/Cargo.lock b/Cargo.lock index 6499dfbb7..d5b7e61c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1415,6 +1415,15 @@ dependencies = [ "winreg", ] +[[package]] +name = "iso8601-duration" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b51dd97fa24074214b9eb14da518957573f4dec3189112610ae1ccec9ac464" +dependencies = [ + "nom 5.1.2", +] + [[package]] name = "itoa" version = "0.4.7" @@ -1776,6 +1785,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "nom" version = "6.1.2" @@ -2413,6 +2433,19 @@ dependencies = [ "serde", ] +[[package]] +name = "rstest" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "041bb0202c14f6a158bbbf086afb03d0c6e975c2dec7d4912f8061ed44f290af" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2", + "quote", + "rustc_version 0.3.3", + "syn", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -2431,7 +2464,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", ] [[package]] @@ -2502,7 +2544,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", ] [[package]] @@ -2511,6 +2562,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.125" @@ -2675,7 +2735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" dependencies = [ "discard", - "rustc_version", + "rustc_version 0.2.3", "stdweb-derive", "stdweb-internal-macros", "stdweb-internal-runtime", @@ -2768,9 +2828,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.69" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2", "quote", @@ -2812,12 +2872,14 @@ dependencies = [ "chrono", "dirs-next", "env_logger 0.8.3", + "iso8601-duration", "lazy_static", "log", "mdbook", - "nom", + "nom 6.1.2", "predicates", "prettytable-rs", + "rstest", "serde_json", "taskchampion", "tempfile", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 53c26dd2b..05cfcb20c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -22,8 +22,9 @@ termcolor = "^1.1.2" atty = "^0.2.14" toml = "^0.5.8" toml_edit = "^0.2.0" -chrono = "*" +chrono = "0.4" lazy_static = "1" +iso8601-duration = "0.1" # only needed for usage-docs mdbook = { version = "0.4", optional = true } @@ -36,6 +37,7 @@ path = "../taskchampion" assert_cmd = "^1.0.3" predicates = "^1.0.7" tempfile = "3" +rstest = "0.10" [features] usage-docs = [ "mdbook", "serde_json" ] diff --git a/cli/src/argparse/args/colon.rs b/cli/src/argparse/args/colon.rs index 8dde7c74c..ecf1af6c9 100644 --- a/cli/src/argparse/args/colon.rs +++ b/cli/src/argparse/args/colon.rs @@ -1,4 +1,4 @@ -use super::any; +use super::{any, timestamp}; use crate::argparse::NOW; use chrono::prelude::*; use nom::bytes::complete::tag as nomtag; @@ -31,16 +31,6 @@ pub(crate) fn status_colon(input: &str) -> IResult<&str, Status> { map_res(colon_prefixed("status"), to_status)(input) } -/// Recognizes timestamps -pub(crate) fn timestamp(input: &str) -> IResult<&str, DateTime> { - // TODO: full relative date language supported by TW - fn nn_d_to_timestamp(input: &str) -> Result, ()> { - // TODO: don't unwrap - Ok(*NOW + chrono::Duration::days(input.parse().unwrap())) - } - map_res(terminated(digit1, char('d')), nn_d_to_timestamp)(input) -} - /// Recognizes `wait:` to None and `wait:` to `Some(ts)` pub(crate) fn wait_colon(input: &str) -> IResult<&str, Option>> { fn to_wait(input: DateTime) -> Result>, ()> { @@ -51,7 +41,10 @@ pub(crate) fn wait_colon(input: &str) -> IResult<&str, Option>> { } preceded( nomtag("wait:"), - alt((map_res(timestamp, to_wait), map_res(nomtag(""), to_none))), + alt(( + map_res(timestamp(*NOW, Local), to_wait), + map_res(nomtag(""), to_none), + )), )(input) } diff --git a/cli/src/argparse/args/mod.rs b/cli/src/argparse/args/mod.rs index 8beaf08c1..f7124fa29 100644 --- a/cli/src/argparse/args/mod.rs +++ b/cli/src/argparse/args/mod.rs @@ -5,9 +5,12 @@ mod colon; mod idlist; mod misc; mod tags; +mod time; pub(crate) use arg_matching::arg_matching; pub(crate) use colon::{status_colon, wait_colon}; pub(crate) use idlist::{id_list, TaskId}; pub(crate) use misc::{any, literal, report_name}; pub(crate) use tags::{minus_tag, plus_tag}; +#[allow(unused_imports)] +pub(crate) use time::{duration, timestamp}; diff --git a/cli/src/argparse/args/time.rs b/cli/src/argparse/args/time.rs new file mode 100644 index 000000000..f17ebd880 --- /dev/null +++ b/cli/src/argparse/args/time.rs @@ -0,0 +1,529 @@ +use chrono::{prelude::*, Duration}; +use iso8601_duration::Duration as IsoDuration; +use lazy_static::lazy_static; +use nom::{ + branch::*, + bytes::complete::*, + character::complete::*, + character::*, + combinator::*, + error::{Error, ErrorKind}, + multi::*, + sequence::*, + Err, IResult, +}; +use std::str::FromStr; + +// https://taskwarrior.org/docs/dates.html +// https://taskwarrior.org/docs/named_dates.html +// https://taskwarrior.org/docs/durations.html + +/// A case for matching durations. If `.3` is true, then the value can be used +/// without a prefix, e.g., `minute`. If false, it cannot, e.g., `minutes` +#[derive(Debug)] +struct DurationCase(&'static str, Duration, bool); + +// https://github.com/GothenburgBitFactory/libshared/blob/9a5f24e2acb38d05afb8f8e316a966dee196a42a/src/Duration.cpp#L50 +// TODO: use const when chrono supports it +lazy_static! { + static ref DURATION_CASES: Vec = vec![ + DurationCase("annual", Duration::days(365), true), + DurationCase("biannual", Duration::days(730), true), + DurationCase("bimonthly", Duration::days(61), true), + DurationCase("biweekly", Duration::days(14), true), + DurationCase("biyearly", Duration::days(730), true), + DurationCase("daily", Duration::days(1), true), + DurationCase("days", Duration::days(1), false), + DurationCase("day", Duration::days(1), true), + DurationCase("d", Duration::days(1), false), + DurationCase("fortnight", Duration::days(14), true), + DurationCase("hours", Duration::hours(1), false), + DurationCase("hour", Duration::hours(1), true), + DurationCase("hrs", Duration::hours(1), false), + DurationCase("hr", Duration::hours(1), true), + DurationCase("h", Duration::hours(1), false), + DurationCase("minutes", Duration::minutes(1), false), + DurationCase("minute", Duration::minutes(1), true), + DurationCase("mins", Duration::minutes(1), false), + DurationCase("min", Duration::minutes(1), true), + DurationCase("monthly", Duration::days(30), true), + DurationCase("months", Duration::days(30), false), + DurationCase("month", Duration::days(30), true), + DurationCase("mnths", Duration::days(30), false), + DurationCase("mths", Duration::days(30), false), + DurationCase("mth", Duration::days(30), true), + DurationCase("mos", Duration::days(30), false), + DurationCase("mo", Duration::days(30), true), + DurationCase("m", Duration::days(30), false), + DurationCase("quarterly", Duration::days(91), true), + DurationCase("quarters", Duration::days(91), false), + DurationCase("quarter", Duration::days(91), true), + DurationCase("qrtrs", Duration::days(91), false), + DurationCase("qrtr", Duration::days(91), true), + DurationCase("qtrs", Duration::days(91), false), + DurationCase("qtr", Duration::days(91), true), + DurationCase("q", Duration::days(91), false), + DurationCase("semiannual", Duration::days(183), true), + DurationCase("sennight", Duration::days(14), false), + DurationCase("seconds", Duration::seconds(1), false), + DurationCase("second", Duration::seconds(1), true), + DurationCase("secs", Duration::seconds(1), false), + DurationCase("sec", Duration::seconds(1), true), + DurationCase("s", Duration::seconds(1), false), + DurationCase("weekdays", Duration::days(1), true), + DurationCase("weekly", Duration::days(7), true), + DurationCase("weeks", Duration::days(7), false), + DurationCase("week", Duration::days(7), true), + DurationCase("wks", Duration::days(7), false), + DurationCase("wk", Duration::days(7), true), + DurationCase("w", Duration::days(7), false), + DurationCase("yearly", Duration::days(365), true), + DurationCase("years", Duration::days(365), false), + DurationCase("year", Duration::days(365), true), + DurationCase("yrs", Duration::days(365), false), + DurationCase("yr", Duration::days(365), true), + DurationCase("y", Duration::days(365), false), + ]; +} + +/// Parses suffixes like 'min', and 'd'; standalone is true if there is no numeric prefix, in which +/// case plurals (like `days`) are not matched. +fn duration_suffix(has_prefix: bool) -> impl Fn(&str) -> IResult<&str, Duration> { + move |input: &str| { + // Rust wants this to have a default value, but it is not actually used + // because DURATION_CASES has at least one case with case.2 == `true` + let mut res = Err(Err::Failure(Error::new(input, ErrorKind::Tag))); + for case in DURATION_CASES.iter() { + if !case.2 && !has_prefix { + // this case requires a prefix, and input does not have one + continue; + } + res = tag(case.0)(input); + match res { + Ok((i, _)) => { + return Ok((i, case.1)); + } + Err(Err::Error(_)) => { + // recoverable error + continue; + } + Err(e) => { + // irrecoverable error + return Err(e); + } + } + } + + // return the last error + Err(res.unwrap_err()) + } +} +/// Calculate the multiplier for a decimal prefix; this uses integer math +/// where possible, falling back to floating-point math on seconds +fn decimal_prefix_multiplier(input: &str) -> IResult<&str, f64> { + map_res( + // recognize NN or NN.NN + alt((recognize(tuple((digit1, char('.'), digit1))), digit1)), + |input: &str| -> Result::Err> { + let mul = input.parse::()?; + Ok(mul) + }, + )(input) +} + +/// Parse an iso8601 duration, converting it to a [`chrono::Duration`] on the assumption +/// that a year is 365 days and a month is 30 days. +fn iso8601_dur(input: &str) -> IResult<&str, Duration> { + if let Ok(iso_dur) = IsoDuration::parse(input) { + // iso8601_duration uses f32, but f32 underflows seconds for values as small as + // a year. So we upgrade to f64 immediately. f64 has a 53-bit mantissa which can + // represent almost 300 million years without underflow, so it should be adequate. + let days = iso_dur.year as f64 * 365.0 + iso_dur.month as f64 * 30.0 + iso_dur.day as f64; + let hours = days * 24.0 + iso_dur.hour as f64; + let mins = hours * 60.0 + iso_dur.minute as f64; + let secs = mins * 60.0 + iso_dur.second as f64; + let dur = Duration::seconds(secs as i64); + Ok((&input[input.len()..], dur)) + } else { + Err(Err::Error(Error::new(input, ErrorKind::Tag))) + } +} + +/// Recognizes durations +pub(crate) fn duration(input: &str) -> IResult<&str, Duration> { + alt(( + map_res( + tuple(( + decimal_prefix_multiplier, + multispace0, + duration_suffix(true), + )), + |input: (f64, &str, Duration)| -> Result { + // `as i64` is saturating, so for large offsets this will + // just pick an imprecise very-futuristic date + let secs = (input.0 * input.2.num_seconds() as f64) as i64; + Ok(Duration::seconds(secs)) + }, + ), + duration_suffix(false), + iso8601_dur, + ))(input) +} + +/// Parse a rfc3339 datestamp +fn rfc3339_timestamp(input: &str) -> IResult<&str, DateTime> { + if let Ok(dt) = DateTime::parse_from_rfc3339(input) { + // convert to UTC and truncate seconds + let dt = dt.with_timezone(&Utc).trunc_subsecs(0); + Ok((&input[input.len()..], dt)) + } else { + Err(Err::Error(Error::new(input, ErrorKind::Tag))) + } +} + +fn named_date( + now: DateTime, + local: Tz, +) -> impl Fn(&str) -> IResult<&str, DateTime> { + move |input: &str| { + let local_today = now.with_timezone(&local).date(); + let remaining = &input[input.len()..]; + match input { + "yesterday" => Ok((remaining, local_today - Duration::days(1))), + "today" => Ok((remaining, local_today)), + "tomorrow" => Ok((remaining, local_today + Duration::days(1))), + // TODO: lots more! + _ => Err(Err::Error(Error::new(input, ErrorKind::Tag))), + } + .map(|(rem, dt)| (rem, dt.and_hms(0, 0, 0).with_timezone(&Utc))) + } +} + +/// recognize a digit +fn digit(input: &str) -> IResult<&str, char> { + satisfy(|c| is_digit(c as u8))(input) +} + +/// Parse yyyy-mm-dd as the given date, at the local midnight +fn yyyy_mm_dd(local: Tz) -> impl Fn(&str) -> IResult<&str, DateTime> { + move |input: &str| { + fn parse_int(input: &str) -> Result::Err> { + input.parse::() + } + map_res( + tuple(( + map_res(recognize(count(digit, 4)), parse_int::), + char('-'), + map_res(recognize(many_m_n(1, 2, digit)), parse_int::), + char('-'), + map_res(recognize(many_m_n(1, 2, digit)), parse_int::), + )), + |input: (i32, char, u32, char, u32)| -> Result, ()> { + // try to convert, handling out-of-bounds months or days as an error + let ymd = match local.ymd_opt(input.0, input.2, input.4) { + chrono::LocalResult::Single(ymd) => Ok(ymd), + _ => Err(()), + }?; + Ok(ymd.and_hms(0, 0, 0).with_timezone(&Utc)) + }, + )(input) + } +} + +/// Recognizes timestamps +pub(crate) fn timestamp( + now: DateTime, + local: Tz, +) -> impl Fn(&str) -> IResult<&str, DateTime> { + move |input: &str| { + alt(( + // relative time + map_res( + duration, + |duration: Duration| -> Result, ()> { Ok(now + duration) }, + ), + rfc3339_timestamp, + yyyy_mm_dd(local), + value(now, tag("now")), + named_date(now, local), + ))(input) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::argparse::NOW; + use rstest::rstest; + + const M: i64 = 60; + const H: i64 = M * 60; + const DAY: i64 = H * 24; + const MONTH: i64 = DAY * 30; + const YEAR: i64 = DAY * 365; + + // TODO: use const when chrono supports it + lazy_static! { + // India standard time (not an even multiple of hours) + static ref IST: FixedOffset = FixedOffset::east(5 * 3600 + 30 * 60); + // Utc, but as a FixedOffset TimeZone impl + static ref UTC_FO: FixedOffset = FixedOffset::east(0); + // Hawaii + static ref HST: FixedOffset = FixedOffset::west(10 * 3600); + } + + /// test helper to ensure that the entire input is consumed + fn complete_duration(input: &str) -> IResult<&str, Duration> { + all_consuming(duration)(input) + } + + /// test helper to ensure that the entire input is consumed + fn complete_timestamp( + now: DateTime, + local: Tz, + ) -> impl Fn(&str) -> IResult<&str, DateTime> { + move |input: &str| all_consuming(timestamp(now, local))(input) + } + + /// Shorthand day and time + fn dt(y: i32, m: u32, d: u32, hh: u32, mm: u32, ss: u32) -> DateTime { + Utc.ymd(y, m, d).and_hms(hh, mm, ss) + } + + /// Local day and time, parameterized on the timezone + fn ldt( + y: i32, + m: u32, + d: u32, + hh: u32, + mm: u32, + ss: u32, + ) -> Box DateTime> { + Box::new(move |tz| tz.ymd(y, m, d).and_hms(hh, mm, ss).with_timezone(&Utc)) + } + + fn ld(y: i32, m: u32, d: u32) -> Box DateTime> { + ldt(y, m, d, 0, 0, 0) + } + + #[rstest] + #[case::rel_hours_0(dt(2021, 5, 29, 1, 30, 0), "0h", dt(2021, 5, 29, 1, 30, 0))] + #[case::rel_hours_05(dt(2021, 5, 29, 1, 30, 0), "0.5h", dt(2021, 5, 29, 2, 0, 0))] + #[case::rel_hours_no_prefix(dt(2021, 5, 29, 1, 30, 0), "hour", dt(2021, 5, 29, 2, 30, 0))] + #[case::rel_hours_5(dt(2021, 5, 29, 1, 30, 0), "5h", dt(2021, 5, 29, 6, 30, 0))] + #[case::rel_days_0(dt(2021, 5, 29, 1, 30, 0), "0d", dt(2021, 5, 29, 1, 30, 0))] + #[case::rel_days_10(dt(2021, 5, 29, 1, 30, 0), "10d", dt(2021, 6, 8, 1, 30, 0))] + #[case::rfc3339_datetime(*NOW, "2019-10-12T07:20:50.12Z", dt(2019, 10, 12, 7, 20, 50))] + #[case::now(*NOW, "now", *NOW)] + /// Cases where the `local` parameter is ignored + fn test_nonlocal_timestamp( + #[case] now: DateTime, + #[case] input: &'static str, + #[case] output: DateTime, + ) { + let (_, res) = complete_timestamp(now, *IST)(input).unwrap(); + assert_eq!(res, output, "parsing {:?}", input); + } + + #[rstest] + /// Cases where the `local` parameter matters + #[case::yyyy_mm_dd(ld(2000, 1, 1), "2021-01-01", ld(2021, 1, 1))] + #[case::yyyy_m_d(ld(2000, 1, 1), "2021-1-1", ld(2021, 1, 1))] + #[case::yesterday(ld(2021, 3, 1), "yesterday", ld(2021, 2, 28))] + #[case::yesterday_from_evening(ldt(2021, 3, 1, 21, 30, 30), "yesterday", ld(2021, 2, 28))] + #[case::today(ld(2021, 3, 1), "today", ld(2021, 3, 1))] + #[case::today_from_evening(ldt(2021, 3, 1, 21, 30, 30), "today", ld(2021, 3, 1))] + #[case::tomorrow(ld(2021, 3, 1), "tomorrow", ld(2021, 3, 2))] + #[case::tomorow_from_evening(ldt(2021, 3, 1, 21, 30, 30), "tomorrow", ld(2021, 3, 2))] + fn test_local_timestamp( + #[case] now: Box DateTime>, + #[values(*IST, *UTC_FO, *HST)] tz: FixedOffset, + #[case] input: &str, + #[case] output: Box DateTime>, + ) { + let now = now(tz); + let output = output(tz); + let (_, res) = complete_timestamp(now, tz)(input).unwrap(); + assert_eq!( + res, output, + "parsing {:?} relative to {:?} in timezone {:?}", + input, now, tz + ); + } + + #[rstest] + #[case::rfc3339_datetime_bad_month(*NOW, "2019-10-99T07:20:50.12Z")] + #[case::yyyy_mm_dd_bad_month(*NOW, "2019-10-99")] + fn test_timestamp_err(#[case] now: DateTime, #[case] input: &'static str) { + let res = complete_timestamp(now, Utc)(input); + assert!( + res.is_err(), + "expected error parsing {:?}, got {:?}", + input, + res.unwrap() + ); + } + + // All test cases from + // https://github.com/GothenburgBitFactory/libshared/blob/9a5f24e2acb38d05afb8f8e316a966dee196a42a/test/duration.t.cpp#L136 + #[rstest] + #[case("0seconds", 0)] + #[case("2 seconds", 2)] + #[case("10seconds", 10)] + #[case("1.5seconds", 1)] + #[case("0second", 0)] + #[case("2 second", 2)] + #[case("10second", 10)] + #[case("1.5second", 1)] + #[case("0s", 0)] + #[case("2 s", 2)] + #[case("10s", 10)] + #[case("1.5s", 1)] + #[case("0minutes", 0)] + #[case("2 minutes", 2 * M)] + #[case("10minutes", 10 * M)] + #[case("1.5minutes", M + 30)] + #[case("0minute", 0)] + #[case("2 minute", 2 * M)] + #[case("10minute", 10 * M)] + #[case("1.5minute", M + 30)] + #[case("0min", 0)] + #[case("2 min", 2 * M)] + #[case("10min", 10 * M)] + #[case("1.5min", M + 30)] + #[case("0hours", 0)] + #[case("2 hours", 2 * H)] + #[case("10hours", 10 * H)] + #[case("1.5hours", H + 30 * M)] + #[case("0hour", 0)] + #[case("2 hour", 2 * H)] + #[case("10hour", 10 * H)] + #[case("1.5hour", H + 30 * M)] + #[case("0h", 0)] + #[case("2 h", 2 * H)] + #[case("10h", 10 * H)] + #[case("1.5h", H + 30 * M)] + #[case("weekdays", DAY)] + #[case("daily", DAY)] + #[case("0days", 0)] + #[case("2 days", 2 * DAY)] + #[case("10days", 10 * DAY)] + #[case("1.5days", DAY + 12 * H)] + #[case("0day", 0)] + #[case("2 day", 2 * DAY)] + #[case("10day", 10 * DAY)] + #[case("1.5day", DAY + 12 * H)] + #[case("0d", 0)] + #[case("2 d", 2 * DAY)] + #[case("10d", 10 * DAY)] + #[case("1.5d", DAY + 12 * H)] + #[case("weekly", 7 * DAY)] + #[case("0weeks", 0)] + #[case("2 weeks", 14 * DAY)] + #[case("10weeks", 70 * DAY)] + #[case("1.5weeks", 10 * DAY + 12 * H)] + #[case("0week", 0)] + #[case("2 week", 14 * DAY)] + #[case("10week", 70 * DAY)] + #[case("1.5week", 10 * DAY + 12 * H)] + #[case("0w", 0)] + #[case("2 w", 14 * DAY)] + #[case("10w", 70 * DAY)] + #[case("1.5w", 10 * DAY + 12 * H)] + #[case("monthly", 30 * DAY)] + #[case("0months", 0)] + #[case("2 months", 60 * DAY)] + #[case("10months", 300 * DAY)] + #[case("1.5months", 45 * DAY)] + #[case("0month", 0)] + #[case("2 month", 60 * DAY)] + #[case("10month", 300 * DAY)] + #[case("1.5month", 45 * DAY)] + #[case("0mo", 0)] + #[case("2 mo", 60 * DAY)] + #[case("10mo", 300 * DAY)] + #[case("1.5mo", 45 * DAY)] + #[case("quarterly", 91 * DAY)] + #[case("0quarters", 0)] + #[case("2 quarters", 182 * DAY)] + #[case("10quarters", 910 * DAY)] + #[case("1.5quarters", 136 * DAY + 12 * H)] + #[case("0quarter", 0)] + #[case("2 quarter", 182 * DAY)] + #[case("10quarter", 910 * DAY)] + #[case("1.5quarter", 136 * DAY + 12 * H)] + #[case("0q", 0)] + #[case("2 q", 182 * DAY)] + #[case("10q", 910 * DAY)] + #[case("1.5q", 136 * DAY + 12 * H)] + #[case("yearly", YEAR)] + #[case("0years", 0)] + #[case("2 years", 2 * YEAR)] + #[case("10years", 10 * YEAR)] + #[case("1.5years", 547 * DAY + 12 * H)] + #[case("0year", 0)] + #[case("2 year", 2 * YEAR)] + #[case("10year", 10 * YEAR)] + #[case("1.5year", 547 * DAY + 12 * H)] + #[case("0y", 0)] + #[case("2 y", 2 * YEAR)] + #[case("10y", 10 * YEAR)] + #[case("1.5y", 547 * DAY + 12 * H)] + #[case("annual", YEAR)] + #[case("biannual", 2 * YEAR)] + #[case("bimonthly", 61 * DAY)] + #[case("biweekly", 14 * DAY)] + #[case("biyearly", 2 * YEAR)] + #[case("fortnight", 14 * DAY)] + #[case("semiannual", 183 * DAY)] + #[case("0sennight", 0)] + #[case("2 sennight", 28 * DAY)] + #[case("10sennight", 140 * DAY)] + #[case("1.5sennight", 21 * DAY)] + fn test_duration_units(#[case] input: &'static str, #[case] seconds: i64) { + let (_, res) = complete_duration(input).expect(input); + assert_eq!(res.num_seconds(), seconds, "parsing {}", input); + } + + #[rstest] + #[case("years")] + #[case("minutes")] + #[case("eons")] + #[case("P1S")] // missing T + #[case("p1y")] // lower-case + fn test_duration_errors(#[case] input: &'static str) { + let res = complete_duration(input); + assert!( + res.is_err(), + "did not get expected error parsing duration {:?}; got {:?}", + input, + res.unwrap() + ); + } + + // https://github.com/GothenburgBitFactory/libshared/blob/9a5f24e2acb38d05afb8f8e316a966dee196a42a/test/duration.t.cpp#L115 + #[rstest] + #[case("P1Y", YEAR)] + #[case("P1M", MONTH)] + #[case("P1D", DAY)] + #[case("P1Y1M", YEAR + MONTH)] + #[case("P1Y1D", YEAR + DAY)] + #[case("P1M1D", MONTH + DAY)] + #[case("P1Y1M1D", YEAR + MONTH + DAY)] + #[case("PT1H", H)] + #[case("PT1M", M)] + #[case("PT1S", 1)] + #[case("PT1H1M", H + M)] + #[case("PT1H1S", H + 1)] + #[case("PT1M1S", M + 1)] + #[case("PT1H1M1S", H + M + 1)] + #[case("P1Y1M1DT1H1M1S", YEAR + MONTH + DAY + H + M + 1)] + #[case("PT24H", DAY)] + #[case("PT40000000S", 40000000)] + #[case("PT3600S", H)] + #[case("PT60M", H)] + fn test_duration_8601(#[case] input: &'static str, #[case] seconds: i64) { + let (_, res) = complete_duration(input).expect(input); + assert_eq!(res.num_seconds(), seconds, "parsing {}", input); + } +} diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index f837b0ecf..7f2607631 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -34,13 +34,13 @@ use crate::usage::Usage; use chrono::prelude::*; use lazy_static::lazy_static; -type ArgList<'a> = &'a [&'a str]; - lazy_static! { // A static value of NOW to make tests easier - pub(super) static ref NOW: DateTime = Utc::now(); + pub(crate) static ref NOW: DateTime = Utc::now(); } +type ArgList<'a> = &'a [&'a str]; + pub(crate) fn get_usage(usage: &mut Usage) { Subcommand::get_usage(usage); Filter::get_usage(usage); diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 5b3cac3df..dcdc3d303 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -156,7 +156,7 @@ impl Modification { description: " Set the time before which the task is not actionable and should not be shown in reports. With `wait:`, the time - is un-set.", + is un-set. See the documentation for the timestamp syntax.", }); } } diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 822dc4f7f..568eb57cf 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -7,6 +7,7 @@ * [Tags](./tags.md) * [Filters](./filters.md) * [Modifications](./modifications.md) + * [Dates and Durations](./time.md) * [Configuration](./config-file.md) * [Environment](./environment.md) * [Synchronization](./task-sync.md) diff --git a/docs/src/time.md b/docs/src/time.md new file mode 100644 index 000000000..c6dc5e282 --- /dev/null +++ b/docs/src/time.md @@ -0,0 +1,36 @@ +## Timestamps + +Times may be specified in a wide variety of convenient formats. + + * [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) timestamps, such as `2019-10-12 07:20:50.12Z` + * A date of the format `YYYY-MM-DD` is interpreted as _local_ midnight on the given date. + Single-digit month and day are accepted, but the year must contain four digits. + * `now` refers to the exact current time + * `yesterday`, `today`, and `tomorrow` refer to _local_ midnight on the given day + * Any duration (described below) may be used as a timestamp, and is considered relative to the current time. + +Times are stored internally as UTC. + +## Durations + +Durations can be given in a dizzying array of units. +Each can be preceded by a whole number or a decimal multiplier, e.g., `3days`. +The multiplier is optional with the singular forms of the units; for example `day` is allowed. +Some of the units allow an adjectival form, such as `daily` or `annually`; this form is more readable in some cases, but otherwise has the same meaning. + + * `s`, `sec`, `secs`, `second`, or `seconds` + * `min`, `mins`, `minute`, or `minutes` (note that `m` is a month!) + * `h`, `hr`, `hrs`, `hour`, or `hours` + * `d`, `day`, `days`, `daily`, or `weekdays` (note, weekdays includes weekends!) + * `w`, `wk`, `wks`, `week`, `weeks`, or `weekly` + * `biweekly`, `fornight` or `sennight` (14 days) + * `m`, `mo`, `mos`, `mth`, `mths`, `mnths`, `month`, `months`, or `monthly` (always 30 days, regardless of calendar month) + * `binmonthly` (61 days) + * `q`, `qtr`, `qtrs`, `qrtr`, `qrtrs`, `quarter`, `quarters`, or `quarterly` (91 days) + * `semiannual` (183 days) + * `y`, `yr`, `yrs`, `year`, `years`, `yearly`, or `annual` (365 days, regardless of leap days) + * `biannual` or `biyearly` (730 days) + +[ISO 8601 standard durations](https://en.wikipedia.org/wiki/ISO_8601#Durations) are also allowed. +While the standard does not specify the length of "P1Y" or "P1M", Taskchampion treats those as 365 and 30 days, respectively. + From b18701c3cbfc44c219ba13268d91c4ba772e30e0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 1 Jun 2021 09:20:14 -0400 Subject: [PATCH 283/548] remove many duration strings to simplify --- cli/src/argparse/args/time.rs | 63 ----------------------------------- docs/src/time.md | 24 +++++-------- 2 files changed, 9 insertions(+), 78 deletions(-) diff --git a/cli/src/argparse/args/time.rs b/cli/src/argparse/args/time.rs index f17ebd880..4581d0a41 100644 --- a/cli/src/argparse/args/time.rs +++ b/cli/src/argparse/args/time.rs @@ -27,61 +27,27 @@ struct DurationCase(&'static str, Duration, bool); // TODO: use const when chrono supports it lazy_static! { static ref DURATION_CASES: Vec = vec![ - DurationCase("annual", Duration::days(365), true), - DurationCase("biannual", Duration::days(730), true), - DurationCase("bimonthly", Duration::days(61), true), - DurationCase("biweekly", Duration::days(14), true), - DurationCase("biyearly", Duration::days(730), true), - DurationCase("daily", Duration::days(1), true), DurationCase("days", Duration::days(1), false), DurationCase("day", Duration::days(1), true), DurationCase("d", Duration::days(1), false), - DurationCase("fortnight", Duration::days(14), true), DurationCase("hours", Duration::hours(1), false), DurationCase("hour", Duration::hours(1), true), - DurationCase("hrs", Duration::hours(1), false), - DurationCase("hr", Duration::hours(1), true), DurationCase("h", Duration::hours(1), false), DurationCase("minutes", Duration::minutes(1), false), DurationCase("minute", Duration::minutes(1), true), DurationCase("mins", Duration::minutes(1), false), DurationCase("min", Duration::minutes(1), true), - DurationCase("monthly", Duration::days(30), true), DurationCase("months", Duration::days(30), false), DurationCase("month", Duration::days(30), true), - DurationCase("mnths", Duration::days(30), false), - DurationCase("mths", Duration::days(30), false), - DurationCase("mth", Duration::days(30), true), - DurationCase("mos", Duration::days(30), false), DurationCase("mo", Duration::days(30), true), - DurationCase("m", Duration::days(30), false), - DurationCase("quarterly", Duration::days(91), true), - DurationCase("quarters", Duration::days(91), false), - DurationCase("quarter", Duration::days(91), true), - DurationCase("qrtrs", Duration::days(91), false), - DurationCase("qrtr", Duration::days(91), true), - DurationCase("qtrs", Duration::days(91), false), - DurationCase("qtr", Duration::days(91), true), - DurationCase("q", Duration::days(91), false), - DurationCase("semiannual", Duration::days(183), true), - DurationCase("sennight", Duration::days(14), false), DurationCase("seconds", Duration::seconds(1), false), DurationCase("second", Duration::seconds(1), true), - DurationCase("secs", Duration::seconds(1), false), - DurationCase("sec", Duration::seconds(1), true), DurationCase("s", Duration::seconds(1), false), - DurationCase("weekdays", Duration::days(1), true), - DurationCase("weekly", Duration::days(7), true), DurationCase("weeks", Duration::days(7), false), DurationCase("week", Duration::days(7), true), - DurationCase("wks", Duration::days(7), false), - DurationCase("wk", Duration::days(7), true), DurationCase("w", Duration::days(7), false), - DurationCase("yearly", Duration::days(365), true), DurationCase("years", Duration::days(365), false), DurationCase("year", Duration::days(365), true), - DurationCase("yrs", Duration::days(365), false), - DurationCase("yr", Duration::days(365), true), DurationCase("y", Duration::days(365), false), ]; } @@ -403,8 +369,6 @@ mod test { #[case("2 h", 2 * H)] #[case("10h", 10 * H)] #[case("1.5h", H + 30 * M)] - #[case("weekdays", DAY)] - #[case("daily", DAY)] #[case("0days", 0)] #[case("2 days", 2 * DAY)] #[case("10days", 10 * DAY)] @@ -417,7 +381,6 @@ mod test { #[case("2 d", 2 * DAY)] #[case("10d", 10 * DAY)] #[case("1.5d", DAY + 12 * H)] - #[case("weekly", 7 * DAY)] #[case("0weeks", 0)] #[case("2 weeks", 14 * DAY)] #[case("10weeks", 70 * DAY)] @@ -430,7 +393,6 @@ mod test { #[case("2 w", 14 * DAY)] #[case("10w", 70 * DAY)] #[case("1.5w", 10 * DAY + 12 * H)] - #[case("monthly", 30 * DAY)] #[case("0months", 0)] #[case("2 months", 60 * DAY)] #[case("10months", 300 * DAY)] @@ -443,20 +405,6 @@ mod test { #[case("2 mo", 60 * DAY)] #[case("10mo", 300 * DAY)] #[case("1.5mo", 45 * DAY)] - #[case("quarterly", 91 * DAY)] - #[case("0quarters", 0)] - #[case("2 quarters", 182 * DAY)] - #[case("10quarters", 910 * DAY)] - #[case("1.5quarters", 136 * DAY + 12 * H)] - #[case("0quarter", 0)] - #[case("2 quarter", 182 * DAY)] - #[case("10quarter", 910 * DAY)] - #[case("1.5quarter", 136 * DAY + 12 * H)] - #[case("0q", 0)] - #[case("2 q", 182 * DAY)] - #[case("10q", 910 * DAY)] - #[case("1.5q", 136 * DAY + 12 * H)] - #[case("yearly", YEAR)] #[case("0years", 0)] #[case("2 years", 2 * YEAR)] #[case("10years", 10 * YEAR)] @@ -469,17 +417,6 @@ mod test { #[case("2 y", 2 * YEAR)] #[case("10y", 10 * YEAR)] #[case("1.5y", 547 * DAY + 12 * H)] - #[case("annual", YEAR)] - #[case("biannual", 2 * YEAR)] - #[case("bimonthly", 61 * DAY)] - #[case("biweekly", 14 * DAY)] - #[case("biyearly", 2 * YEAR)] - #[case("fortnight", 14 * DAY)] - #[case("semiannual", 183 * DAY)] - #[case("0sennight", 0)] - #[case("2 sennight", 28 * DAY)] - #[case("10sennight", 140 * DAY)] - #[case("1.5sennight", 21 * DAY)] fn test_duration_units(#[case] input: &'static str, #[case] seconds: i64) { let (_, res) = complete_duration(input).expect(input); assert_eq!(res.num_seconds(), seconds, "parsing {}", input); diff --git a/docs/src/time.md b/docs/src/time.md index c6dc5e282..b82e1b2e1 100644 --- a/docs/src/time.md +++ b/docs/src/time.md @@ -3,10 +3,10 @@ Times may be specified in a wide variety of convenient formats. * [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) timestamps, such as `2019-10-12 07:20:50.12Z` - * A date of the format `YYYY-MM-DD` is interpreted as _local_ midnight on the given date. + * A date of the format `YYYY-MM-DD` is interpreted as the _local_ midnight at the beginning of the given date. Single-digit month and day are accepted, but the year must contain four digits. * `now` refers to the exact current time - * `yesterday`, `today`, and `tomorrow` refer to _local_ midnight on the given day + * `yesterday`, `today`, and `tomorrow` refer to the _local_ midnight at the beginning of the given day * Any duration (described below) may be used as a timestamp, and is considered relative to the current time. Times are stored internally as UTC. @@ -18,19 +18,13 @@ Each can be preceded by a whole number or a decimal multiplier, e.g., `3days`. The multiplier is optional with the singular forms of the units; for example `day` is allowed. Some of the units allow an adjectival form, such as `daily` or `annually`; this form is more readable in some cases, but otherwise has the same meaning. - * `s`, `sec`, `secs`, `second`, or `seconds` - * `min`, `mins`, `minute`, or `minutes` (note that `m` is a month!) - * `h`, `hr`, `hrs`, `hour`, or `hours` - * `d`, `day`, `days`, `daily`, or `weekdays` (note, weekdays includes weekends!) - * `w`, `wk`, `wks`, `week`, `weeks`, or `weekly` - * `biweekly`, `fornight` or `sennight` (14 days) - * `m`, `mo`, `mos`, `mth`, `mths`, `mnths`, `month`, `months`, or `monthly` (always 30 days, regardless of calendar month) - * `binmonthly` (61 days) - * `q`, `qtr`, `qtrs`, `qrtr`, `qrtrs`, `quarter`, `quarters`, or `quarterly` (91 days) - * `semiannual` (183 days) - * `y`, `yr`, `yrs`, `year`, `years`, `yearly`, or `annual` (365 days, regardless of leap days) - * `biannual` or `biyearly` (730 days) + * `s`, `second`, or `seconds` + * `min`, `mins`, `minute`, or `minutes` (note that `m` not allowed, as it might also mean `month`) + * `h`, `hour`, or `hours` + * `d`, `day`, or `days` + * `w`, `week`, or `weeks` + * `mo`, or `months` (always 30 days, regardless of calendar month) + * `y`, `year`, or `years` (365 days, regardless of leap days) [ISO 8601 standard durations](https://en.wikipedia.org/wiki/ISO_8601#Durations) are also allowed. While the standard does not specify the length of "P1Y" or "P1M", Taskchampion treats those as 365 and 30 days, respectively. - From ac6b020b6dd8adbad991679122168548901b2f75 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 1 Jun 2021 09:23:36 -0400 Subject: [PATCH 284/548] minor updates from review --- cli/src/argparse/args/colon.rs | 14 +++++++------- cli/src/argparse/modification.rs | 6 +++--- cli/src/invocation/cmd/info.rs | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cli/src/argparse/args/colon.rs b/cli/src/argparse/args/colon.rs index ecf1af6c9..3fa17ce97 100644 --- a/cli/src/argparse/args/colon.rs +++ b/cli/src/argparse/args/colon.rs @@ -5,8 +5,8 @@ use nom::bytes::complete::tag as nomtag; use nom::{branch::*, character::complete::*, combinator::*, sequence::*, IResult}; use taskchampion::Status; -/// Recognizes a colon-prefixed pair -fn colon_prefixed(prefix: &'static str) -> impl Fn(&str) -> IResult<&str, &str> { +/// Recognizes up to the colon of the common `:...` syntax +fn colon_prefix(prefix: &'static str) -> impl Fn(&str) -> IResult<&str, &str> { fn to_suffix<'a>(input: (&'a str, char, &'a str)) -> Result<&'a str, ()> { Ok(input.2) } @@ -28,7 +28,7 @@ pub(crate) fn status_colon(input: &str) -> IResult<&str, Status> { _ => Err(()), } } - map_res(colon_prefixed("status"), to_status)(input) + map_res(colon_prefix("status"), to_status)(input) } /// Recognizes `wait:` to None and `wait:` to `Some(ts)` @@ -53,10 +53,10 @@ mod test { use super::*; #[test] - fn test_colon_prefixed() { - assert_eq!(colon_prefixed("foo")("foo:abc").unwrap().1, "abc"); - assert_eq!(colon_prefixed("foo")("foo:").unwrap().1, ""); - assert!(colon_prefixed("foo")("foo").is_err()); + fn test_colon_prefix() { + assert_eq!(colon_prefix("foo")("foo:abc").unwrap().1, "abc"); + assert_eq!(colon_prefix("foo")("foo:").unwrap().1, ""); + assert!(colon_prefix("foo")("foo").is_err()); } #[test] diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index dcdc3d303..083f7fc8e 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -154,9 +154,9 @@ impl Modification { syntax: "wait:", summary: "Set or unset the task's wait time", description: " - Set the time before which the task is not actionable and - should not be shown in reports. With `wait:`, the time - is un-set. See the documentation for the timestamp syntax.", + Set the time before which the task is not actionable and should not be shown in + reports, e.g., `wait:3day` to wait for three days. With `wait:`, the time is + un-set. See the documentation for the timestamp syntax.", }); } } diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index f450b7a6f..c77476a69 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -36,8 +36,8 @@ pub(crate) fn execute( tags.sort(); t.add_row(row![b->"Tags", tags.join(" ")]); } - if task.is_waiting() { - t.add_row(row![b->"Wait", task.get_wait().unwrap()]); + if let Some(wait) = task.get_wait() { + t.add_row(row![b->"Wait", wait]); } } t.print(w)?; From 5f28eb3a743b2d3730820fc5b02d85c2ce986078 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 4 Jun 2021 09:26:12 -0400 Subject: [PATCH 285/548] produce Tag instances in the parser (#260) and.. * fix usage-docs plugin * upgrade mdbook --- Cargo.lock | 4 +-- cli/Cargo.toml | 2 +- cli/src/argparse/args/arg_matching.rs | 2 +- cli/src/argparse/args/misc.rs | 2 +- cli/src/argparse/args/tags.rs | 38 ++++++--------------------- cli/src/argparse/filter.rs | 30 ++++++++++----------- cli/src/argparse/modification.rs | 24 ++++++++--------- cli/src/bin/usage-docs.rs | 5 +++- cli/src/invocation/filter.rs | 24 ++++++++--------- cli/src/invocation/modify.rs | 3 --- cli/src/macros.rs | 6 +++++ cli/src/usage.rs | 2 +- 12 files changed, 62 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5b7e61c9..37d2536bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1630,9 +1630,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "mdbook" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed4060ccf332a0479df37e84c8435ad20be737d5337c3a90fa1b3b0d480a3a0" +checksum = "aeb86d199d0c1e8d41f3a9e9b0ba8639d6951a10043129159809ac9c18e3ce05" dependencies = [ "ammonia", "anyhow", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 05cfcb20c..fca908f70 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -27,7 +27,7 @@ lazy_static = "1" iso8601-duration = "0.1" # only needed for usage-docs -mdbook = { version = "0.4", optional = true } +mdbook = { version = "0.4.9", optional = true } serde_json = { version = "*", optional = true } [dependencies.taskchampion] diff --git a/cli/src/argparse/args/arg_matching.rs b/cli/src/argparse/args/arg_matching.rs index e1161738e..f52aa4254 100644 --- a/cli/src/argparse/args/arg_matching.rs +++ b/cli/src/argparse/args/arg_matching.rs @@ -44,7 +44,7 @@ mod test { fn test_arg_matching() { assert_eq!( arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), - (argv!["bar"], "foo") + (argv!["bar"], tag!("foo")) ); assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); } diff --git a/cli/src/argparse/args/misc.rs b/cli/src/argparse/args/misc.rs index 006a0b939..27d2a1315 100644 --- a/cli/src/argparse/args/misc.rs +++ b/cli/src/argparse/args/misc.rs @@ -25,7 +25,7 @@ mod test { fn test_arg_matching() { assert_eq!( arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), - (argv!["bar"], "foo") + (argv!["bar"], tag!("foo")) ); assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); } diff --git a/cli/src/argparse/args/tags.rs b/cli/src/argparse/args/tags.rs index 8c2cbd9c1..c15dae6bf 100644 --- a/cli/src/argparse/args/tags.rs +++ b/cli/src/argparse/args/tags.rs @@ -3,31 +3,13 @@ use std::convert::TryFrom; use taskchampion::Tag; /// Recognizes a tag prefixed with `+` and returns the tag value -pub(crate) fn plus_tag(input: &str) -> IResult<&str, &str> { - fn to_tag(input: (char, &str)) -> Result<&str, ()> { - Ok(input.1) - } - map_res( - all_consuming(tuple(( - char('+'), - recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), - ))), - to_tag, - )(input) +pub(crate) fn plus_tag(input: &str) -> IResult<&str, Tag> { + preceded(char('+'), map_res(rest, Tag::try_from))(input) } /// Recognizes a tag prefixed with `-` and returns the tag value -pub(crate) fn minus_tag(input: &str) -> IResult<&str, &str> { - fn to_tag(input: (char, &str)) -> Result<&str, ()> { - Ok(input.1) - } - map_res( - all_consuming(tuple(( - char('-'), - recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), - ))), - to_tag, - )(input) +pub(crate) fn minus_tag(input: &str) -> IResult<&str, Tag> { + preceded(char('-'), map_res(rest, Tag::try_from))(input) } #[cfg(test)] @@ -36,21 +18,17 @@ mod test { #[test] fn test_plus_tag() { - assert_eq!(plus_tag("+abc").unwrap().1, "abc"); - assert_eq!(plus_tag("+abc123").unwrap().1, "abc123"); + assert_eq!(plus_tag("+abc").unwrap().1, tag!("abc")); + assert_eq!(plus_tag("+abc123").unwrap().1, tag!("abc123")); assert!(plus_tag("-abc123").is_err()); - assert!(plus_tag("+abc123 ").is_err()); - assert!(plus_tag(" +abc123").is_err()); assert!(plus_tag("+1abc").is_err()); } #[test] fn test_minus_tag() { - assert_eq!(minus_tag("-abc").unwrap().1, "abc"); - assert_eq!(minus_tag("-abc123").unwrap().1, "abc123"); + assert_eq!(minus_tag("-abc").unwrap().1, tag!("abc")); + assert_eq!(minus_tag("-abc123").unwrap().1, tag!("abc123")); assert!(minus_tag("+abc123").is_err()); - assert!(minus_tag("-abc123 ").is_err()); - assert!(minus_tag(" -abc123").is_err()); assert!(minus_tag("-1abc").is_err()); } } diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index be63aa1af..fa2746bb1 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -8,7 +8,7 @@ use nom::{ multi::{fold_many0, fold_many1}, IResult, }; -use taskchampion::Status; +use taskchampion::{Status, Tag}; /// A filter represents a selection of a particular set of tasks. /// @@ -26,10 +26,10 @@ pub(crate) struct Filter { #[derive(Debug, PartialEq, Clone)] pub(crate) enum Condition { /// Task has the given tag - HasTag(String), + HasTag(Tag), /// Task does not have the given tag - NoTag(String), + NoTag(Tag), /// Task has the given status Status(Status), @@ -68,15 +68,15 @@ impl Condition { } fn parse_plus_tag(input: ArgList) -> IResult { - fn to_condition(input: &str) -> Result { - Ok(Condition::HasTag(input.to_owned())) + fn to_condition(input: Tag) -> Result { + Ok(Condition::HasTag(input)) } map_res(arg_matching(plus_tag), to_condition)(input) } fn parse_minus_tag(input: ArgList) -> IResult { - fn to_condition(input: &str) -> Result { - Ok(Condition::NoTag(input.to_owned())) + fn to_condition(input: Tag) -> Result { + Ok(Condition::NoTag(input)) } map_res(arg_matching(minus_tag), to_condition)(input) } @@ -320,8 +320,8 @@ mod test { Filter { conditions: vec![ Condition::IdList(vec![TaskId::WorkingSetId(1),]), - Condition::HasTag("yes".into()), - Condition::NoTag("no".into()), + Condition::HasTag(tag!("yes")), + Condition::NoTag(tag!("no")), ], } ); @@ -353,10 +353,10 @@ mod test { conditions: vec![ // from first filter Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]), - Condition::HasTag("yes".into()), + Condition::HasTag(tag!("yes")), // from second filter Condition::IdList(vec![TaskId::WorkingSetId(2), TaskId::WorkingSetId(3)]), - Condition::HasTag("no".into()), + Condition::HasTag(tag!("no")), ], } ); @@ -373,9 +373,9 @@ mod test { conditions: vec![ // from first filter Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]), - Condition::HasTag("yes".into()), + Condition::HasTag(tag!("yes")), // from second filter - Condition::HasTag("no".into()), + Condition::HasTag(tag!("no")), ], } ); @@ -390,8 +390,8 @@ mod test { both, Filter { conditions: vec![ - Condition::HasTag("yes".into()), - Condition::HasTag("no".into()), + Condition::HasTag(tag!("yes")), + Condition::HasTag(tag!("no")), ], } ); diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 083f7fc8e..bd37db928 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -4,7 +4,7 @@ use crate::usage; use chrono::prelude::*; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use std::collections::HashSet; -use taskchampion::Status; +use taskchampion::{Status, Tag}; #[derive(Debug, PartialEq, Clone)] pub enum DescriptionMod { @@ -44,17 +44,17 @@ pub struct Modification { pub active: Option, /// Add tags - pub add_tags: HashSet, + pub add_tags: HashSet, /// Remove tags - pub remove_tags: HashSet, + pub remove_tags: HashSet, } /// A single argument that is part of a modification, used internally to this module enum ModArg<'a> { Description(&'a str), - PlusTag(&'a str), - MinusTag(&'a str), + PlusTag(Tag), + MinusTag(Tag), Wait(Option>), } @@ -71,10 +71,10 @@ impl Modification { } } ModArg::PlusTag(tag) => { - acc.add_tags.insert(tag.to_owned()); + acc.add_tags.insert(tag); } ModArg::MinusTag(tag) => { - acc.remove_tags.insert(tag.to_owned()); + acc.remove_tags.insert(tag); } ModArg::Wait(wait) => { acc.wait = Some(wait); @@ -105,14 +105,14 @@ impl Modification { } fn plus_tag(input: ArgList) -> IResult { - fn to_modarg(input: &str) -> Result { + fn to_modarg(input: Tag) -> Result, ()> { Ok(ModArg::PlusTag(input)) } map_res(arg_matching(plus_tag), to_modarg)(input) } fn minus_tag(input: ArgList) -> IResult { - fn to_modarg(input: &str) -> Result { + fn to_modarg(input: Tag) -> Result, ()> { Ok(ModArg::MinusTag(input)) } map_res(arg_matching(minus_tag), to_modarg)(input) @@ -198,7 +198,7 @@ mod test { assert_eq!( modification, Modification { - add_tags: set![s!("abc"), s!("def")], + add_tags: set![tag!("abc"), tag!("def")], ..Default::default() } ); @@ -252,8 +252,8 @@ mod test { modification, Modification { description: DescriptionMod::Set(s!("new desc fun")), - add_tags: set![s!("next")], - remove_tags: set![s!("daytime")], + add_tags: set![tag!("next")], + remove_tags: set![tag!("daytime")], ..Default::default() } ); diff --git a/cli/src/bin/usage-docs.rs b/cli/src/bin/usage-docs.rs index f78179c71..baa4079e1 100644 --- a/cli/src/bin/usage-docs.rs +++ b/cli/src/bin/usage-docs.rs @@ -40,7 +40,10 @@ fn process(_ctx: &PreprocessorContext, mut book: Book) -> Result { if new_content != chapter.content { eprintln!( "Substituting usage in {:?}", - chapter.source_path.as_ref().unwrap() + chapter + .source_path + .as_ref() + .unwrap_or(chapter.path.as_ref().unwrap()) ); } chapter.content = new_content; diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index 0cc6e31fe..0d4add799 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -1,22 +1,17 @@ use crate::argparse::{Condition, Filter, TaskId}; use std::collections::HashSet; -use std::convert::TryInto; -use taskchampion::{Replica, Status, Tag, Task, Uuid, WorkingSet}; +use taskchampion::{Replica, Status, Task, Uuid, WorkingSet}; fn match_task(filter: &Filter, task: &Task, uuid: Uuid, working_set: &WorkingSet) -> bool { for cond in &filter.conditions { match cond { Condition::HasTag(ref tag) => { - // see #111 for the unwrap - let tag: Tag = tag.try_into().unwrap(); - if !task.has_tag(&tag) { + if !task.has_tag(tag) { return false; } } Condition::NoTag(ref tag) => { - // see #111 for the unwrap - let tag: Tag = tag.try_into().unwrap(); - if task.has_tag(&tag) { + if task.has_tag(tag) { return false; } } @@ -254,8 +249,8 @@ mod test { #[test] fn tag_filtering() -> anyhow::Result<()> { let mut replica = test_replica(); - let yes: Tag = "yes".try_into()?; - let no: Tag = "no".try_into()?; + let yes = tag!("yes"); + let no = tag!("no"); let mut t1 = replica .new_task(Status::Pending, s!("A"))? @@ -274,7 +269,7 @@ mod test { // look for just "yes" (A and B) let filter = Filter { - conditions: vec![Condition::HasTag(s!("yes"))], + conditions: vec![Condition::HasTag(tag!("yes"))], }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) @@ -284,7 +279,7 @@ mod test { // look for tags without "no" (A, D) let filter = Filter { - conditions: vec![Condition::NoTag(s!("no"))], + conditions: vec![Condition::NoTag(tag!("no"))], }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) @@ -294,7 +289,10 @@ mod test { // look for tags with "yes" and "no" (B) let filter = Filter { - conditions: vec![Condition::HasTag(s!("yes")), Condition::HasTag(s!("no"))], + conditions: vec![ + Condition::HasTag(tag!("yes")), + Condition::HasTag(tag!("no")), + ], }; let filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index 7ef6d758c..dd943fdd1 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,5 +1,4 @@ use crate::argparse::{DescriptionMod, Modification}; -use std::convert::TryInto; use taskchampion::TaskMut; /// Apply the given modification @@ -31,12 +30,10 @@ pub(super) fn apply_modification( } for tag in modification.add_tags.iter() { - let tag = tag.try_into()?; // see #111 task.add_tag(&tag)?; } for tag in modification.remove_tags.iter() { - let tag = tag.try_into()?; // see #111 task.remove_tag(&tag)?; } diff --git a/cli/src/macros.rs b/cli/src/macros.rs index 02e11e5cf..1a3024c13 100644 --- a/cli/src/macros.rs +++ b/cli/src/macros.rs @@ -30,3 +30,9 @@ macro_rules! set( macro_rules! s( { $s:expr } => { $s.to_owned() }; ); + +/// Create a Tag from an &str; just a testing shorthand +#[cfg(test)] +macro_rules! tag( + { $s:expr } => { { use std::convert::TryFrom; taskchampion::Tag::try_from($s).unwrap() } }; +); diff --git a/cli/src/usage.rs b/cli/src/usage.rs index 59a2ba982..b3f688909 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -93,7 +93,7 @@ impl Usage { /// With the appropriate documentation. pub fn substitute_docs(&self, content: &str) -> Result { // this is not efficient, but it doesn't need to be - let mut lines = content.lines(); + let lines = content.lines(); let mut w = String::new(); const DOC_HEADER_PREFIX: &str = "Ij~p?MZK_HHbm0uRn9i7~fsCJDiXPoAD~rE#$9a47q!=G7 zq-{0qq&;E2{W&vHt+#qLV$*WcZob9)Hy$UCM)G(I-n@BZ^LqGr6bpR*Zb1x2iI0^X<3AH8@o(`;iN*!Eqa%a6@5Goz>7wDz9<@g?+e z|M-iNV9e#h8=JE-mF&98&+maI1r-(gfOQ}R0*gI^Gm#?zdAz8QZgl?MdyGaP%KuSf z6ev-y|8twt;y0X!nGSyYTU+SXf$deJ@_-e0$-9wHmVaIU8}2U%zUXYiT@blc4gIsI z`)w#jNA31+OZo-+FV$sTJnWubcM7Q{0PiUbk9N#@&n|+=FX(^Ky5O^ET7h`uQ8f)v zQuKVrb+JA-kKO-80JfxD_&YIw^Z$R_N+yT8Xha77mpz0hk7oWusk}Ql0zu>0*qG1Y zQ;=RKI%8v#L&lv&MZYH^KQo)W{q^+q z_qK5w`irX%)z-S^7Z%okl68-LdJ5Td7IV0+XSjx0@@HkGfm|*hQfvo*9FYiv#m>(@ zc~S$idGlt)(7U}M@2f!~XlOvHp%m{rv-8ewklcs9M-NT?xZl*|Dv?0H4R#*x>toZl zu6)6V**Q4WcZqBI$FJy@UbrwnJw5$nf4>`(X#z2}wOzKv$hqT5#~2@AEPzaap!@gl z*8ptL+}wO5Eo~NPC`?TZ*7r#mpbUZ}$>DHnKwjPpkROfcWO=oBlQ6zDULuvYJ%646 z7((IGq?wr*#dbqTP1CWB-T?uNPq!u1o{87N>7lNAV1R^xVvMoQ4$wJ)LZOhLhgpEVzVHrS!?<5t>w3E4yK8)7(kJo|Up5b>tCOWCPX90wFl=Nx^#Qu<%-MCQ)tObg# znx?e1wdwYpxHm7upfOd91(swe6nc=WQ4>O$T)xiSd?92AfV33=6(lq=T|o=x;>C+c zGBf8vuF{Lt02~x(JNuu%5fY>(&n-dRb60!Ok|m}V7TW*Xw4oLU>~H7Ig~(*GKLOx` zJWKfm5`0e1B4DV3f(_t$a{iF>E}1^n{OkF1b%&{_sObE~i*@JlnMaNtn+=&9drI{7 z_fJuC(2!DfB@$Qozo?3G*jF2r5^88flcZHBhP$P2Vu2f+v|Bd57A~2i?nSh{d^ta} zWiGIFijICa(N z=qSXn`f%68lzP~S-Ny7MD6s==J!Ak&lG^bnx6K9n8SvtF9&y6?Aow7kZPx1R zZ@ZIQGOg+dB0#X70V4PB-w!$_nvZgwlBcJmw^~}7I60k4hIXs*o};hEi^tvBb#(|9 zi)F00sE!5g`Zn-deW!pA!lq08B&%poL5)wdKNS*Gb8oRrhAjm)x0Io0bM&|&^c*xQB&Fx50eRA0U)`YzBK~>V6 z_Vi|YvYBtS>zlT=z2Ck?0LoRvhXu=I{4!4ZlpM;5462O&Ru-r|}raRby@urA6PrD|_F+zpGm9y;C&Y2+~&9uma@1GyXS-L?Q;jS#`J8 zS`V@ZT=AaH!9eNdS0B5xK?(y53pDxwM*l=GQNKKf;^Dt0p9{W&SsN!2Z^opIyJ+UGW3A3pi+~eA#Qo1Ka+?|}QnxlIMhrJb+)X=sbL?%@;O2!ZKzzgg zWv``+G`+pO_xAR(^7Her_I7umSF(|}e{0zBH-PCTmM{6e>SL*r5BSFZcvm}n&aAyf sIV%NeG>hgJi~lX?_kYtHE&8buKXPGXJwuEF|J#RPQ3T`_M9{wf00T~@V*mgE literal 0 HcmV?d00001 diff --git a/docs/src/time.md b/docs/src/time.md index 28ab9b735..dc3ba28a4 100644 --- a/docs/src/time.md +++ b/docs/src/time.md @@ -42,4 +42,7 @@ Some commonly used named timestamps * `sow` Start of the next week * `eow` End of the week * `eoww` End of work week - * `soww` Start of the next work week \ No newline at end of file + * `soww` Start of the next work week + + +![named timestamp](/docs/assets/cgi/named_timestamp.jpg) From 037807e3acc104e17be7be38d2a6590a6af1b098 Mon Sep 17 00:00:00 2001 From: Ravi Sawlani Date: Sat, 28 Aug 2021 02:06:33 +0530 Subject: [PATCH 322/548] update image ref --- docs/{assets/cgi => src/images}/name_timestamp.png | Bin docs/src/time.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/{assets/cgi => src/images}/name_timestamp.png (100%) diff --git a/docs/assets/cgi/name_timestamp.png b/docs/src/images/name_timestamp.png similarity index 100% rename from docs/assets/cgi/name_timestamp.png rename to docs/src/images/name_timestamp.png diff --git a/docs/src/time.md b/docs/src/time.md index dc3ba28a4..4053aea29 100644 --- a/docs/src/time.md +++ b/docs/src/time.md @@ -45,4 +45,4 @@ Some commonly used named timestamps * `soww` Start of the next work week -![named timestamp](/docs/assets/cgi/named_timestamp.jpg) +![named timestamp](images/name_timestamp.png) From 477bf9e3283129549e5677956dff2b884e722389 Mon Sep 17 00:00:00 2001 From: dbr/Ben Date: Sat, 4 Sep 2021 12:05:30 +0930 Subject: [PATCH 323/548] Tweaks from code review Co-authored-by: Dustin J. Mitchell --- sync-server/src/storage/sqlite.rs | 4 ++-- taskchampion/src/storage/sqlite.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index be075f62b..0fec8d9d1 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -41,7 +41,7 @@ impl FromSql for Client { } } -/// Parsers Operation stored as JSON in string column +/// Parses Operation stored as JSON in string column impl ToSql for Client { fn to_sql(&self) -> rusqlite::Result> { let s = serde_json::to_string(&self) @@ -72,7 +72,7 @@ impl SqliteStorage { let queries = vec![ "CREATE TABLE IF NOT EXISTS clients (client_key STRING PRIMARY KEY, latest_version_id STRING);", - "CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, client_key STRING, parent_version_id STRING, history_segment STRING);", + "CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, client_key STRING, parent_version_id STRING, history_segment BLOB);", ]; for q in queries { txn.execute(q, []).context("Creating table")?; diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index b6ff83027..fed95d455 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -288,7 +288,7 @@ impl<'t> StorageTxn for Txn<'t> { let rows: Vec> = rows.collect(); let mut res = Vec::with_capacity(rows.len()); - for _ in 0..self.get_next_working_set_number().context("HUh")? { + for _ in 0..self.get_next_working_set_number().context("Getting working set number")? { res.push(None); } for r in rows { From 1d627994371559af815ce7646fcdfd3364f55613 Mon Sep 17 00:00:00 2001 From: dbr Date: Sat, 4 Sep 2021 12:53:29 +1000 Subject: [PATCH 324/548] Deduplicate StoredUuid wrapper --- taskchampion/src/server/local.rs | 30 +----------------------------- taskchampion/src/storage/mod.rs | 2 +- taskchampion/src/storage/sqlite.rs | 2 +- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 8b5dfa9a7..12d50a5db 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -1,6 +1,7 @@ use crate::server::{ AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, }; +use crate::storage::sqlite::StoredUuid; use anyhow::Context; use rusqlite::params; use rusqlite::types::{FromSql, ToSql}; @@ -9,35 +10,6 @@ use serde::{Deserialize, Serialize}; use std::path::Path; use uuid::Uuid; -// FIXME: Duplicated -/// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` -pub struct StoredUuid(Uuid); - -/// Conversion from Uuid stored as a string (rusqlite's uuid feature stores as binary blob) -impl FromSql for StoredUuid { - fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { - let u = Uuid::parse_str(value.as_str()?) - .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; - Ok(StoredUuid(u)) - } -} - -/// Store Uuid as string in database -impl ToSql for StoredUuid { - fn to_sql(&self) -> rusqlite::Result> { - let s = self.0.to_string(); - Ok(s.into()) - } -} - -impl FromSql for Version { - fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { - let u = serde_json::from_str(value.as_str()?) - .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; - Ok(u) - } -} - #[derive(Serialize, Deserialize, Debug)] struct Version { version_id: VersionId, diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index 7520950e0..18e64a9bb 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -12,7 +12,7 @@ use uuid::Uuid; mod config; mod inmemory; mod operation; -mod sqlite; +pub(crate) mod sqlite; pub use config::StorageConfig; pub use inmemory::InMemoryStorage; diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index fed95d455..63fb3bad5 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -12,7 +12,7 @@ enum SqliteError { } /// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` -struct StoredUuid(Uuid); +pub(crate) struct StoredUuid(pub(crate) Uuid); /// Conversion from Uuid stored as a string (rusqlite's uuid feature stores as binary blob) impl FromSql for StoredUuid { From 89e9a42374d096e3fcfdca6a32bb19746d04bb2a Mon Sep 17 00:00:00 2001 From: dbr Date: Sat, 4 Sep 2021 13:02:03 +1000 Subject: [PATCH 325/548] Refactor calculation of next working set ID As per Dustin's code-review comment --- taskchampion/src/storage/sqlite.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 63fb3bad5..55f7f7793 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -112,14 +112,14 @@ impl<'t> Txn<'t> { fn get_next_working_set_number(&self) -> anyhow::Result { let t = self.get_txn()?; - let result: Option = t - .query_row("SELECT COALESCE(MAX(id), 0) FROM working_set", [], |r| { + let next_id: Option = t + .query_row("SELECT COALESCE(MAX(id), 0) + 1 FROM working_set", [], |r| { r.get(0) }) .optional() .context("Getting highest working set ID")?; - Ok(result.unwrap_or(0) + 1) + Ok(next_id.unwrap_or(0)) } } From 5db04ee1afc79472d70a61216b23a621591e56de Mon Sep 17 00:00:00 2001 From: dbr Date: Sat, 4 Sep 2021 13:05:10 +1000 Subject: [PATCH 326/548] Tidying --- taskchampion/src/server/local.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 12d50a5db..657d60881 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -4,7 +4,6 @@ use crate::server::{ use crate::storage::sqlite::StoredUuid; use anyhow::Context; use rusqlite::params; -use rusqlite::types::{FromSql, ToSql}; use rusqlite::OptionalExtension; use serde::{Deserialize, Serialize}; use std::path::Path; From f8ed4cecdd9d71233c210a7456c209f8a53674f0 Mon Sep 17 00:00:00 2001 From: dbr Date: Sat, 4 Sep 2021 13:05:21 +1000 Subject: [PATCH 327/548] Reset operation auto-increment ID --- taskchampion/src/storage/sqlite.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 55f7f7793..0e56479fe 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -267,6 +267,8 @@ impl<'t> StorageTxn for Txn<'t> { let t = self.get_txn()?; t.execute("DELETE FROM operations", []) .context("Clear all existing operations")?; + t.execute("DELETE FROM sqlite_sequence WHERE name = 'operations'", []) + .context("Clear all existing operations")?; for o in ops { self.add_operation(o)?; From a4b67d9f4ef1bf10ed7d5811064db18bc73bae35 Mon Sep 17 00:00:00 2001 From: dbr Date: Sat, 4 Sep 2021 13:29:03 +1000 Subject: [PATCH 328/548] Remove unused error variant --- sync-server/src/storage/sqlite.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index 0fec8d9d1..34cdd05b7 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -6,8 +6,6 @@ use std::path::Path; #[derive(Debug, thiserror::Error)] enum SqliteError { - #[error("SQLite transaction already committted")] - TransactionAlreadyCommitted, #[error("Failed to create SQLite transaction")] CreateTransactionFailed, } From 8ca7f70cef5e094c034043abe447d78eaf78d942 Mon Sep 17 00:00:00 2001 From: dbr Date: Sun, 5 Sep 2021 17:02:48 +1000 Subject: [PATCH 329/548] Store changelog snippets as .md --- CONTRIBUTING.md | 2 +- scripts/changelog.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 658c0957f..2adfeba3e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,7 +58,7 @@ In order to manage this, changelog entries are stored as text files in the `.cha To add a new changelog entry, you can simply run `python3 ./script/changelog.py add "Fixed thingo to increase zorbloxification [Issue #2](http://example.com)` -This creates a file named `./changelogs/yyyy-mm-dd-branchname.txt` (timestamp, current git branch) which contains a markdown snippet. +This creates a file named `./changelogs/yyyy-mm-dd-branchname.md` (timestamp, current git branch) which contains a markdown snippet. If you don't have a Python 3 intepreter installed, you can simply create this file manually. It should contain a list item like `- Fixed thingo [...]` diff --git a/scripts/changelog.py b/scripts/changelog.py index 5775d99c0..0eac4fa35 100755 --- a/scripts/changelog.py +++ b/scripts/changelog.py @@ -22,7 +22,7 @@ def get_changefiles() -> List[str]: changedir = get_dir() changefiles = [] for f in os.listdir(changedir): - if f.endswith(".txt") and not f.startswith("."): + if f.endswith(".md") and not f.startswith("."): changefiles.append(os.path.join(changedir, f)) return changefiles @@ -34,7 +34,7 @@ def cmd_add(args): timestamp = ymd() branchname = git_current_branch() - fname = os.path.join(get_dir(), "%s-%s.txt" % (timestamp, branchname)) + fname = os.path.join(get_dir(), "%s-%s.md" % (timestamp, branchname)) with open(fname, "a") as f: f.write(text) f.write("\n") From 72b494148515371e917d50231e5a7505160e0141 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 5 Sep 2021 21:55:09 +0000 Subject: [PATCH 330/548] fix new clippy warnings --- cli/src/invocation/mod.rs | 2 +- cli/src/invocation/modify.rs | 4 ++-- cli/src/settings/report.rs | 2 +- cli/src/settings/settings.rs | 2 +- sync-server/src/storage/kv.rs | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index b2335a345..640925a69 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -125,7 +125,7 @@ fn get_server(settings: &Settings) -> anyhow::Result> { settings.server_origin.as_ref(), settings.encryption_secret.as_ref(), ) { - let client_key = Uuid::parse_str(&client_key)?; + let client_key = Uuid::parse_str(client_key)?; log::debug!("Using sync-server with origin {}", origin); log::debug!("Sync client ID: {}", client_key); diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index dd943fdd1..2e27fba74 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -30,11 +30,11 @@ pub(super) fn apply_modification( } for tag in modification.add_tags.iter() { - task.add_tag(&tag)?; + task.add_tag(tag)?; } for tag in modification.remove_tags.iter() { - task.remove_tag(&tag)?; + task.remove_tag(tag)?; } if let Some(wait) = modification.wait { diff --git a/cli/src/settings/report.rs b/cli/src/settings/report.rs index 1911bf84e..78650ffe3 100644 --- a/cli/src/settings/report.rs +++ b/cli/src/settings/report.rs @@ -130,7 +130,7 @@ impl TryFrom<&toml::Value> for Report { .map(|(i, v)| { v.as_str() .ok_or_else(|| anyhow!(".filter[{}]: not a string", i)) - .and_then(|s| Condition::parse_str(&s)) + .and_then(|s| Condition::parse_str(s)) .map_err(|e| anyhow!(".filter[{}]: {}", i, e)) }) .collect::>>()?, diff --git a/cli/src/settings/settings.rs b/cli/src/settings/settings.rs index 83f8938ae..e1809091f 100644 --- a/cli/src/settings/settings.rs +++ b/cli/src/settings/settings.rs @@ -97,7 +97,7 @@ impl Settings { "server_dir", "reports", ]; - let table = table_with_keys(&config_toml, &table_keys)?; + let table = table_with_keys(config_toml, &table_keys)?; fn get_str_cfg( table: &Table, diff --git a/sync-server/src/storage/kv.rs b/sync-server/src/storage/kv.rs index bd5fa3502..19218a235 100644 --- a/sync-server/src/storage/kv.rs +++ b/sync-server/src/storage/kv.rs @@ -86,7 +86,7 @@ impl<'t> StorageTxn for Txn<'t> { let bucket = self.clients_bucket(); let kvtxn = self.kvtxn(); - let client = match kvtxn.get(&bucket, key) { + let client = match kvtxn.get(bucket, key) { Ok(buf) => buf, Err(Error::NotFound) => return Ok(None), Err(e) => return Err(e.into()), @@ -101,7 +101,7 @@ impl<'t> StorageTxn for Txn<'t> { let bucket = self.clients_bucket(); let kvtxn = self.kvtxn(); let client = Client { latest_version_id }; - kvtxn.set(&bucket, key, Msgpack::to_value_buf(client)?)?; + kvtxn.set(bucket, key, Msgpack::to_value_buf(client)?)?; Ok(()) } @@ -122,7 +122,7 @@ impl<'t> StorageTxn for Txn<'t> { let key = version_db_key(client_key, parent_version_id); let bucket = self.versions_bucket(); let kvtxn = self.kvtxn(); - let version = match kvtxn.get(&bucket, key) { + let version = match kvtxn.get(bucket, key) { Ok(buf) => buf, Err(Error::NotFound) => return Ok(None), Err(e) => return Err(e.into()), @@ -147,7 +147,7 @@ impl<'t> StorageTxn for Txn<'t> { parent_version_id, history_segment, }; - kvtxn.set(&bucket, key, Msgpack::to_value_buf(version)?)?; + kvtxn.set(bucket, key, Msgpack::to_value_buf(version)?)?; Ok(()) } From 91b2e1164f59a5cf7ac0240a4e7c266618d69586 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 5 Sep 2021 21:57:34 +0000 Subject: [PATCH 331/548] run clippy on the MSRV --- .github/workflows/checks.yml | 7 +++++++ .github/workflows/tests.yml | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index dc11d46a8..3c96dafb7 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -27,6 +27,13 @@ jobs: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + - uses: actions-rs/toolchain@v1 + with: + # Fixed version for clippy lints. Bump this as necesary. It must not + # be older than the MSRV in tests.yml. + toolchain: "1.54" + override: true + - uses: actions-rs/cargo@v1.0.1 with: command: check diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6baef4519..78a0e183e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,8 @@ jobs: strategy: matrix: rust: - - "1.47" # MSRV + # MSRV; most not be higher than the clippy rust version in checks.yml + - "1.47" - "stable" os: - ubuntu-latest From 11a3b7882bc1c7b9cf84b260e4f36f130a420817 Mon Sep 17 00:00:00 2001 From: dbr Date: Fri, 10 Sep 2021 10:03:46 +1000 Subject: [PATCH 332/548] Changelog entry! --- .changelogs/2021-09-10-sqlstore.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelogs/2021-09-10-sqlstore.md diff --git a/.changelogs/2021-09-10-sqlstore.md b/.changelogs/2021-09-10-sqlstore.md new file mode 100644 index 000000000..b0151816c --- /dev/null +++ b/.changelogs/2021-09-10-sqlstore.md @@ -0,0 +1 @@ +- Breaking: Removed the KV based storage backend in client and server, and replaced with SQLite ([Issue #131](https://github.com/taskchampion/taskchampion/issues/131), [PR #206](https://github.com/taskchampion/taskchampion/pull/206)) From ebcf9527dcaad95981e9b975fadf85ef6311abd9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 7 Sep 2021 02:44:38 +0000 Subject: [PATCH 333/548] refactor sync-server into a lib crate with a binary --- sync-server/src/api/add_version.rs | 31 ++++++------ sync-server/src/api/get_child_version.rs | 24 ++++----- sync-server/src/api/mod.rs | 2 +- .../taskchampion-sync-server.rs} | 49 +++++-------------- sync-server/src/lib.rs | 37 ++++++++++++++ sync-server/src/storage/inmemory.rs | 5 +- sync-server/src/storage/mod.rs | 25 +++++----- sync-server/src/storage/sqlite.rs | 2 +- 8 files changed, 93 insertions(+), 82 deletions(-) rename sync-server/src/{main.rs => bin/taskchampion-sync-server.rs} (52%) create mode 100644 sync-server/src/lib.rs diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 60db1f385..9804258a6 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -77,9 +77,8 @@ pub(crate) async fn service( #[cfg(test)] mod test { - use crate::api::ServerState; - use crate::app_scope; use crate::storage::{InMemoryStorage, Storage}; + use crate::Server; use actix_web::{http::StatusCode, test, App}; use uuid::Uuid; @@ -88,16 +87,16 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // set up the storage contents.. { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::nil()).unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -125,16 +124,16 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // set up the storage contents.. { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, version_id).unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -159,9 +158,9 @@ mod test { async fn test_bad_content_type() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -178,9 +177,9 @@ mod test { async fn test_empty_body() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index a38113803..f5d67184e 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -45,8 +45,8 @@ pub(crate) async fn service( #[cfg(test)] mod test { use crate::api::ServerState; - use crate::app_scope; use crate::storage::{InMemoryStorage, Storage}; + use crate::Server; use actix_web::{http::StatusCode, test, App}; use uuid::Uuid; @@ -55,18 +55,18 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // set up the storage contents.. { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap(); txn.add_version(client_key, version_id, parent_version_id, b"abcd".to_vec()) .unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() @@ -97,9 +97,9 @@ mod test { async fn test_client_not_found() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() @@ -116,15 +116,15 @@ mod test { async fn test_version_not_found() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // create the client, but not the version { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index 26aa8eba0..917dda83e 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -20,7 +20,7 @@ pub(crate) const CLIENT_KEY_HEADER: &str = "X-Client-Key"; pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; /// The type containing a reference to the Storage object in the Actix state. -pub(crate) type ServerState = Arc>; +pub(crate) type ServerState = Arc; pub(crate) fn api_scope() -> Scope { web::scope("") diff --git a/sync-server/src/main.rs b/sync-server/src/bin/taskchampion-sync-server.rs similarity index 52% rename from sync-server/src/main.rs rename to sync-server/src/bin/taskchampion-sync-server.rs index 394031738..88f0bb180 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/bin/taskchampion-sync-server.rs @@ -1,29 +1,9 @@ #![deny(clippy::all)] -use crate::storage::{SqliteStorage, Storage}; -use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder, Scope}; -use api::{api_scope, ServerState}; +use actix_web::{middleware::Logger, App, HttpServer}; use clap::Arg; - -mod api; -mod server; -mod storage; - -// TODO: use hawk to sign requests - -#[get("/")] -async fn index() -> impl Responder { - format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION")) -} - -/// Return a scope defining the URL rules for this server, with access to -/// the given ServerState. -pub(crate) fn app_scope(server_state: ServerState) -> Scope { - web::scope("") - .data(server_state) - .service(index) - .service(api_scope()) -} +use taskchampion_sync_server::storage::SqliteStorage; +use taskchampion_sync_server::Server; #[actix_web::main] async fn main() -> anyhow::Result<()> { @@ -56,33 +36,26 @@ async fn main() -> anyhow::Result<()> { let data_dir = matches.value_of("data-dir").unwrap(); let port = matches.value_of("port").unwrap(); - let server_box: Box = Box::new(SqliteStorage::new(data_dir)?); - let server_state = ServerState::new(server_box); + let server = Server::new(Box::new(SqliteStorage::new(data_dir)?)); log::warn!("Serving on port {}", port); - HttpServer::new(move || { - App::new() - .wrap(Logger::default()) - .service(app_scope(server_state.clone())) - }) - .bind(format!("0.0.0.0:{}", port))? - .run() - .await?; + HttpServer::new(move || App::new().wrap(Logger::default()).service(server.service())) + .bind(format!("0.0.0.0:{}", port))? + .run() + .await?; Ok(()) } #[cfg(test)] mod test { use super::*; - use crate::api::ServerState; - use crate::storage::{InMemoryStorage, Storage}; use actix_web::{test, App}; + use taskchampion_sync_server::storage::{InMemoryStorage, Storage}; #[actix_rt::test] async fn test_index_get() { - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(Box::new(InMemoryStorage::new())); + let mut app = test::init_service(App::new().service(server.service())).await; let req = test::TestRequest::get().uri("/").to_request(); let resp = test::call_service(&mut app, req).await; diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs new file mode 100644 index 000000000..c219f2e1b --- /dev/null +++ b/sync-server/src/lib.rs @@ -0,0 +1,37 @@ +#![deny(clippy::all)] + +mod api; +mod server; +pub mod storage; + +use crate::storage::Storage; +use actix_web::{get, web, Responder, Scope}; +use api::{api_scope, ServerState}; + +#[get("/")] +async fn index() -> impl Responder { + format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION")) +} + +/// A Server represents a sync server. +#[derive(Clone)] +pub struct Server { + storage: ServerState, +} + +impl Server { + /// Create a new sync server with the given storage implementation. + pub fn new(storage: Box) -> Self { + Self { + storage: storage.into(), + } + } + + /// Get an Actix-web service for this server. + pub fn service(&self) -> Scope { + web::scope("") + .data(self.storage.clone()) + .service(index) + .service(api_scope()) + } +} diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs index e37abb07a..143b2e760 100644 --- a/sync-server/src/storage/inmemory.rs +++ b/sync-server/src/storage/inmemory.rs @@ -10,10 +10,11 @@ struct Inner { versions: HashMap<(Uuid, Uuid), Version>, } -pub(crate) struct InMemoryStorage(Mutex); +pub struct InMemoryStorage(Mutex); impl InMemoryStorage { - pub(crate) fn new() -> Self { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { Self(Mutex::new(Inner { clients: HashMap::new(), versions: HashMap::new(), diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index f9a2fc699..30b961fa1 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -1,27 +1,28 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; -#[cfg(test)] +#[cfg(debug_assertions)] mod inmemory; -#[cfg(test)] -pub(crate) use inmemory::InMemoryStorage; + +#[cfg(debug_assertions)] +pub use inmemory::InMemoryStorage; mod sqlite; -pub(crate) use self::sqlite::SqliteStorage; +pub use self::sqlite::SqliteStorage; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub(crate) struct Client { - pub(crate) latest_version_id: Uuid, +pub struct Client { + pub latest_version_id: Uuid, } #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub(crate) struct Version { - pub(crate) version_id: Uuid, - pub(crate) parent_version_id: Uuid, - pub(crate) history_segment: Vec, +pub struct Version { + pub version_id: Uuid, + pub parent_version_id: Uuid, + pub history_segment: Vec, } -pub(crate) trait StorageTxn { +pub trait StorageTxn { /// Get information about the given client fn get_client(&mut self, client_key: Uuid) -> anyhow::Result>; @@ -58,7 +59,7 @@ pub(crate) trait StorageTxn { /// A trait for objects able to act as storage. Most of the interesting behavior is in the /// [`crate::storage::StorageTxn`] trait. -pub(crate) trait Storage: Send + Sync { +pub trait Storage: Send + Sync { /// Begin a transaction fn txn<'a>(&'a self) -> anyhow::Result>; } diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index 34cdd05b7..ba56cb0a6 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -49,7 +49,7 @@ impl ToSql for Client { } /// An on-disk storage backend which uses SQLite -pub(crate) struct SqliteStorage { +pub struct SqliteStorage { db_file: std::path::PathBuf, } From fb39c90592db1254d89a02f8d208f6d062e902d7 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 7 Sep 2021 03:08:35 +0000 Subject: [PATCH 334/548] Add an integration test combining replica and server This confirms that task changes are replicated via the server. --- Cargo.lock | 136 +++++++---------------- Cargo.toml | 3 +- README.md | 3 +- cli/Cargo.toml | 1 + replica-server-tests/Cargo.toml | 18 +++ replica-server-tests/src/lib.rs | 1 + replica-server-tests/tests/cross-sync.rs | 84 ++++++++++++++ sync-server/Cargo.toml | 3 +- 8 files changed, 151 insertions(+), 98 deletions(-) create mode 100644 replica-server-tests/Cargo.toml create mode 100644 replica-server-tests/src/lib.rs create mode 100644 replica-server-tests/tests/cross-sync.rs diff --git a/Cargo.lock b/Cargo.lock index 28bed3b3e..0dec00598 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "actix-codec" version = "0.3.0" @@ -12,7 +14,7 @@ dependencies = [ "futures-sink", "log", "pin-project 0.4.28", - "tokio 0.2.25", + "tokio", "tokio-util", ] @@ -23,7 +25,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" dependencies = [ "actix-codec", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "actix-utils", "derive_more", @@ -43,7 +45,7 @@ checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" dependencies = [ "actix-codec", "actix-connect", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "actix-threadpool", "actix-utils", @@ -92,16 +94,6 @@ dependencies = [ "syn", ] -[[package]] -name = "actix-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f86cd6857c135e6e9fe57b1619a88d1f94a7df34c00e11fe13e64fd3438837" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "actix-router" version = "0.2.7" @@ -121,24 +113,13 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" dependencies = [ - "actix-macros 0.1.3", + "actix-macros", "actix-threadpool", "copyless", "futures-channel", "futures-util", "smallvec", - "tokio 0.2.25", -] - -[[package]] -name = "actix-rt" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d7cd957c9ed92288a7c3c96af81fa5291f65247a76a34dac7b6af74e52ba0" -dependencies = [ - "actix-macros 0.2.1", - "futures-core", - "tokio 1.6.2", + "tokio", ] [[package]] @@ -148,13 +129,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" dependencies = [ "actix-codec", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "actix-utils", "futures-channel", "futures-util", "log", - "mio 0.6.23", + "mio", "mio-uds", "num_cpus", "slab", @@ -177,8 +158,8 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" dependencies = [ - "actix-macros 0.1.3", - "actix-rt 1.1.1", + "actix-macros", + "actix-rt", "actix-server", "actix-service", "log", @@ -219,7 +200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" dependencies = [ "actix-codec", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "bitflags", "bytes 0.5.6", @@ -240,9 +221,9 @@ checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" dependencies = [ "actix-codec", "actix-http", - "actix-macros 0.1.3", + "actix-macros", "actix-router", - "actix-rt 1.1.1", + "actix-rt", "actix-server", "actix-service", "actix-testing", @@ -400,7 +381,7 @@ checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691" dependencies = [ "actix-codec", "actix-http", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "base64 0.13.0", "bytes 0.5.6", @@ -1252,7 +1233,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 0.2.25", + "tokio", "tokio-util", "tracing", "tracing-futures", @@ -1430,7 +1411,7 @@ dependencies = [ "itoa", "pin-project 1.0.7", "socket2", - "tokio 0.2.25", + "tokio", "tower-service", "tracing", "want", @@ -1741,7 +1722,7 @@ dependencies = [ "serde_json", "shlex", "tempfile", - "tokio 0.2.25", + "tokio", "toml", "warp", ] @@ -1791,25 +1772,12 @@ dependencies = [ "kernel32-sys", "libc", "log", - "miow 0.2.2", + "miow", "net2", "slab", "winapi 0.2.8", ] -[[package]] -name = "mio" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" -dependencies = [ - "libc", - "log", - "miow 0.3.7", - "ntapi", - "winapi 0.3.9", -] - [[package]] name = "mio-extras" version = "2.0.6" @@ -1818,7 +1786,7 @@ checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", "log", - "mio 0.6.23", + "mio", "slab", ] @@ -1830,7 +1798,7 @@ checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" dependencies = [ "iovec", "libc", - "mio 0.6.23", + "mio", ] [[package]] @@ -1845,15 +1813,6 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "net2" version = "0.2.37" @@ -1913,21 +1872,12 @@ dependencies = [ "fsevent-sys", "inotify", "libc", - "mio 0.6.23", + "mio", "mio-extras", "walkdir", "winapi 0.3.9", ] -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "num-integer" version = "0.1.44" @@ -2470,6 +2420,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "replica-server-tests" +version = "0.3.0" +dependencies = [ + "actix-rt", + "actix-web", + "anyhow", + "taskchampion", + "taskchampion-sync-server", + "tempfile", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -3002,7 +2964,7 @@ dependencies = [ name = "taskchampion-sync-server" version = "0.3.0" dependencies = [ - "actix-rt 2.2.0", + "actix-rt", "actix-web", "anyhow", "clap", @@ -3209,7 +3171,7 @@ dependencies = [ "lazy_static", "libc", "memchr", - "mio 0.6.23", + "mio", "mio-uds", "pin-project-lite 0.1.12", "signal-hook-registry", @@ -3218,22 +3180,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "tokio" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aea337f72e96efe29acc234d803a5981cd9a2b6ed21655cd7fc21cfe021e8ec7" -dependencies = [ - "autocfg", - "libc", - "mio 0.7.13", - "once_cell", - "parking_lot", - "pin-project-lite 0.2.6", - "signal-hook-registry", - "winapi 0.3.9", -] - [[package]] name = "tokio-macros" version = "0.2.6" @@ -3254,7 +3200,7 @@ dependencies = [ "futures-util", "log", "pin-project 0.4.28", - "tokio 0.2.25", + "tokio", "tungstenite", ] @@ -3269,7 +3215,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite 0.1.12", - "tokio 0.2.25", + "tokio", ] [[package]] @@ -3351,7 +3297,7 @@ dependencies = [ "rand 0.7.3", "smallvec", "thiserror", - "tokio 0.2.25", + "tokio", "url", ] @@ -3370,7 +3316,7 @@ dependencies = [ "resolv-conf", "smallvec", "thiserror", - "tokio 0.2.25", + "tokio", "trust-dns-proto", ] @@ -3579,7 +3525,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded 0.6.1", - "tokio 0.2.25", + "tokio", "tokio-tungstenite", "tower-service", "tracing", diff --git a/Cargo.toml b/Cargo.toml index cd6520de1..eef6d1ce7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,6 @@ members = [ "taskchampion", "cli", - "sync-server" + "sync-server", + "replica-server-tests" ] diff --git a/README.md b/README.md index 1b4e1d664..e937b17d2 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,12 @@ Assuming that continues, it is unlikely that TaskChampion will ever be recommend ## Structure -There are three crates here: +There are four crates here: * [taskchampion](./taskchampion) - the core of the tool * [taskchampion-cli](./cli) - the command-line binary * [taskchampion-sync-server](./sync-server) - the server against which `task sync` operates + * [replica-server-tests](./replica-server-tests) - the server against which `task sync` operates ## Documentation Generation diff --git a/cli/Cargo.toml b/cli/Cargo.toml index feada5597..351e1ecfc 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -3,6 +3,7 @@ authors = ["Dustin J. Mitchell "] edition = "2018" name = "taskchampion-cli" version = "0.3.0" +publish = false build = "build.rs" diff --git a/replica-server-tests/Cargo.toml b/replica-server-tests/Cargo.toml new file mode 100644 index 000000000..c702e4164 --- /dev/null +++ b/replica-server-tests/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "replica-server-tests" +version = "0.3.0" +authors = ["Dustin J. Mitchell "] +edition = "2018" +publish = false + +[dependencies.taskchampion-sync-server] +path = "../sync-server" + +[dependencies.taskchampion] +path = "../taskchampion" + +[dev-dependencies] +anyhow = "1.0" +actix-web = "^3.3.2" +actix-rt = "^1.1.1" +tempfile = "3" diff --git a/replica-server-tests/src/lib.rs b/replica-server-tests/src/lib.rs new file mode 100644 index 000000000..e783558fc --- /dev/null +++ b/replica-server-tests/src/lib.rs @@ -0,0 +1 @@ +// test-only crate diff --git a/replica-server-tests/tests/cross-sync.rs b/replica-server-tests/tests/cross-sync.rs new file mode 100644 index 000000000..a828d0f0d --- /dev/null +++ b/replica-server-tests/tests/cross-sync.rs @@ -0,0 +1,84 @@ +use actix_web::{App, HttpServer}; +use taskchampion::{Replica, ServerConfig, Status, StorageConfig, Uuid}; +use taskchampion_sync_server::{storage::InMemoryStorage, Server}; + +#[actix_rt::test] +async fn cross_sync() -> anyhow::Result<()> { + let server = Server::new(Box::new(InMemoryStorage::new())); + let httpserver = + HttpServer::new(move || App::new().service(server.service())).bind("0.0.0.0:0")?; + + // bind was to :0, so the kernel will have selected an unused port + let port = httpserver.addrs()[0].port(); + + httpserver.run(); + + // set up two replicas, and demonstrate replication between them + let mut rep1 = Replica::new(StorageConfig::InMemory.into_storage()?); + let mut rep2 = Replica::new(StorageConfig::InMemory.into_storage()?); + + let client_key = Uuid::new_v4(); + let encryption_secret = b"abc123".to_vec(); + let make_server = || { + ServerConfig::Remote { + origin: format!("http://127.0.0.1:{}", port), + client_key, + encryption_secret: encryption_secret.clone(), + } + .into_server() + }; + + let mut serv1 = make_server()?; + let mut serv2 = make_server()?; + + // add some tasks on rep1 + let t1 = rep1.new_task(Status::Pending, "test 1".into())?; + let t2 = rep1.new_task(Status::Pending, "test 2".into())?; + + // modify t1 + let mut t1 = t1.into_mut(&mut rep1); + t1.start()?; + let t1 = t1.into_immut(); + + rep1.sync(&mut serv1)?; + rep2.sync(&mut serv2)?; + + // those tasks should exist on rep2 now + let t12 = rep2 + .get_task(t1.get_uuid())? + .expect("expected task 1 on rep2"); + let t22 = rep2 + .get_task(t2.get_uuid())? + .expect("expected task 2 on rep2"); + + assert_eq!(t12.get_description(), "test 1"); + assert_eq!(t12.is_active(), true); + assert_eq!(t22.get_description(), "test 2"); + assert_eq!(t22.is_active(), false); + + // make non-conflicting changes on the two replicas + let mut t2 = t2.into_mut(&mut rep1); + t2.set_status(Status::Completed)?; + let t2 = t2.into_immut(); + + let mut t12 = t12.into_mut(&mut rep2); + t12.set_status(Status::Completed)?; + + // sync those changes back and forth + rep1.sync(&mut serv1)?; // rep1 -> server + rep2.sync(&mut serv2)?; // server -> rep2, rep2 -> server + rep1.sync(&mut serv1)?; // server -> rep1 + + let t1 = rep1 + .get_task(t1.get_uuid())? + .expect("expected task 1 on rep1"); + assert_eq!(t1.get_status(), Status::Completed); + + let t22 = rep2 + .get_task(t2.get_uuid())? + .expect("expected task 2 on rep2"); + assert_eq!(t22.get_status(), Status::Completed); + + // note that we just drop the server here.. + Ok(()) +} diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 6c66f6726..ef1442fad 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -3,6 +3,7 @@ name = "taskchampion-sync-server" version = "0.3.0" authors = ["Dustin J. Mitchell "] edition = "2018" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,5 +21,5 @@ env_logger = "^0.8.3" rusqlite = { version = "0.25", features = ["bundled"] } [dev-dependencies] -actix-rt = "^2.2.0" +actix-rt = "^1.1.1" tempfile = "3" From 757f923c66e1adc2400328acb6cdc9ff80a8409d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 13 Sep 2021 17:42:16 -0400 Subject: [PATCH 335/548] reword README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e937b17d2..a4582257e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ There are four crates here: * [taskchampion](./taskchampion) - the core of the tool * [taskchampion-cli](./cli) - the command-line binary * [taskchampion-sync-server](./sync-server) - the server against which `task sync` operates - * [replica-server-tests](./replica-server-tests) - the server against which `task sync` operates + * [replica-server-tests](./replica-server-tests) - integration tests covering both _taskchampion-cli_ and _taskchampion-sync-server_ ## Documentation Generation From 7d3aae4555220f48a9098ddf809ba16b7c12920e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 14 Sep 2021 09:26:44 -0400 Subject: [PATCH 336/548] allow publishing taskchampion-cli, to allow 'cargo install' --- cli/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 351e1ecfc..feada5597 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -3,7 +3,6 @@ authors = ["Dustin J. Mitchell "] edition = "2018" name = "taskchampion-cli" version = "0.3.0" -publish = false build = "build.rs" From 217f3bf28a5e48fb7d344a76a7d553901494f0db Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 15 Sep 2021 22:32:35 +0000 Subject: [PATCH 337/548] Add cache-control headers to API responses --- sync-server/src/api/get_child_version.rs | 1 - .../src/bin/taskchampion-sync-server.rs | 34 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index f5d67184e..d67cde8f2 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -44,7 +44,6 @@ pub(crate) async fn service( #[cfg(test)] mod test { - use crate::api::ServerState; use crate::storage::{InMemoryStorage, Storage}; use crate::Server; use actix_web::{http::StatusCode, test, App}; diff --git a/sync-server/src/bin/taskchampion-sync-server.rs b/sync-server/src/bin/taskchampion-sync-server.rs index 88f0bb180..30056351e 100644 --- a/sync-server/src/bin/taskchampion-sync-server.rs +++ b/sync-server/src/bin/taskchampion-sync-server.rs @@ -1,10 +1,21 @@ #![deny(clippy::all)] -use actix_web::{middleware::Logger, App, HttpServer}; +use actix_web::{middleware, middleware::Logger, App, HttpServer}; use clap::Arg; use taskchampion_sync_server::storage::SqliteStorage; use taskchampion_sync_server::Server; +// The `.wrap` method returns an opaque type, meaning that we can't easily return it from +// functions. So, we must apply these default headers when the app is created, which occurs both +// in `main` and in the tests. To check that those are both doing precisely the same thing, we use +// a macro. This is ugly, and will go away when actix-web is no longer the framework in use. +macro_rules! cache_control_headers { + ($wrapped:expr) => { + $wrapped + .wrap(middleware::DefaultHeaders::new().header("Cache-Control", "no-store, max-age=0")) + }; +} + #[actix_web::main] async fn main() -> anyhow::Result<()> { env_logger::init(); @@ -39,10 +50,14 @@ async fn main() -> anyhow::Result<()> { let server = Server::new(Box::new(SqliteStorage::new(data_dir)?)); log::warn!("Serving on port {}", port); - HttpServer::new(move || App::new().wrap(Logger::default()).service(server.service())) - .bind(format!("0.0.0.0:{}", port))? - .run() - .await?; + HttpServer::new(move || { + cache_control_headers!(App::new()) + .wrap(Logger::default()) + .service(server.service()) + }) + .bind(format!("0.0.0.0:{}", port))? + .run() + .await?; Ok(()) } @@ -50,15 +65,20 @@ async fn main() -> anyhow::Result<()> { mod test { use super::*; use actix_web::{test, App}; - use taskchampion_sync_server::storage::{InMemoryStorage, Storage}; + use taskchampion_sync_server::storage::InMemoryStorage; #[actix_rt::test] async fn test_index_get() { let server = Server::new(Box::new(InMemoryStorage::new())); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = cache_control_headers!(App::new()).service(server.service()); + let mut app = test::init_service(app).await; let req = test::TestRequest::get().uri("/").to_request(); let resp = test::call_service(&mut app, req).await; assert!(resp.status().is_success()); + assert_eq!( + resp.headers().get("Cache-Control").unwrap(), + &"no-store, max-age=0".to_string() + ) } } From b1b17310e65efd5a3e52870b33cfe57d18af06a9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 25 Sep 2021 23:16:16 +0000 Subject: [PATCH 338/548] fix typo --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 3617abd76..b60ab7a22 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,6 +1,6 @@ # Release process -1. Ensure the changelog is updated with everything from the `.changelogs` directory. `python3 ./script/changelog.py build` will output a Markdown snippet to include in `CHANGELOG.md` then `rm .changelog/*.txt` +1. Ensure the changelog is updated with everything from the `.changelogs` directory. `python3 ./scripts/changelog.py build` will output a Markdown snippet to include in `CHANGELOG.md` then `rm .changelog/*.txt` 1. Run `git pull upstream main` 1. Run `cargo test` 1. Run `cargo clean && cargo clippy` From fe7d421c218c0561abb83ff6888af317c6ce0b25 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 25 Sep 2021 23:42:16 +0000 Subject: [PATCH 339/548] v0.4.0 --- .changelogs/2021-09-10-sqlstore.md | 1 - CHANGELOG.md | 3 +++ Cargo.lock | 8 ++++---- cli/Cargo.toml | 2 +- replica-server-tests/Cargo.toml | 2 +- sync-server/Cargo.toml | 2 +- taskchampion/Cargo.toml | 2 +- 7 files changed, 11 insertions(+), 9 deletions(-) delete mode 100644 .changelogs/2021-09-10-sqlstore.md diff --git a/.changelogs/2021-09-10-sqlstore.md b/.changelogs/2021-09-10-sqlstore.md deleted file mode 100644 index b0151816c..000000000 --- a/.changelogs/2021-09-10-sqlstore.md +++ /dev/null @@ -1 +0,0 @@ -- Breaking: Removed the KV based storage backend in client and server, and replaced with SQLite ([Issue #131](https://github.com/taskchampion/taskchampion/issues/131), [PR #206](https://github.com/taskchampion/taskchampion/pull/206)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4b0775c..2c5baacae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Note: unreleased change log entries are kept in `.changelogs/` directory in repo root, and can be added with `./script/changelog.py add "Added thing for reason" +## 0.4.0 - 2021-09-25 +- Breaking: Removed the KV based storage backend in client and server, and replaced with SQLite ([Issue #131](https://github.com/taskchampion/taskchampion/issues/131), [PR #206](https://github.com/taskchampion/taskchampion/pull/206)) + ## 0.3.0 - 2021-01-11 - Flexible named reports - Updates to the TaskChampion crate API diff --git a/Cargo.lock b/Cargo.lock index 0dec00598..2fe3ee1b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2422,7 +2422,7 @@ dependencies = [ [[package]] name = "replica-server-tests" -version = "0.3.0" +version = "0.4.0" dependencies = [ "actix-rt", "actix-web", @@ -2911,7 +2911,7 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "taskchampion" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "chrono", @@ -2932,7 +2932,7 @@ dependencies = [ [[package]] name = "taskchampion-cli" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "assert_cmd", @@ -2962,7 +2962,7 @@ dependencies = [ [[package]] name = "taskchampion-sync-server" -version = "0.3.0" +version = "0.4.0" dependencies = [ "actix-rt", "actix-web", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index feada5597..19d8c777f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Dustin J. Mitchell "] edition = "2018" name = "taskchampion-cli" -version = "0.3.0" +version = "0.4.0" build = "build.rs" diff --git a/replica-server-tests/Cargo.toml b/replica-server-tests/Cargo.toml index c702e4164..494dc69c7 100644 --- a/replica-server-tests/Cargo.toml +++ b/replica-server-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "replica-server-tests" -version = "0.3.0" +version = "0.4.0" authors = ["Dustin J. Mitchell "] edition = "2018" publish = false diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index ef1442fad..7e35f1030 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskchampion-sync-server" -version = "0.3.0" +version = "0.4.0" authors = ["Dustin J. Mitchell "] edition = "2018" publish = false diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index fe51874c9..6d29b4dd5 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskchampion" -version = "0.3.0" +version = "0.4.0" authors = ["Dustin J. Mitchell "] description = "Personal task-tracking" homepage = "https://taskchampion.github.io/taskchampion/" From 67b2f261a162117e8011b24f1d2c39823f6c66f9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 25 Sep 2021 23:50:39 +0000 Subject: [PATCH 340/548] fix build-docs to create gh-pages branch --- build-docs.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build-docs.sh b/build-docs.sh index b03c22ab9..1bf8fb112 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -1,5 +1,7 @@ #! /bin/bash +set -x + REMOTE=origin set -e @@ -13,6 +15,8 @@ fi cargo build -p taskchampion-cli --features usage-docs --bin usage-docs # create a worktree of this repo, with the `gh-pages` branch checked out +git branch -D gh-pages 2>/dev/null || true +git checkout -b gh-pages $REMOTE/gh-pages if ! [ -d ./docs/tmp ]; then git worktree add docs/tmp gh-pages fi From e9dfcaaa445e4e4ecf93c656b058f53e240e26c0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 25 Sep 2021 23:53:08 +0000 Subject: [PATCH 341/548] more updates to build-docs.sh --- build-docs.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build-docs.sh b/build-docs.sh index 1bf8fb112..cc81cd47d 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -15,8 +15,7 @@ fi cargo build -p taskchampion-cli --features usage-docs --bin usage-docs # create a worktree of this repo, with the `gh-pages` branch checked out -git branch -D gh-pages 2>/dev/null || true -git checkout -b gh-pages $REMOTE/gh-pages +git branch -f gh-pages $REMOTE/gh-pages if ! [ -d ./docs/tmp ]; then git worktree add docs/tmp gh-pages fi From a122a28993e502f9fcccee8aa909aacf898437e0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 26 Sep 2021 09:07:15 -0400 Subject: [PATCH 342/548] Add derive feature for serde This feature had previously been indirectly required by a dependency, and this is no longer the case. --- taskchampion/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 6d29b4dd5..5c612ae86 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" [dependencies] uuid = { version = "^0.8.2", features = ["serde", "v4"] } -serde = "^1.0.125" +serde = { version = "^1.0.125", features = ["derive"] } serde_json = "^1.0" chrono = { version = "^0.4.10", features = ["serde"] } anyhow = "1.0" From c62eeb4fcbb0d2602329db10e8649091bbd20f96 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 26 Sep 2021 09:26:38 -0400 Subject: [PATCH 343/548] v0.4.1 --- CHANGELOG.md | 3 +++ Cargo.lock | 8 ++++---- cli/Cargo.toml | 2 +- replica-server-tests/Cargo.toml | 2 +- sync-server/Cargo.toml | 2 +- taskchampion/Cargo.toml | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c5baacae..91a692b5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Note: unreleased change log entries are kept in `.changelogs/` directory in repo root, and can be added with `./script/changelog.py add "Added thing for reason" +## 0.4.1 - 2021-09-24 +- Fix for the build process to include the serde feature "derive". 0.4.0 could not be published to crates.io due to this bug. + ## 0.4.0 - 2021-09-25 - Breaking: Removed the KV based storage backend in client and server, and replaced with SQLite ([Issue #131](https://github.com/taskchampion/taskchampion/issues/131), [PR #206](https://github.com/taskchampion/taskchampion/pull/206)) diff --git a/Cargo.lock b/Cargo.lock index 2fe3ee1b6..3c454db59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2422,7 +2422,7 @@ dependencies = [ [[package]] name = "replica-server-tests" -version = "0.4.0" +version = "0.4.1" dependencies = [ "actix-rt", "actix-web", @@ -2911,7 +2911,7 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "taskchampion" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "chrono", @@ -2932,7 +2932,7 @@ dependencies = [ [[package]] name = "taskchampion-cli" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "assert_cmd", @@ -2962,7 +2962,7 @@ dependencies = [ [[package]] name = "taskchampion-sync-server" -version = "0.4.0" +version = "0.4.1" dependencies = [ "actix-rt", "actix-web", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 19d8c777f..1bd72c7b6 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Dustin J. Mitchell "] edition = "2018" name = "taskchampion-cli" -version = "0.4.0" +version = "0.4.1" build = "build.rs" diff --git a/replica-server-tests/Cargo.toml b/replica-server-tests/Cargo.toml index 494dc69c7..0287c8d74 100644 --- a/replica-server-tests/Cargo.toml +++ b/replica-server-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "replica-server-tests" -version = "0.4.0" +version = "0.4.1" authors = ["Dustin J. Mitchell "] edition = "2018" publish = false diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 7e35f1030..b26a738d1 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskchampion-sync-server" -version = "0.4.0" +version = "0.4.1" authors = ["Dustin J. Mitchell "] edition = "2018" publish = false diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 5c612ae86..63ddd142f 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskchampion" -version = "0.4.0" +version = "0.4.1" authors = ["Dustin J. Mitchell "] description = "Personal task-tracking" homepage = "https://taskchampion.github.io/taskchampion/" From bfb732947c4bf89d5a34f03db8d17d2fd7a198f9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 26 Sep 2021 09:33:37 -0400 Subject: [PATCH 344/548] more build-docs fixes --- build-docs.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build-docs.sh b/build-docs.sh index cc81cd47d..82863b556 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -17,13 +17,14 @@ cargo build -p taskchampion-cli --features usage-docs --bin usage-docs # create a worktree of this repo, with the `gh-pages` branch checked out git branch -f gh-pages $REMOTE/gh-pages if ! [ -d ./docs/tmp ]; then - git worktree add docs/tmp gh-pages + git worktree add -f docs/tmp gh-pages fi # update the wortree (cd docs/tmp && git pull $REMOTE gh-pages) # remove all files in the worktree and regenerate the book there +git worktree rm docs/tmp rm -rf docs/tmp/* mdbook build docs cp -rp docs/book/* docs/tmp From 7881b2993cbaf65a04a8e69b81c300c8893cf08e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 26 Sep 2021 09:34:35 -0400 Subject: [PATCH 345/548] update docs --- build-docs.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build-docs.sh b/build-docs.sh index 82863b556..f693349c7 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -24,9 +24,10 @@ fi (cd docs/tmp && git pull $REMOTE gh-pages) # remove all files in the worktree and regenerate the book there -git worktree rm docs/tmp +git worktree remove -f docs/tmp rm -rf docs/tmp/* mdbook build docs +mkdir docs/tmp cp -rp docs/book/* docs/tmp # add everything in the worktree, commit, and push From 255cf29d4f21973816938e6eb8c47d903be94fc4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 26 Sep 2021 13:59:04 -0400 Subject: [PATCH 346/548] Update docs * improve linking * parallel construction for storage and servers * clarify rationale for Task/TaskMut --- taskchampion/src/lib.rs | 12 +++++++++++- taskchampion/src/replica.rs | 5 ++++- taskchampion/src/task/mod.rs | 2 +- taskchampion/src/task/tag.rs | 3 ++- taskchampion/src/task/task.rs | 26 ++++++++++++++++++++------ 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 665fe3a64..df72bdb22 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -3,16 +3,26 @@ This crate implements the core of TaskChampion, the [replica](crate::Replica). +Users of this crate can manipulate a task database using this API, including synchronizing that task database with others via a synchronization server. + +Example uses of this crate: + * user interfaces for task management, such as mobile apps, web apps, or command-line interfaces + * integrations for task management, such as synchronization with ticket-tracking systems or + request forms. + # Replica A TaskChampion replica is a local copy of a user's task data. As the name suggests, several replicas of the same data can exist (such as on a user's laptop and on their phone) and can synchronize with one another. -Replicas are accessed using the [`Replica`](crate::replica) type. +Replicas are accessed using the [`Replica`](crate::Replica) type. # Task Storage +Replicas access the task database via a [storage object](crate::storage::Storage). +Create a storage object with [`StorageConfig`](crate::storage::StorageConfig). + The [`storage`](crate::storage) module supports pluggable storage for a replica's data. An implementation is provided, but users of this crate can provide their own implementation as well. diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 361476951..46d8c3c6b 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -16,7 +16,10 @@ use uuid::Uuid; /// ## Tasks /// /// Tasks are uniquely identified by UUIDs. -/// Most task modifications are performed via the [`crate::Task`] and [`crate::TaskMut`] types. +/// Most task modifications are performed via the [`Task`](crate::Task) and +/// [`TaskMut`](crate::TaskMut) types. Use of two types for tasks allows easy +/// read-only manipulation of lots of tasks, with exclusive access required only +/// for modifications. /// /// ## Working Set /// diff --git a/taskchampion/src/task/mod.rs b/taskchampion/src/task/mod.rs index ecb755c29..bd0808015 100644 --- a/taskchampion/src/task/mod.rs +++ b/taskchampion/src/task/mod.rs @@ -10,7 +10,7 @@ mod task; pub use annotation::Annotation; pub use priority::Priority; pub use status::Status; -pub use tag::{Tag, INVALID_TAG_CHARACTERS}; +pub use tag::Tag; pub use task::{Task, TaskMut}; pub type Timestamp = DateTime; diff --git a/taskchampion/src/task/tag.rs b/taskchampion/src/task/tag.rs index d3a4842e7..8c6fb4254 100644 --- a/taskchampion/src/task/tag.rs +++ b/taskchampion/src/task/tag.rs @@ -5,7 +5,7 @@ use std::str::FromStr; /// A Tag is a descriptor for a task, that is either present or absent, and can be used for /// filtering. Tags composed of all uppercase letters are reserved for synthetic tags. /// -/// Valid tags must not contain whitespace or any of the characters in [`INVALID_TAG_CHARACTERS`]. +/// Valid tags must not contain whitespace or any of the characters in `+-*/(<>^! %=~`. /// The first characters additionally cannot be a digit, and subsequent characters cannot be `:`. /// This definition is based on [that of /// TaskWarrior](https://github.com/GothenburgBitFactory/taskwarrior/blob/663c6575ceca5bd0135ae884879339dac89d3142/src/Lexer.cpp#L146-L164). @@ -19,6 +19,7 @@ pub(super) enum TagInner { Synthetic(SyntheticTag), } +// see doc comment for Tag, above pub const INVALID_TAG_CHARACTERS: &str = "+-*/(<>^! %=~"; impl Tag { diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 40b2d6b0f..27824b860 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -8,6 +8,16 @@ use std::convert::AsRef; use std::convert::TryInto; use uuid::Uuid; +/* The Task and TaskMut classes wrap the underlying [`TaskMap`], which is a simple key/value map. + * They provide semantic meaning to that TaskMap according to the TaskChampion data model. For + * example, [`get_status`](Task::get_status) and [`set_status`](TaskMut::set_status) translate from + * strings in the TaskMap to [`Status`]. + * + * The same approach applies for more complex data such as dependencies or annotations. Users of + * this API should only need the [`get_taskmap`](Task::get_taskmap) method for debugging purposes, + * and should never need to make changes to the TaskMap directly. + */ + /// A task, as publicly exposed by this crate. /// /// Note that Task objects represent a snapshot of the task at a moment in time, and are not @@ -15,16 +25,21 @@ use uuid::Uuid; /// but a Task that is cached for more than a few seconds may cause the user to see stale /// data. Fetch, use, and drop Tasks quickly. /// -/// This struct contains only getters for various values on the task. The `into_mut` method returns -/// a TaskMut which can be used to modify the task. +/// This struct contains only getters for various values on the task. The +/// [`into_mut`](Task::into_mut) method +/// returns a TaskMut which can be used to modify the task. #[derive(Debug, Clone, PartialEq)] pub struct Task { uuid: Uuid, taskmap: TaskMap, } -/// A mutable task, with setter methods. Most methods are simple setters and not further -/// described. Calling a setter will update the Replica, as well as the included Task. +/// A mutable task, with setter methods. +/// +/// Most methods are simple setters and not further described. Calling a setter will update the +/// referenced Replica, as well as the included Task, immediately. +/// +/// The [`Task`] methods are available on [`TaskMut`] via [`Deref`](std::ops::Deref). pub struct TaskMut<'r> { task: Task, replica: &'r mut Replica, @@ -150,8 +165,7 @@ impl Task { } impl<'r> TaskMut<'r> { - /// Get the immutable version of this object. Note that TaskMut [`std::ops::Deref`]s to - /// [`crate::task::Task`], so all of that struct's getter methods can be used on TaskMut. + /// Get the immutable version of this object, ending the exclusive reference to the Replica. pub fn into_immut(self) -> Task { self.task } From a76d7580ce045657d9d536bb1750f5a42805131e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 26 Sep 2021 13:59:58 -0400 Subject: [PATCH 347/548] cargo fmt --- cli/src/argparse/args/time.rs | 36 ++++++++++++++++++++---------- taskchampion/src/storage/sqlite.rs | 13 +++++++---- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/cli/src/argparse/args/time.rs b/cli/src/argparse/args/time.rs index 47a82e1fc..5d81b7abc 100644 --- a/cli/src/argparse/args/time.rs +++ b/cli/src/argparse/args/time.rs @@ -160,12 +160,24 @@ fn named_date( "today" => Ok((remaining, local_today)), "tomorrow" => Ok((remaining, local_today + Duration::days(1))), // TODO: lots more! - "eod" => Ok((remaining,local_today + Duration::days(1))), - "sod" => Ok((remaining,local_today)), - "eow" => Ok((remaining,local_today + Duration::days((6-day_index).into()))), - "eoww" => Ok((remaining,local_today + Duration::days((5-day_index).into()))), - "sow" => Ok((remaining,local_today + Duration::days((6-day_index).into()))), - "soww" => Ok((remaining,local_today + Duration::days((7-day_index).into()))), + "eod" => Ok((remaining, local_today + Duration::days(1))), + "sod" => Ok((remaining, local_today)), + "eow" => Ok(( + remaining, + local_today + Duration::days((6 - day_index).into()), + )), + "eoww" => Ok(( + remaining, + local_today + Duration::days((5 - day_index).into()), + )), + "sow" => Ok(( + remaining, + local_today + Duration::days((6 - day_index).into()), + )), + "soww" => Ok(( + remaining, + local_today + Duration::days((7 - day_index).into()), + )), _ => Err(Err::Error(Error::new(input, ErrorKind::Tag))), } .map(|(rem, dt)| (rem, dt.and_hms(0, 0, 0).with_timezone(&Utc))) @@ -308,12 +320,12 @@ mod test { #[case::today_from_evening(ldt(2021, 3, 1, 21, 30, 30), "today", ld(2021, 3, 1))] #[case::tomorrow(ld(2021, 3, 1), "tomorrow", ld(2021, 3, 2))] #[case::tomorow_from_evening(ldt(2021, 3, 1, 21, 30, 30), "tomorrow", ld(2021, 3, 2))] - #[case::end_of_week(ld(2021,8,25,), "eow", ld(2021,8,29))] - #[case::end_of_work_week(ld(2021,8,25), "eoww", ld(2021,8,28))] - #[case::start_of_week(ld(2021,8,25), "sow", ld(2021,8,29))] - #[case::start_of_work_week(ld(2021,8,25), "soww", ld(2021,8,30))] - #[case::end_of_today(ld(2021,8,25), "eod", ld(2021,8,26))] - #[case::start_of_today(ld(2021,8,25), "sod", ld(2021,8,25))] + #[case::end_of_week(ld(2021, 8, 25,), "eow", ld(2021, 8, 29))] + #[case::end_of_work_week(ld(2021, 8, 25), "eoww", ld(2021, 8, 28))] + #[case::start_of_week(ld(2021, 8, 25), "sow", ld(2021, 8, 29))] + #[case::start_of_work_week(ld(2021, 8, 25), "soww", ld(2021, 8, 30))] + #[case::end_of_today(ld(2021, 8, 25), "eod", ld(2021, 8, 26))] + #[case::start_of_today(ld(2021, 8, 25), "sod", ld(2021, 8, 25))] fn test_local_timestamp( #[case] now: Box DateTime>, #[values(*IST, *UTC_FO, *HST)] tz: FixedOffset, diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 0e56479fe..4cd1347ed 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -113,9 +113,11 @@ impl<'t> Txn<'t> { fn get_next_working_set_number(&self) -> anyhow::Result { let t = self.get_txn()?; let next_id: Option = t - .query_row("SELECT COALESCE(MAX(id), 0) + 1 FROM working_set", [], |r| { - r.get(0) - }) + .query_row( + "SELECT COALESCE(MAX(id), 0) + 1 FROM working_set", + [], + |r| r.get(0), + ) .optional() .context("Getting highest working set ID")?; @@ -290,7 +292,10 @@ impl<'t> StorageTxn for Txn<'t> { let rows: Vec> = rows.collect(); let mut res = Vec::with_capacity(rows.len()); - for _ in 0..self.get_next_working_set_number().context("Getting working set number")? { + for _ in 0..self + .get_next_working_set_number() + .context("Getting working set number")? + { res.push(None); } for r in rows { From a95860b0d10878d6cb08a3d7f03388cd5e077cee Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 29 Sep 2021 12:24:02 +0000 Subject: [PATCH 348/548] use actions/cache@v2 --- .github/workflows/checks.yml | 8 ++++---- .github/workflows/publish-docs.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 3c96dafb7..bad2acdce 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -16,13 +16,13 @@ jobs: - uses: actions/checkout@v1 - name: Cache cargo registry - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} @@ -60,13 +60,13 @@ jobs: mdbook-version: '0.4.10' - name: Cache cargo registry - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 8bb43f89c..83dceb76c 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -19,13 +19,13 @@ jobs: mdbook-version: '0.4.10' - name: Cache cargo registry - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 78a0e183e..05ccd9ad3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,13 +27,13 @@ jobs: - uses: actions/checkout@v1 - name: Cache cargo registry - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cargo/registry key: ${{ runner.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: target key: ${{ runner.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} From a14366012449a57ea451e73b330d1e5b1e588da5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 2 Oct 2021 01:01:28 +0000 Subject: [PATCH 349/548] Switch to pretty_assertions --- Cargo.lock | 52 ++++++++++++++++++- cli/Cargo.toml | 1 + cli/src/argparse/args/arg_matching.rs | 1 + cli/src/argparse/args/colon.rs | 1 + cli/src/argparse/args/idlist.rs | 1 + cli/src/argparse/args/misc.rs | 1 + cli/src/argparse/args/tags.rs | 1 + cli/src/argparse/args/time.rs | 37 ++++++++----- cli/src/argparse/command.rs | 1 + cli/src/argparse/filter.rs | 1 + cli/src/argparse/modification.rs | 1 + cli/src/argparse/subcommand.rs | 1 + cli/src/errors.rs | 1 + cli/src/invocation/cmd/add.rs | 1 + cli/src/invocation/cmd/config.rs | 1 + cli/src/invocation/cmd/gc.rs | 1 + cli/src/invocation/cmd/info.rs | 1 + cli/src/invocation/cmd/modify.rs | 1 + cli/src/invocation/cmd/report.rs | 1 + cli/src/invocation/cmd/sync.rs | 1 + cli/src/invocation/filter.rs | 1 + cli/src/invocation/report.rs | 1 + cli/src/settings/report.rs | 1 + cli/src/settings/settings.rs | 1 + cli/src/settings/util.rs | 1 + replica-server-tests/Cargo.toml | 1 + replica-server-tests/tests/cross-sync.rs | 1 + sync-server/Cargo.toml | 1 + sync-server/src/api/add_version.rs | 1 + sync-server/src/api/get_child_version.rs | 1 + .../src/bin/taskchampion-sync-server.rs | 1 + sync-server/src/server.rs | 1 + sync-server/src/storage/sqlite.rs | 1 + taskchampion/Cargo.toml | 1 + taskchampion/src/replica.rs | 1 + taskchampion/src/server/local.rs | 1 + taskchampion/src/server/remote/crypto.rs | 1 + taskchampion/src/storage/inmemory.rs | 1 + taskchampion/src/storage/operation.rs | 1 + taskchampion/src/storage/sqlite.rs | 14 +++-- taskchampion/src/task/priority.rs | 1 + taskchampion/src/task/status.rs | 1 + taskchampion/src/task/tag.rs | 1 + taskchampion/src/task/task.rs | 1 + taskchampion/src/taskdb.rs | 1 + taskchampion/src/utils.rs | 1 + taskchampion/src/workingset.rs | 1 + 47 files changed, 130 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c454db59..4c0d541ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -313,6 +313,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "anyhow" version = "1.0.41" @@ -629,7 +638,7 @@ version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term", + "ansi_term 0.11.0", "atty", "bitflags", "strsim", @@ -748,6 +757,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "derive_more" version = "0.99.14" @@ -772,6 +791,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + [[package]] name = "difference" version = "2.0.0" @@ -1935,6 +1960,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -2152,6 +2186,18 @@ dependencies = [ "treeline", ] +[[package]] +name = "pretty_assertions" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" +dependencies = [ + "ansi_term 0.12.1", + "ctor", + "diff", + "output_vt100", +] + [[package]] name = "prettytable-rs" version = "0.8.0" @@ -2427,6 +2473,7 @@ dependencies = [ "actix-rt", "actix-web", "anyhow", + "pretty_assertions", "taskchampion", "taskchampion-sync-server", "tempfile", @@ -2916,6 +2963,7 @@ dependencies = [ "anyhow", "chrono", "log", + "pretty_assertions", "proptest", "rstest", "rusqlite", @@ -2948,6 +2996,7 @@ dependencies = [ "mdbook", "nom 6.1.2", "predicates", + "pretty_assertions", "prettytable-rs", "rstest", "serde_json", @@ -2971,6 +3020,7 @@ dependencies = [ "env_logger 0.8.4", "futures", "log", + "pretty_assertions", "rusqlite", "serde", "serde_json", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 1bd72c7b6..37fc20b19 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -43,6 +43,7 @@ assert_cmd = "^1.0.3" predicates = "^1.0.7" tempfile = "3" rstest = "0.10" +pretty_assertions = "1" [features] usage-docs = [ "mdbook", "serde_json" ] diff --git a/cli/src/argparse/args/arg_matching.rs b/cli/src/argparse/args/arg_matching.rs index f9582bbbb..a95e4ec1e 100644 --- a/cli/src/argparse/args/arg_matching.rs +++ b/cli/src/argparse/args/arg_matching.rs @@ -43,6 +43,7 @@ where mod test { use super::super::*; use super::*; + use pretty_assertions::assert_eq; #[test] fn test_arg_matching() { diff --git a/cli/src/argparse/args/colon.rs b/cli/src/argparse/args/colon.rs index 3fa17ce97..3fc94c734 100644 --- a/cli/src/argparse/args/colon.rs +++ b/cli/src/argparse/args/colon.rs @@ -51,6 +51,7 @@ pub(crate) fn wait_colon(input: &str) -> IResult<&str, Option>> { #[cfg(test)] mod test { use super::*; + use pretty_assertions::assert_eq; #[test] fn test_colon_prefix() { diff --git a/cli/src/argparse/args/idlist.rs b/cli/src/argparse/args/idlist.rs index f8c09ae04..095dd1cee 100644 --- a/cli/src/argparse/args/idlist.rs +++ b/cli/src/argparse/args/idlist.rs @@ -75,6 +75,7 @@ pub(crate) fn id_list(input: &str) -> IResult<&str, Vec> { #[cfg(test)] mod test { use super::*; + use pretty_assertions::assert_eq; #[test] fn test_id_list_single() { diff --git a/cli/src/argparse/args/misc.rs b/cli/src/argparse/args/misc.rs index 27d2a1315..5cb957f10 100644 --- a/cli/src/argparse/args/misc.rs +++ b/cli/src/argparse/args/misc.rs @@ -20,6 +20,7 @@ pub(crate) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, & mod test { use super::super::*; use super::*; + use pretty_assertions::assert_eq; #[test] fn test_arg_matching() { diff --git a/cli/src/argparse/args/tags.rs b/cli/src/argparse/args/tags.rs index c15dae6bf..1dbea5ee2 100644 --- a/cli/src/argparse/args/tags.rs +++ b/cli/src/argparse/args/tags.rs @@ -15,6 +15,7 @@ pub(crate) fn minus_tag(input: &str) -> IResult<&str, Tag> { #[cfg(test)] mod test { use super::*; + use pretty_assertions::assert_eq; #[test] fn test_plus_tag() { diff --git a/cli/src/argparse/args/time.rs b/cli/src/argparse/args/time.rs index 47a82e1fc..66c385f85 100644 --- a/cli/src/argparse/args/time.rs +++ b/cli/src/argparse/args/time.rs @@ -160,12 +160,24 @@ fn named_date( "today" => Ok((remaining, local_today)), "tomorrow" => Ok((remaining, local_today + Duration::days(1))), // TODO: lots more! - "eod" => Ok((remaining,local_today + Duration::days(1))), - "sod" => Ok((remaining,local_today)), - "eow" => Ok((remaining,local_today + Duration::days((6-day_index).into()))), - "eoww" => Ok((remaining,local_today + Duration::days((5-day_index).into()))), - "sow" => Ok((remaining,local_today + Duration::days((6-day_index).into()))), - "soww" => Ok((remaining,local_today + Duration::days((7-day_index).into()))), + "eod" => Ok((remaining, local_today + Duration::days(1))), + "sod" => Ok((remaining, local_today)), + "eow" => Ok(( + remaining, + local_today + Duration::days((6 - day_index).into()), + )), + "eoww" => Ok(( + remaining, + local_today + Duration::days((5 - day_index).into()), + )), + "sow" => Ok(( + remaining, + local_today + Duration::days((6 - day_index).into()), + )), + "soww" => Ok(( + remaining, + local_today + Duration::days((7 - day_index).into()), + )), _ => Err(Err::Error(Error::new(input, ErrorKind::Tag))), } .map(|(rem, dt)| (rem, dt.and_hms(0, 0, 0).with_timezone(&Utc))) @@ -227,6 +239,7 @@ pub(crate) fn timestamp( mod test { use super::*; use crate::argparse::NOW; + use pretty_assertions::assert_eq; use rstest::rstest; const M: i64 = 60; @@ -308,12 +321,12 @@ mod test { #[case::today_from_evening(ldt(2021, 3, 1, 21, 30, 30), "today", ld(2021, 3, 1))] #[case::tomorrow(ld(2021, 3, 1), "tomorrow", ld(2021, 3, 2))] #[case::tomorow_from_evening(ldt(2021, 3, 1, 21, 30, 30), "tomorrow", ld(2021, 3, 2))] - #[case::end_of_week(ld(2021,8,25,), "eow", ld(2021,8,29))] - #[case::end_of_work_week(ld(2021,8,25), "eoww", ld(2021,8,28))] - #[case::start_of_week(ld(2021,8,25), "sow", ld(2021,8,29))] - #[case::start_of_work_week(ld(2021,8,25), "soww", ld(2021,8,30))] - #[case::end_of_today(ld(2021,8,25), "eod", ld(2021,8,26))] - #[case::start_of_today(ld(2021,8,25), "sod", ld(2021,8,25))] + #[case::end_of_week(ld(2021, 8, 25,), "eow", ld(2021, 8, 29))] + #[case::end_of_work_week(ld(2021, 8, 25), "eoww", ld(2021, 8, 28))] + #[case::start_of_week(ld(2021, 8, 25), "sow", ld(2021, 8, 29))] + #[case::start_of_work_week(ld(2021, 8, 25), "soww", ld(2021, 8, 30))] + #[case::end_of_today(ld(2021, 8, 25), "eod", ld(2021, 8, 26))] + #[case::start_of_today(ld(2021, 8, 25), "sod", ld(2021, 8, 25))] fn test_local_timestamp( #[case] now: Box DateTime>, #[values(*IST, *UTC_FO, *HST)] tz: FixedOffset, diff --git a/cli/src/argparse/command.rs b/cli/src/argparse/command.rs index ad7cbd61a..712114f21 100644 --- a/cli/src/argparse/command.rs +++ b/cli/src/argparse/command.rs @@ -58,6 +58,7 @@ impl Command { #[cfg(test)] mod test { use super::*; + use pretty_assertions::assert_eq; // NOTE: most testing of specific subcommands is handled in `subcommand.rs`. diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index fa2746bb1..2895c03b7 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -201,6 +201,7 @@ impl Filter { #[cfg(test)] mod test { use super::*; + use pretty_assertions::assert_eq; #[test] fn test_empty_parse0() { diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index bd37db928..2005bd2c3 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -165,6 +165,7 @@ impl Modification { mod test { use super::*; use crate::argparse::NOW; + use pretty_assertions::assert_eq; #[test] fn test_empty() { diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 53d67556f..2efc52ea4 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -411,6 +411,7 @@ impl Sync { mod test { use super::*; use crate::argparse::Condition; + use pretty_assertions::assert_eq; const EMPTY: Vec<&str> = vec![]; diff --git a/cli/src/errors.rs b/cli/src/errors.rs index 16ac96285..6da512136 100644 --- a/cli/src/errors.rs +++ b/cli/src/errors.rs @@ -39,6 +39,7 @@ impl From for Error { mod test { use super::*; use anyhow::anyhow; + use pretty_assertions::assert_eq; #[test] fn test_exit_status() { diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index abee1bf4d..47df033bc 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -20,6 +20,7 @@ pub(crate) fn execute( mod test { use super::*; use crate::invocation::test::*; + use pretty_assertions::assert_eq; #[test] fn test_add() { diff --git a/cli/src/invocation/cmd/config.rs b/cli/src/invocation/cmd/config.rs index fc8aa6a3f..a3cc9b223 100644 --- a/cli/src/invocation/cmd/config.rs +++ b/cli/src/invocation/cmd/config.rs @@ -34,6 +34,7 @@ pub(crate) fn execute( mod test { use super::*; use crate::invocation::test::*; + use pretty_assertions::assert_eq; use std::fs; use tempfile::TempDir; diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs index 9b14b9fbb..f35eaed76 100644 --- a/cli/src/invocation/cmd/gc.rs +++ b/cli/src/invocation/cmd/gc.rs @@ -12,6 +12,7 @@ pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Result mod test { use super::*; use crate::invocation::test::*; + use pretty_assertions::assert_eq; #[test] fn test_gc() { diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index c77476a69..58bd46fba 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -50,6 +50,7 @@ pub(crate) fn execute( mod test { use super::*; use crate::invocation::test::*; + use taskchampion::Status; #[test] diff --git a/cli/src/invocation/cmd/modify.rs b/cli/src/invocation/cmd/modify.rs index 0fa803441..3bf7e5b41 100644 --- a/cli/src/invocation/cmd/modify.rs +++ b/cli/src/invocation/cmd/modify.rs @@ -71,6 +71,7 @@ mod test { use crate::argparse::DescriptionMod; use crate::invocation::test::test_replica; use crate::invocation::test::*; + use pretty_assertions::assert_eq; use taskchampion::Status; #[test] diff --git a/cli/src/invocation/cmd/report.rs b/cli/src/invocation/cmd/report.rs index ab079af28..9be4030a7 100644 --- a/cli/src/invocation/cmd/report.rs +++ b/cli/src/invocation/cmd/report.rs @@ -19,6 +19,7 @@ mod test { use super::*; use crate::argparse::Filter; use crate::invocation::test::*; + use taskchampion::Status; #[test] diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index ce213a5ba..b43b01362 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -15,6 +15,7 @@ pub(crate) fn execute( mod test { use super::*; use crate::invocation::test::*; + use pretty_assertions::assert_eq; use tempfile::TempDir; #[test] diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index 0d4add799..6e2378ae4 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -172,6 +172,7 @@ pub(super) fn filtered_tasks( mod test { use super::*; use crate::invocation::test::*; + use pretty_assertions::assert_eq; use taskchampion::Status; #[test] diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index d67dae4fb..db20b56ce 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -133,6 +133,7 @@ mod test { use crate::invocation::test::*; use crate::settings::Sort; use chrono::prelude::*; + use pretty_assertions::assert_eq; use std::convert::TryInto; use taskchampion::{Status, Uuid}; diff --git a/cli/src/settings/report.rs b/cli/src/settings/report.rs index 78650ffe3..88ca1a3cb 100644 --- a/cli/src/settings/report.rs +++ b/cli/src/settings/report.rs @@ -259,6 +259,7 @@ pub(crate) fn get_usage(u: &mut Usage) { #[cfg(test)] mod test { use super::*; + use pretty_assertions::assert_eq; use taskchampion::Status; use toml::toml; diff --git a/cli/src/settings/settings.rs b/cli/src/settings/settings.rs index e1809091f..7e94a637b 100644 --- a/cli/src/settings/settings.rs +++ b/cli/src/settings/settings.rs @@ -325,6 +325,7 @@ impl Default for Settings { #[cfg(test)] mod test { use super::*; + use pretty_assertions::assert_eq; use tempfile::TempDir; use toml::toml; diff --git a/cli/src/settings/util.rs b/cli/src/settings/util.rs index ea585bef2..85d2e1d52 100644 --- a/cli/src/settings/util.rs +++ b/cli/src/settings/util.rs @@ -17,6 +17,7 @@ pub(super) fn table_with_keys<'a>(cfg: &'a toml::Value, keys: &[&str]) -> Result #[cfg(test)] mod test { use super::*; + use pretty_assertions::assert_eq; use toml::toml; #[test] diff --git a/replica-server-tests/Cargo.toml b/replica-server-tests/Cargo.toml index 0287c8d74..1dfb537ce 100644 --- a/replica-server-tests/Cargo.toml +++ b/replica-server-tests/Cargo.toml @@ -16,3 +16,4 @@ anyhow = "1.0" actix-web = "^3.3.2" actix-rt = "^1.1.1" tempfile = "3" +pretty_assertions = "1" diff --git a/replica-server-tests/tests/cross-sync.rs b/replica-server-tests/tests/cross-sync.rs index a828d0f0d..67e02e10c 100644 --- a/replica-server-tests/tests/cross-sync.rs +++ b/replica-server-tests/tests/cross-sync.rs @@ -1,4 +1,5 @@ use actix_web::{App, HttpServer}; +use pretty_assertions::assert_eq; use taskchampion::{Replica, ServerConfig, Status, StorageConfig, Uuid}; use taskchampion_sync_server::{storage::InMemoryStorage, Server}; diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index b26a738d1..c0d26656e 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -23,3 +23,4 @@ rusqlite = { version = "0.25", features = ["bundled"] } [dev-dependencies] actix-rt = "^1.1.1" tempfile = "3" +pretty_assertions = "1" diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 9804258a6..ce9ba1104 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -80,6 +80,7 @@ mod test { use crate::storage::{InMemoryStorage, Storage}; use crate::Server; use actix_web::{http::StatusCode, test, App}; + use pretty_assertions::{assert_eq}; use uuid::Uuid; #[actix_rt::test] diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index d67cde8f2..2b0517145 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -47,6 +47,7 @@ mod test { use crate::storage::{InMemoryStorage, Storage}; use crate::Server; use actix_web::{http::StatusCode, test, App}; + use pretty_assertions::{assert_eq}; use uuid::Uuid; #[actix_rt::test] diff --git a/sync-server/src/bin/taskchampion-sync-server.rs b/sync-server/src/bin/taskchampion-sync-server.rs index 30056351e..aa03cbaf8 100644 --- a/sync-server/src/bin/taskchampion-sync-server.rs +++ b/sync-server/src/bin/taskchampion-sync-server.rs @@ -65,6 +65,7 @@ async fn main() -> anyhow::Result<()> { mod test { use super::*; use actix_web::{test, App}; + use pretty_assertions::{assert_eq}; use taskchampion_sync_server::storage::InMemoryStorage; #[actix_rt::test] diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 0fcfb4755..7711c77c0 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -81,6 +81,7 @@ pub(crate) fn add_version<'a>( mod test { use super::*; use crate::storage::{InMemoryStorage, Storage}; + use pretty_assertions::{assert_eq}; #[test] fn gcv_not_found() -> anyhow::Result<()> { diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index ba56cb0a6..fc4c2dc0f 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -203,6 +203,7 @@ impl StorageTxn for Txn { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; use tempfile::TempDir; #[test] diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 63ddd142f..168d4ba1f 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -28,3 +28,4 @@ strum_macros = "0.21" proptest = "^1.0.0" tempfile = "3" rstest = "0.10" +pretty_assertions = "1" diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 361476951..75b5137e4 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -146,6 +146,7 @@ impl Replica { mod tests { use super::*; use crate::task::Status; + use pretty_assertions::{assert_eq}; use uuid::Uuid; #[test] diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 657d60881..a9441ac2e 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -159,6 +159,7 @@ impl Server for LocalServer { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; use tempfile::TempDir; #[test] diff --git a/taskchampion/src/server/remote/crypto.rs b/taskchampion/src/server/remote/crypto.rs index a236ef7be..612a0eb28 100644 --- a/taskchampion/src/server/remote/crypto.rs +++ b/taskchampion/src/server/remote/crypto.rs @@ -78,6 +78,7 @@ impl AsRef<[u8]> for HistoryCiphertext { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; #[test] fn round_trip() { diff --git a/taskchampion/src/storage/inmemory.rs b/taskchampion/src/storage/inmemory.rs index e400d61bf..7aa081e57 100644 --- a/taskchampion/src/storage/inmemory.rs +++ b/taskchampion/src/storage/inmemory.rs @@ -166,6 +166,7 @@ impl Storage for InMemoryStorage { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; // (note: this module is heavily used in tests so most of its functionality is well-tested // elsewhere and not tested here) diff --git a/taskchampion/src/storage/operation.rs b/taskchampion/src/storage/operation.rs index ef52f2cb4..7bcf875eb 100644 --- a/taskchampion/src/storage/operation.rs +++ b/taskchampion/src/storage/operation.rs @@ -128,6 +128,7 @@ mod test { use crate::storage::InMemoryStorage; use crate::taskdb::TaskDb; use chrono::{Duration, Utc}; + use pretty_assertions::{assert_eq}; use proptest::prelude::*; // note that `tests/operation_transform_invariant.rs` tests the transform function quite diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 0e56479fe..759826e4a 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -113,9 +113,11 @@ impl<'t> Txn<'t> { fn get_next_working_set_number(&self) -> anyhow::Result { let t = self.get_txn()?; let next_id: Option = t - .query_row("SELECT COALESCE(MAX(id), 0) + 1 FROM working_set", [], |r| { - r.get(0) - }) + .query_row( + "SELECT COALESCE(MAX(id), 0) + 1 FROM working_set", + [], + |r| r.get(0), + ) .optional() .context("Getting highest working set ID")?; @@ -290,7 +292,10 @@ impl<'t> StorageTxn for Txn<'t> { let rows: Vec> = rows.collect(); let mut res = Vec::with_capacity(rows.len()); - for _ in 0..self.get_next_working_set_number().context("Getting working set number")? { + for _ in 0..self + .get_next_working_set_number() + .context("Getting working set number")? + { res.push(None); } for r in rows { @@ -351,6 +356,7 @@ impl<'t> StorageTxn for Txn<'t> { mod test { use super::*; use crate::storage::taskmap_with; + use pretty_assertions::{assert_eq}; use tempfile::TempDir; #[test] diff --git a/taskchampion/src/task/priority.rs b/taskchampion/src/task/priority.rs index 5e3f29d0d..a8ecc98bd 100644 --- a/taskchampion/src/task/priority.rs +++ b/taskchampion/src/task/priority.rs @@ -35,6 +35,7 @@ impl Priority { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; #[test] fn test_priority() { diff --git a/taskchampion/src/task/status.rs b/taskchampion/src/task/status.rs index f9f9fe773..7a14193a7 100644 --- a/taskchampion/src/task/status.rs +++ b/taskchampion/src/task/status.rs @@ -41,6 +41,7 @@ impl Status { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; #[test] fn test_status() { diff --git a/taskchampion/src/task/tag.rs b/taskchampion/src/task/tag.rs index d3a4842e7..123cf3bd7 100644 --- a/taskchampion/src/task/tag.rs +++ b/taskchampion/src/task/tag.rs @@ -138,6 +138,7 @@ pub(super) enum SyntheticTag { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; use rstest::rstest; use std::convert::TryInto; diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 40b2d6b0f..bb1002ccd 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -300,6 +300,7 @@ impl<'r> std::ops::Deref for TaskMut<'r> { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; fn with_mut_task(f: F) { let mut replica = Replica::new_inmemory(); diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index b373bc582..7b512b976 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -359,6 +359,7 @@ mod tests { use crate::server::test::TestServer; use crate::storage::InMemoryStorage; use chrono::Utc; + use pretty_assertions::{assert_eq}; use proptest::prelude::*; use std::collections::HashMap; use uuid::Uuid; diff --git a/taskchampion/src/utils.rs b/taskchampion/src/utils.rs index 6747b6cbd..7e8dd3867 100644 --- a/taskchampion/src/utils.rs +++ b/taskchampion/src/utils.rs @@ -41,6 +41,7 @@ impl AsRef<[u8]> for Key { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; #[test] fn test_from_bytes() { diff --git a/taskchampion/src/workingset.rs b/taskchampion/src/workingset.rs index 04aa4dcc5..c64c577c9 100644 --- a/taskchampion/src/workingset.rs +++ b/taskchampion/src/workingset.rs @@ -69,6 +69,7 @@ impl WorkingSet { #[cfg(test)] mod test { use super::*; + use pretty_assertions::{assert_eq}; fn make() -> (Uuid, Uuid, WorkingSet) { let uuid1 = Uuid::new_v4(); From bcef6bf392e62522db7c1d39acff255d1e6c9862 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 1 Oct 2021 22:18:24 -0400 Subject: [PATCH 350/548] remove redundant {..} --- sync-server/src/api/add_version.rs | 2 +- sync-server/src/api/get_child_version.rs | 2 +- sync-server/src/bin/taskchampion-sync-server.rs | 2 +- sync-server/src/server.rs | 2 +- sync-server/src/storage/sqlite.rs | 2 +- taskchampion/src/replica.rs | 2 +- taskchampion/src/server/local.rs | 2 +- taskchampion/src/server/remote/crypto.rs | 2 +- taskchampion/src/storage/inmemory.rs | 2 +- taskchampion/src/storage/operation.rs | 2 +- taskchampion/src/storage/sqlite.rs | 2 +- taskchampion/src/task/priority.rs | 2 +- taskchampion/src/task/status.rs | 2 +- taskchampion/src/task/tag.rs | 2 +- taskchampion/src/task/task.rs | 2 +- taskchampion/src/taskdb.rs | 2 +- taskchampion/src/utils.rs | 2 +- taskchampion/src/workingset.rs | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index ce9ba1104..c6d7314d0 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -80,7 +80,7 @@ mod test { use crate::storage::{InMemoryStorage, Storage}; use crate::Server; use actix_web::{http::StatusCode, test, App}; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use uuid::Uuid; #[actix_rt::test] diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 2b0517145..917c65126 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -47,7 +47,7 @@ mod test { use crate::storage::{InMemoryStorage, Storage}; use crate::Server; use actix_web::{http::StatusCode, test, App}; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use uuid::Uuid; #[actix_rt::test] diff --git a/sync-server/src/bin/taskchampion-sync-server.rs b/sync-server/src/bin/taskchampion-sync-server.rs index aa03cbaf8..ef6f5a977 100644 --- a/sync-server/src/bin/taskchampion-sync-server.rs +++ b/sync-server/src/bin/taskchampion-sync-server.rs @@ -65,7 +65,7 @@ async fn main() -> anyhow::Result<()> { mod test { use super::*; use actix_web::{test, App}; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use taskchampion_sync_server::storage::InMemoryStorage; #[actix_rt::test] diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 7711c77c0..9bc007cc4 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -81,7 +81,7 @@ pub(crate) fn add_version<'a>( mod test { use super::*; use crate::storage::{InMemoryStorage, Storage}; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; #[test] fn gcv_not_found() -> anyhow::Result<()> { diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index fc4c2dc0f..0d4f694b1 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -203,7 +203,7 @@ impl StorageTxn for Txn { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use tempfile::TempDir; #[test] diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 75b5137e4..063e0c470 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -146,7 +146,7 @@ impl Replica { mod tests { use super::*; use crate::task::Status; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use uuid::Uuid; #[test] diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index a9441ac2e..966b93bd6 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -159,7 +159,7 @@ impl Server for LocalServer { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use tempfile::TempDir; #[test] diff --git a/taskchampion/src/server/remote/crypto.rs b/taskchampion/src/server/remote/crypto.rs index 612a0eb28..512846479 100644 --- a/taskchampion/src/server/remote/crypto.rs +++ b/taskchampion/src/server/remote/crypto.rs @@ -78,7 +78,7 @@ impl AsRef<[u8]> for HistoryCiphertext { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; #[test] fn round_trip() { diff --git a/taskchampion/src/storage/inmemory.rs b/taskchampion/src/storage/inmemory.rs index 7aa081e57..448d9ae7f 100644 --- a/taskchampion/src/storage/inmemory.rs +++ b/taskchampion/src/storage/inmemory.rs @@ -166,7 +166,7 @@ impl Storage for InMemoryStorage { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; // (note: this module is heavily used in tests so most of its functionality is well-tested // elsewhere and not tested here) diff --git a/taskchampion/src/storage/operation.rs b/taskchampion/src/storage/operation.rs index 7bcf875eb..68f5fe7d0 100644 --- a/taskchampion/src/storage/operation.rs +++ b/taskchampion/src/storage/operation.rs @@ -128,7 +128,7 @@ mod test { use crate::storage::InMemoryStorage; use crate::taskdb::TaskDb; use chrono::{Duration, Utc}; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use proptest::prelude::*; // note that `tests/operation_transform_invariant.rs` tests the transform function quite diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 759826e4a..86525d0ca 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -356,7 +356,7 @@ impl<'t> StorageTxn for Txn<'t> { mod test { use super::*; use crate::storage::taskmap_with; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use tempfile::TempDir; #[test] diff --git a/taskchampion/src/task/priority.rs b/taskchampion/src/task/priority.rs index a8ecc98bd..cbe786524 100644 --- a/taskchampion/src/task/priority.rs +++ b/taskchampion/src/task/priority.rs @@ -35,7 +35,7 @@ impl Priority { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; #[test] fn test_priority() { diff --git a/taskchampion/src/task/status.rs b/taskchampion/src/task/status.rs index 7a14193a7..fbc4ed866 100644 --- a/taskchampion/src/task/status.rs +++ b/taskchampion/src/task/status.rs @@ -41,7 +41,7 @@ impl Status { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; #[test] fn test_status() { diff --git a/taskchampion/src/task/tag.rs b/taskchampion/src/task/tag.rs index 123cf3bd7..def539a67 100644 --- a/taskchampion/src/task/tag.rs +++ b/taskchampion/src/task/tag.rs @@ -138,7 +138,7 @@ pub(super) enum SyntheticTag { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use rstest::rstest; use std::convert::TryInto; diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index bb1002ccd..1da4a3244 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -300,7 +300,7 @@ impl<'r> std::ops::Deref for TaskMut<'r> { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; fn with_mut_task(f: F) { let mut replica = Replica::new_inmemory(); diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index 7b512b976..d611433f9 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -359,7 +359,7 @@ mod tests { use crate::server::test::TestServer; use crate::storage::InMemoryStorage; use chrono::Utc; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; use proptest::prelude::*; use std::collections::HashMap; use uuid::Uuid; diff --git a/taskchampion/src/utils.rs b/taskchampion/src/utils.rs index 7e8dd3867..7eb0885dc 100644 --- a/taskchampion/src/utils.rs +++ b/taskchampion/src/utils.rs @@ -41,7 +41,7 @@ impl AsRef<[u8]> for Key { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; #[test] fn test_from_bytes() { diff --git a/taskchampion/src/workingset.rs b/taskchampion/src/workingset.rs index c64c577c9..ea746a72b 100644 --- a/taskchampion/src/workingset.rs +++ b/taskchampion/src/workingset.rs @@ -69,7 +69,7 @@ impl WorkingSet { #[cfg(test)] mod test { use super::*; - use pretty_assertions::{assert_eq}; + use pretty_assertions::assert_eq; fn make() -> (Uuid, Uuid, WorkingSet) { let uuid1 = Uuid::new_v4(); From de5d46d3c77b7e85a9b101ac954396d34fa1e633 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 2 Oct 2021 14:57:47 +0000 Subject: [PATCH 351/548] add a cargo-fmt job to CI --- .github/workflows/checks.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index bad2acdce..26a73dae6 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -13,7 +13,7 @@ jobs: name: "Clippy" steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Cache cargo registry uses: actions/cache@v2 @@ -51,7 +51,7 @@ jobs: name: "Documentation" steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Setup mdBook uses: peaceiris/actions-mdbook@v1 @@ -81,3 +81,22 @@ jobs: - run: mdbook test docs - run: mdbook build docs + + fmt: + runs-on: ubuntu-latest + name: "Formatting" + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + components: rustfmt + toolchain: stable + override: true + + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + From fbd140a706cce79672ecfe215726ff3a7468ff2b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 2 Oct 2021 15:03:41 +0000 Subject: [PATCH 352/548] add fmt error --- cli/src/settings/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/settings/util.rs b/cli/src/settings/util.rs index 85d2e1d52..1b717694a 100644 --- a/cli/src/settings/util.rs +++ b/cli/src/settings/util.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, bail, Result}; -use toml::value::Table; +use toml::value::{Table}; /// Check that the input is a table and contains no keys not in the given list, returning /// the table. From 75fd0ff83ad45e5211a0b76c7d934576f5e031aa Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 3 Oct 2021 22:26:03 +0000 Subject: [PATCH 353/548] Remove cargo fmt error This was added to test the 'Formatting' action. It works :) --- cli/src/settings/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/settings/util.rs b/cli/src/settings/util.rs index 1b717694a..85d2e1d52 100644 --- a/cli/src/settings/util.rs +++ b/cli/src/settings/util.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, bail, Result}; -use toml::value::{Table}; +use toml::value::Table; /// Check that the input is a table and contains no keys not in the given list, returning /// the table. From eadce9f15ad144bfecafd21ee48c7819e28881f5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 28 Sep 2021 19:17:20 -0400 Subject: [PATCH 354/548] Add documentation for snapshots --- docs/src/SUMMARY.md | 1 + docs/src/snapshots.md | 48 ++++++++++++++++++++++ docs/src/sync-protocol.md | 85 +++++++++++++++++++++++++++++++++++---- 3 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 docs/src/snapshots.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 568eb57cf..061dbad5b 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -19,5 +19,6 @@ * [Tasks](./tasks.md) * [Synchronization and the Sync Server](./sync.md) * [Synchronization Model](./sync-model.md) + * [Snapshots](./snapshots.md) * [Server-Replica Protocol](./sync-protocol.md) * [Planned Functionality](./plans.md) diff --git a/docs/src/snapshots.md b/docs/src/snapshots.md new file mode 100644 index 000000000..1e608dba3 --- /dev/null +++ b/docs/src/snapshots.md @@ -0,0 +1,48 @@ +# Snapshots + +The basic synchronization model described in the previous page has a few shortcomings: + * servers must store an ever-increasing quantity of versions + * a new replica must download all versions since the beginning in order to derive the current state + +Snapshots allow TaskChampion to avoid both of these issues. +A snapshot is a copy of the task database at a specific version. +It is created by a replica, encrypted, and stored on the server. +A new replica can simply download a recent snapshot and apply any additional versions synchronized since that snapshot was made. +Servers can delete and reclaim space used by older versions, as long as newer snapshots are available. + +## Snapshot Heuristics + +A server implementation must answer a few questions: + * How often should snapshots be made? + * When can versions be deleted? + * When can snapshots be deleted? + +A critical invariant is that at least one snapshot must exist for any database that does not have a child of the nil version. +This ensures that a new replica can always derive the latest state. + +Aside from that invariant, the server implementation can vary in its answers to these questions, with the following considerations: + +Snapshots should be made frequently enough that a new replica can initialize quickly. + +Existing replicas will fail to synchronize if they request a child version that has been deleted. +This failure can cause data loss if the replica had local changes. +It's conceivable that replicas may not sync for weeks or months if, for example, they are located on a home computer while the user is on holiday. + +## Requesting New Snapshots + +The server requests snapshots from replicas, indicating an urgency for the request. +Some replicas, such as those running on PCs or servers, can produce a snapshot even at low urgency. +Other replicas, in more restricted environments such as mobile devices, will only produce a snapshot at high urgency. +This saves resources in these restricted environments. + +A snapshot must be made on a replica with no unsynchronized operations. +As such, it only makes sense to request a snapshot in response to a successful AddVersion request. + +## Handling Deleted Versions + +When a replica requests a child version, the response must distinguish two cases: + + 1. No such child version exists because the replica is up-to-date. + 1. No such child version exists because it has been deleted, and the replica must re-initialize itself. + +The details of this logic are covered in the [Server-Replica Protocol](./sync-protocol.md). diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md index db02d03a2..437f44b1e 100644 --- a/docs/src/sync-protocol.md +++ b/docs/src/sync-protocol.md @@ -7,26 +7,36 @@ The protocol builds on the model presented in the previous chapter, and in parti ## Clients -From the server's perspective, replicas are indistinguishable, so this protocol uses the term "client" to refer generically to all replicas replicating a single task history. +From the server's perspective, replicas accessing the same task history are indistinguishable, so this protocol uses the term "client" to refer generically to all replicas replicating a single task history. Each client is identified and authenticated with a "client key", known only to the server and to the replicas replicating the task history. ## Server For each client, the server is responsible for storing the task history, in the form of a branch-free sequence of versions. +It also stores the latest snapshot, if any exists. + + * versions: a set of {versionId: UUID, parentVersionId: UUID, historySegment: bytes} + * latestVersionId: UUID + * snapshotVersionId: UUID + * snapshot: bytes For each client, it stores a set of versions as well as the latest version ID, defaulting to the nil UUID. Each version has a version ID, a parent version ID, and a history segment (opaque data containing the operations for that version). -The server should maintain the following invariants: +The server should maintain the following invariants for each client: -1. Given a client c, c.latestVersion is nil or exists in the set of versions. -1. Given versions v1 and v2 for a client, with v1.versionId != v2.versionId and v1.parentVersionId != nil, v1.parentVersionId != v2.parentVersionId. +1. latestVersionId is nil or exists in the set of versions. +2. Given versions v1 and v2 for a client, with v1.versionId != v2.versionId and v1.parentVersionId != nil, v1.parentVersionId != v2.parentVersionId. In other words, versions do not branch. +3. If snapshotVersionId is nil, then there is a version with parentVersionId == nil. +4. If snapshotVersionId is not nil, then there is a version with parentVersionId = snapshotVersionId. -Note that versions form a linked list beginning with the version stored in he client. +Note that versions form a linked list beginning with the latestVersionId stored for the client. This linked list need not continue back to a version with v.parentVersionId = nil. It may end at any point when v.parentVersionId is not found in the set of Versions. This observation allows the server to discard older versions. +The third invariant prevents the server from discarding versions if there is no snapshot. +The fourth invariant prevents the server from discarding versions newer than the snapshot. ## Transactions @@ -45,6 +55,7 @@ If it already has one or more versions for the client, then it accepts the versi If the version is accepted, the server generates a new version ID for it. The version is added to the set of versions for the client, the client's latest version ID is set to the new version ID. The new version ID is returned in the response to the client. +The response may also include a request for a snapshot, with associated urgency. If the version is not accepted, the server makes no changes, but responds to the client with a conflict indication containing the latest version ID. The client may then "rebase" its operations and try again. @@ -61,7 +72,32 @@ If found, it returns the version's * parent version ID (matching that in the request), and * history segment. -If not found, the server returns a negative response. +The response is either a version (success, _not-found_, or _gone_, as determined by the first of the following to apply: +* If a version with parentVersionId equal to the requested parentVersionId exists, it is returned. +* If the requested parentVersionId is the nil UUID .. + * ..and snapshotVersionId is nil, the response is _not-found_ (the client has no versions). + * ..and snapshotVersionId is not nil, the response is _gone_ (the first version has been deleted). +* If a version with versionId equal to the requested parentVersionId exists, the response is _not-found_ (the client is up-to-date) +* Otherwise, the response is _gone_ (the requested version has been deleted). + +### AddSnapshot + +The AddSnapshot transaction requests that the server store a new snapshot, generated by the client. +The request contains the following: + + * version ID at which the snapshot was made + * snapshot data (opaque to the server) + +The server should validate that the snapshot is for an existing version and is newer than any existing snapshot. +It may also validate that the snapshot is for a "recent" version (e.g., one of the last 5 versions). +If a snapshot already exists for the given version, the server may keep or discard the new snapshot but should return a success indication to the client. + +The server response is empty. + +### GetSnapshot + +The GetSnapshot transaction requests that the server provide the latest snapshot. +The response contains the snapshot version ID and the snapshot data, if those exist. ## HTTP Representation @@ -79,6 +115,7 @@ The content-type must be `application/vnd.taskchampion.history-segment`. The success response is a 200 OK with an empty body. The new version ID appears in the `X-Version-Id` header. +If included, a snapshot request appears in the `X-Snapshot-Request` header with value `urgency=low` or `urgency=high`. On conflict, the response is a 409 CONFLICT with an empty body. The expected parent version ID appears in the `X-Parent-Version-Id` header. @@ -88,8 +125,40 @@ Other error responses (4xx or 5xx) may be returned and should be treated appropr ### GetChildVersion The request is a `GET` to `/v1/client/get-child-version/`. -The response is 404 NOT FOUND if no such version exists. -Otherwise, the response is a 200 OK. + +The response is determined as described above. +The _not-found_ response is 404 NOT FOUND. +The _gone_ response is 410 GONE. +Neither has a response body. + +On success, the response is a 200 OK. The version's history segment is returned in the response body, with content-type `application/vnd.taskchampion.history-segment`. The version ID appears in the `X-Version-Id` header. The response body may be encoded, in accordance with any `Accept-Encoding` header in the request. + +On failure, a client should treat a 404 NOT FOUND as indicating that it is up-to-date. +Clients should treat a 410 GONE as a synchronization error. +If the client has pending changes to send to the server, based on a now-removed version, then those changes cannot be reconciled and will be lost. +The client should, optionally after consulting the user, download and apply the latest snapshot. + +### AddSnapshot + +The request is a `POST` to `/v1/client/add-snapshot/`. +The request body contains the snapshot data, optionally encoded using any encoding supported by actix-web. +The content-type must be `application/vnd.taskchampion.snapshot`. + +If the version is invalid, as described above, the response should be 400 BAD REQUEST. +The server response should be 200 OK on success. + +### GetSnapshot + +The request is a `GET` to `/v1/client/snapshot`. + +The response is a 200 OK. +The snapshot is returned in the response body, with content-type `application/vnd.taskchampion.snapshot`. +The version ID appears in the `X-Version-Id` header. +The response body may be encoded, in accordance with any `Accept-Encoding` header in the request. + +After downloading and decrypting a snapshot, a client must replace its entire local task database with the content of the snapshot. +Any local operations that had not yet been synchronized must be discarded. +After the snapshot is applied, the client should begin the synchronization process again, starting from the snapshot version. From 8d2be3b495b615788d09b28380ca3f7933e054ed Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 29 Sep 2021 02:19:57 +0000 Subject: [PATCH 355/548] add get_version to server storage api --- sync-server/src/storage/inmemory.rs | 119 ++++++++++++++++++++++++++-- sync-server/src/storage/mod.rs | 7 ++ sync-server/src/storage/sqlite.rs | 80 +++++++++++++------ 3 files changed, 173 insertions(+), 33 deletions(-) diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs index 143b2e760..7aef5f98c 100644 --- a/sync-server/src/storage/inmemory.rs +++ b/sync-server/src/storage/inmemory.rs @@ -6,8 +6,11 @@ struct Inner { /// Clients, indexed by client_key clients: HashMap, - /// Versions, indexed by (client_key, parent_version_id) + /// Versions, indexed by (client_key, version_id) versions: HashMap<(Uuid, Uuid), Version>, + + /// Child versions, indexed by (client_key, parent_version_id) + children: HashMap<(Uuid, Uuid), Uuid>, } pub struct InMemoryStorage(Mutex); @@ -18,6 +21,7 @@ impl InMemoryStorage { Self(Mutex::new(Inner { clients: HashMap::new(), versions: HashMap::new(), + children: HashMap::new(), })) } } @@ -66,11 +70,23 @@ impl<'a> StorageTxn for InnerTxn<'a> { client_key: Uuid, parent_version_id: Uuid, ) -> anyhow::Result> { - Ok(self - .0 - .versions - .get(&(client_key, parent_version_id)) - .cloned()) + if let Some(parent_version_id) = self.0.children.get(&(client_key, parent_version_id)) { + Ok(self + .0 + .versions + .get(&(client_key, *parent_version_id)) + .cloned()) + } else { + Ok(None) + } + } + + fn get_version( + &mut self, + client_key: Uuid, + version_id: Uuid, + ) -> anyhow::Result> { + Ok(self.0.versions.get(&(client_key, version_id)).cloned()) } fn add_version( @@ -86,9 +102,12 @@ impl<'a> StorageTxn for InnerTxn<'a> { parent_version_id, history_segment, }; + self.0 + .children + .insert((client_key, version.parent_version_id), version.version_id); self.0 .versions - .insert((client_key, version.parent_version_id), version); + .insert((client_key, version.version_id), version); Ok(()) } @@ -96,3 +115,89 @@ impl<'a> StorageTxn for InnerTxn<'a> { Ok(()) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_emtpy_dir() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let maybe_client = txn.get_client(Uuid::new_v4())?; + assert!(maybe_client.is_none()); + Ok(()) + } + + #[test] + fn test_get_client_empty() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let maybe_client = txn.get_client(Uuid::new_v4())?; + assert!(maybe_client.is_none()); + Ok(()) + } + + #[test] + fn test_client_storage() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + + let client_key = Uuid::new_v4(); + let latest_version_id = Uuid::new_v4(); + txn.new_client(client_key, latest_version_id)?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + + let latest_version_id = Uuid::new_v4(); + txn.set_client_latest_version_id(client_key, latest_version_id)?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + + Ok(()) + } + + #[test] + fn test_gvbp_empty() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let maybe_version = txn.get_version_by_parent(Uuid::new_v4(), Uuid::new_v4())?; + assert!(maybe_version.is_none()); + Ok(()) + } + + #[test] + fn test_add_version_and_get_version() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + + let client_key = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abc".to_vec(); + txn.add_version( + client_key, + version_id, + parent_version_id, + history_segment.clone(), + )?; + + let expected = Version { + version_id, + parent_version_id, + history_segment, + }; + + let version = txn + .get_version_by_parent(client_key, parent_version_id)? + .unwrap(); + assert_eq!(version, expected); + + let version = txn.get_version(client_key, version_id)?.unwrap(); + assert_eq!(version, expected); + + Ok(()) + } +} diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 30b961fa1..121999892 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -43,6 +43,13 @@ pub trait StorageTxn { parent_version_id: Uuid, ) -> anyhow::Result>; + /// Get a version, indexed by its own version id + fn get_version( + &mut self, + client_key: Uuid, + version_id: Uuid, + ) -> anyhow::Result>; + /// Add a version (that must not already exist) fn add_version( &mut self, diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index 0d4f694b1..e1310a55c 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -71,6 +71,7 @@ impl SqliteStorage { let queries = vec![ "CREATE TABLE IF NOT EXISTS clients (client_key STRING PRIMARY KEY, latest_version_id STRING);", "CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, client_key STRING, parent_version_id STRING, history_segment BLOB);", + "CREATE INDEX IF NOT EXISTS versions_by_parent ON versions (parent_version_id);", ]; for q in queries { txn.execute(q, []).context("Creating table")?; @@ -100,6 +101,34 @@ impl Txn { .transaction() .map_err(|_e| SqliteError::CreateTransactionFailed) } + + /// Implementation for queries from the versions table + fn get_version_impl( + &mut self, + query: &'static str, + client_key: Uuid, + version_id_arg: Uuid, + ) -> anyhow::Result> { + let t = self.get_txn()?; + let r = t + .query_row( + query, + params![&StoredUuid(version_id_arg), &StoredUuid(client_key)], + |r| { + let version_id: StoredUuid = r.get("version_id")?; + let parent_version_id: StoredUuid = r.get("parent_version_id")?; + + Ok(Version { + version_id: version_id.0, + parent_version_id: parent_version_id.0, + history_segment: r.get("history_segment")?, + }) + }, + ) + .optional() + .context("Get version query")?; + Ok(r) + } } impl StorageTxn for Txn { @@ -148,24 +177,20 @@ impl StorageTxn for Txn { client_key: Uuid, parent_version_id: Uuid, ) -> anyhow::Result> { - let t = self.get_txn()?; - let r = t.query_row( + self.get_version_impl( "SELECT version_id, parent_version_id, history_segment FROM versions WHERE parent_version_id = ? AND client_key = ?", - params![&StoredUuid(parent_version_id), &StoredUuid(client_key)], - |r| { - let version_id: StoredUuid = r.get("version_id")?; - let parent_version_id: StoredUuid = r.get("parent_version_id")?; - - Ok(Version{ - version_id: version_id.0, - parent_version_id: parent_version_id.0, - history_segment: r.get("history_segment")?, - })} - ) - .optional() - .context("Get version query") - ?; - Ok(r) + client_key, + parent_version_id) + } + fn get_version( + &mut self, + client_key: Uuid, + version_id: Uuid, + ) -> anyhow::Result> { + self.get_version_impl( + "SELECT version_id, parent_version_id, history_segment FROM versions WHERE version_id = ? AND client_key = ?", + client_key, + version_id) } fn add_version( @@ -260,7 +285,7 @@ mod test { } #[test] - fn test_add_version_and_gvbp() -> anyhow::Result<()> { + fn test_add_version_and_get_version() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; let storage = SqliteStorage::new(&tmp_dir.path())?; let mut txn = storage.txn()?; @@ -275,18 +300,21 @@ mod test { parent_version_id, history_segment.clone(), )?; + + let expected = Version { + version_id, + parent_version_id, + history_segment, + }; + let version = txn .get_version_by_parent(client_key, parent_version_id)? .unwrap(); + assert_eq!(version, expected); + + let version = txn.get_version(client_key, version_id)?.unwrap(); + assert_eq!(version, expected); - assert_eq!( - version, - Version { - version_id, - parent_version_id, - history_segment, - } - ); Ok(()) } } From 2570956710f943a0994c5e08388a469c2fc454bb Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 29 Sep 2021 02:48:39 +0000 Subject: [PATCH 356/548] [breaking] Add snapshot support to server storage This refactors the storage API pretty substantially, and represents a breaking change to the schema used by the sqlite storage --- .changelogs/2021-10-03-server-storage.md | 2 + Cargo.lock | 1 + sync-server/Cargo.toml | 1 + sync-server/src/server.rs | 3 +- sync-server/src/storage/inmemory.rs | 134 +++++++++++++--- sync-server/src/storage/mod.rs | 40 ++++- sync-server/src/storage/sqlite.rs | 196 +++++++++++++++++++---- 7 files changed, 312 insertions(+), 65 deletions(-) create mode 100644 .changelogs/2021-10-03-server-storage.md diff --git a/.changelogs/2021-10-03-server-storage.md b/.changelogs/2021-10-03-server-storage.md new file mode 100644 index 000000000..7834601d5 --- /dev/null +++ b/.changelogs/2021-10-03-server-storage.md @@ -0,0 +1,2 @@ +- The SQLite server storage schema has changed incompatibly, in order to add support for snapshots. + As this is not currently ready for production usage, no migration path is provided except deleting the existing database. diff --git a/Cargo.lock b/Cargo.lock index 4c0d541ac..bb79505da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3016,6 +3016,7 @@ dependencies = [ "actix-rt", "actix-web", "anyhow", + "chrono", "clap", "env_logger 0.8.4", "futures", diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index c0d26656e..25ac111bf 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -19,6 +19,7 @@ clap = "^2.33.0" log = "^0.4.14" env_logger = "^0.8.3" rusqlite = { version = "0.25", features = ["bundled"] } +chrono = { version = "^0.4.10", features = ["serde"] } [dev-dependencies] actix-rt = "^1.1.1" diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 9bc007cc4..c20d0b955 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -71,7 +71,6 @@ pub(crate) fn add_version<'a>( // update the DB txn.add_version(client_key, version_id, parent_version_id, history_segment)?; - txn.set_client_latest_version_id(client_key, version_id)?; txn.commit()?; Ok(AddVersionResult::Ok(version_id)) @@ -102,6 +101,7 @@ mod test { let parent_version_id = Uuid::new_v4(); let history_segment = b"abcd".to_vec(); + txn.new_client(client_key, version_id)?; txn.add_version( client_key, version_id, @@ -130,6 +130,7 @@ mod test { let existing_parent_version_id = Uuid::new_v4(); let client = Client { latest_version_id: existing_parent_version_id, + snapshot: None, }; assert_eq!( diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs index 7aef5f98c..03b36c47a 100644 --- a/sync-server/src/storage/inmemory.rs +++ b/sync-server/src/storage/inmemory.rs @@ -1,4 +1,4 @@ -use super::{Client, Storage, StorageTxn, Uuid, Version}; +use super::{Client, Snapshot, Storage, StorageTxn, Uuid, Version}; use std::collections::HashMap; use std::sync::{Mutex, MutexGuard}; @@ -6,6 +6,9 @@ struct Inner { /// Clients, indexed by client_key clients: HashMap, + /// Snapshot data, indexed by client key + snapshots: HashMap>, + /// Versions, indexed by (client_key, version_id) versions: HashMap<(Uuid, Uuid), Version>, @@ -20,6 +23,7 @@ impl InMemoryStorage { pub fn new() -> Self { Self(Mutex::new(Inner { clients: HashMap::new(), + snapshots: HashMap::new(), versions: HashMap::new(), children: HashMap::new(), })) @@ -46,23 +50,44 @@ impl<'a> StorageTxn for InnerTxn<'a> { if self.0.clients.get(&client_key).is_some() { return Err(anyhow::anyhow!("Client {} already exists", client_key)); } - self.0 - .clients - .insert(client_key, Client { latest_version_id }); + self.0.clients.insert( + client_key, + Client { + latest_version_id, + snapshot: None, + }, + ); Ok(()) } - fn set_client_latest_version_id( + fn set_snapshot( &mut self, client_key: Uuid, - latest_version_id: Uuid, + snapshot: Snapshot, + data: Vec, ) -> anyhow::Result<()> { - if let Some(client) = self.0.clients.get_mut(&client_key) { - client.latest_version_id = latest_version_id; - Ok(()) - } else { - Err(anyhow::anyhow!("Client {} does not exist", client_key)) + let mut client = self + .0 + .clients + .get_mut(&client_key) + .ok_or_else(|| anyhow::anyhow!("no such client"))?; + client.snapshot = Some(snapshot); + self.0.snapshots.insert(client_key, data); + Ok(()) + } + + fn get_snapshot_data( + &mut self, + client_key: Uuid, + version_id: Uuid, + ) -> anyhow::Result>> { + // sanity check + let client = self.0.clients.get(&client_key); + let client = client.ok_or_else(|| anyhow::anyhow!("no such client"))?; + if Some(&version_id) != client.snapshot.as_ref().map(|snap| &snap.version_id) { + return Err(anyhow::anyhow!("unexpected snapshot_version_id")); } + Ok(self.0.snapshots.get(&client_key).cloned()) } fn get_version_by_parent( @@ -102,12 +127,21 @@ impl<'a> StorageTxn for InnerTxn<'a> { parent_version_id, history_segment, }; + + if let Some(client) = self.0.clients.get_mut(&client_key) { + client.latest_version_id = version_id; + if let Some(ref mut snap) = client.snapshot { + snap.versions_since += 1; + } + } else { + return Err(anyhow::anyhow!("Client {} does not exist", client_key)); + } + self.0 .children - .insert((client_key, version.parent_version_id), version.version_id); - self.0 - .versions - .insert((client_key, version.version_id), version); + .insert((client_key, parent_version_id), version_id); + self.0.versions.insert((client_key, version_id), version); + Ok(()) } @@ -119,15 +153,7 @@ impl<'a> StorageTxn for InnerTxn<'a> { #[cfg(test)] mod test { use super::*; - - #[test] - fn test_emtpy_dir() -> anyhow::Result<()> { - let storage = InMemoryStorage::new(); - let mut txn = storage.txn()?; - let maybe_client = txn.get_client(Uuid::new_v4())?; - assert!(maybe_client.is_none()); - Ok(()) - } + use chrono::Utc; #[test] fn test_get_client_empty() -> anyhow::Result<()> { @@ -149,12 +175,25 @@ mod test { let client = txn.get_client(client_key)?.unwrap(); assert_eq!(client.latest_version_id, latest_version_id); + assert!(client.snapshot.is_none()); let latest_version_id = Uuid::new_v4(); - txn.set_client_latest_version_id(client_key, latest_version_id)?; + txn.add_version(client_key, latest_version_id, Uuid::new_v4(), vec![1, 1])?; let client = txn.get_client(client_key)?.unwrap(); assert_eq!(client.latest_version_id, latest_version_id); + assert!(client.snapshot.is_none()); + + let snap = Snapshot { + version_id: Uuid::new_v4(), + timestamp: Utc::now(), + versions_since: 4, + }; + txn.set_snapshot(client_key, snap.clone(), vec![1, 2, 3])?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + assert_eq!(client.snapshot.unwrap(), snap); Ok(()) } @@ -177,6 +216,8 @@ mod test { let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let history_segment = b"abc".to_vec(); + + txn.new_client(client_key, parent_version_id)?; txn.add_version( client_key, version_id, @@ -200,4 +241,47 @@ mod test { Ok(()) } + + #[test] + fn test_snapshots() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + + let client_key = Uuid::new_v4(); + + txn.new_client(client_key, Uuid::new_v4())?; + assert!(txn.get_client(client_key)?.unwrap().snapshot.is_none()); + + let snap = Snapshot { + version_id: Uuid::new_v4(), + timestamp: Utc::now(), + versions_since: 3, + }; + txn.set_snapshot(client_key, snap.clone(), vec![9, 8, 9])?; + + assert_eq!( + txn.get_snapshot_data(client_key, snap.version_id)?.unwrap(), + vec![9, 8, 9] + ); + assert_eq!(txn.get_client(client_key)?.unwrap().snapshot, Some(snap)); + + let snap2 = Snapshot { + version_id: Uuid::new_v4(), + timestamp: Utc::now(), + versions_since: 10, + }; + txn.set_snapshot(client_key, snap2.clone(), vec![0, 2, 4, 6])?; + + assert_eq!( + txn.get_snapshot_data(client_key, snap2.version_id)? + .unwrap(), + vec![0, 2, 4, 6] + ); + assert_eq!(txn.get_client(client_key)?.unwrap().snapshot, Some(snap2)); + + // check that mismatched version is detected + assert!(txn.get_snapshot_data(client_key, Uuid::new_v4()).is_err()); + + Ok(()) + } } diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 121999892..c52624898 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Serialize}; +use chrono::{DateTime, Utc}; use uuid::Uuid; #[cfg(debug_assertions)] @@ -10,12 +10,27 @@ pub use inmemory::InMemoryStorage; mod sqlite; pub use self::sqlite::SqliteStorage; -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Debug)] pub struct Client { + /// The latest version for this client (may be the nil version) pub latest_version_id: Uuid, + /// Data about the latest snapshot for this client + pub snapshot: Option, } -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Debug)] +pub struct Snapshot { + /// ID of the version at which this snapshot was made + pub version_id: Uuid, + + /// Timestamp at which this snapshot was set + pub timestamp: DateTime, + + /// Number of versions since this snapshot was made + pub versions_since: u32, +} + +#[derive(Clone, PartialEq, Debug)] pub struct Version { pub version_id: Uuid, pub parent_version_id: Uuid, @@ -29,13 +44,22 @@ pub trait StorageTxn { /// Create a new client with the given latest_version_id fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()>; - /// Set the client's latest_version_id - fn set_client_latest_version_id( + /// Set the client's most recent snapshot. + fn set_snapshot( &mut self, client_key: Uuid, - latest_version_id: Uuid, + snapshot: Snapshot, + data: Vec, ) -> anyhow::Result<()>; + /// Get the data for the most recent snapshot. The version_id + /// is used to verify that the snapshot is for the correct version. + fn get_snapshot_data( + &mut self, + client_key: Uuid, + version_id: Uuid, + ) -> anyhow::Result>>; + /// Get a version, indexed by parent version id fn get_version_by_parent( &mut self, @@ -50,7 +74,9 @@ pub trait StorageTxn { version_id: Uuid, ) -> anyhow::Result>; - /// Add a version (that must not already exist) + /// Add a version (that must not already exist), and + /// - update latest_version_id + /// - increment snapshot.versions_since fn add_version( &mut self, client_key: Uuid, diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index e1310a55c..47a07014a 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -1,5 +1,6 @@ -use super::{Client, Storage, StorageTxn, Uuid, Version}; +use super::{Client, Snapshot, Storage, StorageTxn, Uuid, Version}; use anyhow::Context; +use chrono::{TimeZone, Utc}; use rusqlite::types::{FromSql, ToSql}; use rusqlite::{params, Connection, OptionalExtension}; use std::path::Path; @@ -30,24 +31,6 @@ impl ToSql for StoredUuid { } } -/// Stores [`Client`] in SQLite -impl FromSql for Client { - fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { - let o: Client = serde_json::from_str(value.as_str()?) - .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; - Ok(o) - } -} - -/// Parses Operation stored as JSON in string column -impl ToSql for Client { - fn to_sql(&self) -> rusqlite::Result> { - let s = serde_json::to_string(&self) - .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; - Ok(s.into()) - } -} - /// An on-disk storage backend which uses SQLite pub struct SqliteStorage { db_file: std::path::PathBuf, @@ -69,12 +52,19 @@ impl SqliteStorage { let txn = con.transaction()?; let queries = vec![ - "CREATE TABLE IF NOT EXISTS clients (client_key STRING PRIMARY KEY, latest_version_id STRING);", + "CREATE TABLE IF NOT EXISTS clients ( + client_key STRING PRIMARY KEY, + latest_version_id STRING, + snapshot_version_id STRING, + versions_since_snapshot INTEGER, + snapshot_timestamp INTEGER, + snapshot BLOB);", "CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, client_key STRING, parent_version_id STRING, history_segment BLOB);", "CREATE INDEX IF NOT EXISTS versions_by_parent ON versions (parent_version_id);", ]; for q in queries { - txn.execute(q, []).context("Creating table")?; + txn.execute(q, []) + .context("Error while creating SQLite tables")?; } txn.commit()?; } @@ -126,7 +116,7 @@ impl Txn { }, ) .optional() - .context("Get version query")?; + .context("Error getting version")?; Ok(r) } } @@ -136,17 +126,42 @@ impl StorageTxn for Txn { let t = self.get_txn()?; let result: Option = t .query_row( - "SELECT latest_version_id FROM clients WHERE client_key = ? LIMIT 1", + "SELECT + latest_version_id, + snapshot_timestamp, + versions_since_snapshot, + snapshot_version_id + FROM clients + WHERE client_key = ? + LIMIT 1", [&StoredUuid(client_key)], |r| { let latest_version_id: StoredUuid = r.get(0)?; + let snapshot_timestamp: Option = r.get(1)?; + let versions_since_snapshot: Option = r.get(2)?; + let snapshot_version_id: Option = r.get(3)?; + + // if all of the relevant fields are non-NULL, return a snapshot + let snapshot = match ( + snapshot_timestamp, + versions_since_snapshot, + snapshot_version_id, + ) { + (Some(ts), Some(vs), Some(v)) => Some(Snapshot { + version_id: v.0, + timestamp: Utc.timestamp(ts, 0), + versions_since: vs, + }), + _ => None, + }; Ok(Client { latest_version_id: latest_version_id.0, + snapshot, }) }, ) .optional() - .context("Get client query")?; + .context("Error getting client")?; Ok(result) } @@ -158,18 +173,66 @@ impl StorageTxn for Txn { "INSERT OR REPLACE INTO clients (client_key, latest_version_id) VALUES (?, ?)", params![&StoredUuid(client_key), &StoredUuid(latest_version_id)], ) - .context("Create client query")?; + .context("Error creating/updating client")?; t.commit()?; Ok(()) } - fn set_client_latest_version_id( + fn set_snapshot( &mut self, client_key: Uuid, - latest_version_id: Uuid, + snapshot: Snapshot, + data: Vec, ) -> anyhow::Result<()> { - // Implementation is same as new_client - self.new_client(client_key, latest_version_id) + let t = self.get_txn()?; + + t.execute( + "UPDATE clients + SET + snapshot_version_id = ?, + snapshot_timestamp = ?, + versions_since_snapshot = ?, + snapshot = ? + WHERE client_key = ?", + params![ + &StoredUuid(snapshot.version_id), + snapshot.timestamp.timestamp(), + snapshot.versions_since, + data, + &StoredUuid(client_key), + ], + ) + .context("Error creating/updating snapshot")?; + t.commit()?; + Ok(()) + } + + fn get_snapshot_data( + &mut self, + client_key: Uuid, + version_id: Uuid, + ) -> anyhow::Result>> { + let t = self.get_txn()?; + let r = t + .query_row( + "SELECT snapshot, snapshot_version_id FROM clients WHERE client_key = ?", + params![&StoredUuid(client_key)], + |r| { + let v: StoredUuid = r.get("snapshot_version_id")?; + let d: Vec = r.get("snapshot")?; + Ok((v.0, d)) + }, + ) + .optional() + .context("Error getting snapshot")?; + r.map(|(v, d)| { + if v != version_id { + return Err(anyhow::anyhow!("unexpected snapshot_version_id")); + } + + Ok(d) + }) + .transpose() } fn get_version_by_parent( @@ -182,6 +245,7 @@ impl StorageTxn for Txn { client_key, parent_version_id) } + fn get_version( &mut self, client_key: Uuid, @@ -209,9 +273,19 @@ impl StorageTxn for Txn { StoredUuid(client_key), StoredUuid(parent_version_id), history_segment - ], + ] ) - .context("Add version query")?; + .context("Error adding version")?; + t.execute( + "UPDATE clients + SET + latest_version_id = ?, + versions_since_snapshot = versions_since_snapshot + 1 + WHERE client_key = ?", + params![StoredUuid(version_id), StoredUuid(client_key),], + ) + .context("Error updating client for new version")?; + t.commit()?; Ok(()) } @@ -228,6 +302,7 @@ impl StorageTxn for Txn { #[cfg(test)] mod test { use super::*; + use chrono::DateTime; use pretty_assertions::assert_eq; use tempfile::TempDir; @@ -264,12 +339,25 @@ mod test { let client = txn.get_client(client_key)?.unwrap(); assert_eq!(client.latest_version_id, latest_version_id); + assert!(client.snapshot.is_none()); let latest_version_id = Uuid::new_v4(); - txn.set_client_latest_version_id(client_key, latest_version_id)?; + txn.add_version(client_key, latest_version_id, Uuid::new_v4(), vec![1, 1])?; let client = txn.get_client(client_key)?.unwrap(); assert_eq!(client.latest_version_id, latest_version_id); + assert!(client.snapshot.is_none()); + + let snap = Snapshot { + version_id: Uuid::new_v4(), + timestamp: "2014-11-28T12:00:09Z".parse::>().unwrap(), + versions_since: 4, + }; + txn.set_snapshot(client_key, snap.clone(), vec![1, 2, 3])?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + assert_eq!(client.snapshot.unwrap(), snap); Ok(()) } @@ -317,4 +405,48 @@ mod test { Ok(()) } + + #[test] + fn test_snapshots() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let storage = SqliteStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + + let client_key = Uuid::new_v4(); + + txn.new_client(client_key, Uuid::new_v4())?; + assert!(txn.get_client(client_key)?.unwrap().snapshot.is_none()); + + let snap = Snapshot { + version_id: Uuid::new_v4(), + timestamp: "2013-10-08T12:00:09Z".parse::>().unwrap(), + versions_since: 3, + }; + txn.set_snapshot(client_key, snap.clone(), vec![9, 8, 9])?; + + assert_eq!( + txn.get_snapshot_data(client_key, snap.version_id)?.unwrap(), + vec![9, 8, 9] + ); + assert_eq!(txn.get_client(client_key)?.unwrap().snapshot, Some(snap)); + + let snap2 = Snapshot { + version_id: Uuid::new_v4(), + timestamp: "2014-11-28T12:00:09Z".parse::>().unwrap(), + versions_since: 10, + }; + txn.set_snapshot(client_key, snap2.clone(), vec![0, 2, 4, 6])?; + + assert_eq!( + txn.get_snapshot_data(client_key, snap2.version_id)? + .unwrap(), + vec![0, 2, 4, 6] + ); + assert_eq!(txn.get_client(client_key)?.unwrap().snapshot, Some(snap2)); + + // check that mismatched version is detected + assert!(txn.get_snapshot_data(client_key, Uuid::new_v4()).is_err()); + + Ok(()) + } } From 53d1f8dbc2df92761b47b7dd221b91577962ff41 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 29 Sep 2021 22:41:40 +0000 Subject: [PATCH 357/548] update get_child_version to distinguish gone and not-found --- sync-server/src/api/get_child_version.rs | 48 +++++--- sync-server/src/server.rs | 137 ++++++++++++++++++++--- 2 files changed, 152 insertions(+), 33 deletions(-) diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 917c65126..fd12e1243 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -2,7 +2,7 @@ use crate::api::{ client_key_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{get_child_version, VersionId}; +use crate::server::{get_child_version, GetVersionResult, VersionId}; use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; /// Get a child version. @@ -23,27 +23,31 @@ pub(crate) async fn service( let client_key = client_key_header(&req)?; - txn.get_client(client_key) + let client = txn + .get_client(client_key) .map_err(failure_to_ise)? .ok_or_else(|| error::ErrorNotFound("no such client"))?; - let result = get_child_version(txn, client_key, parent_version_id).map_err(failure_to_ise)?; - if let Some(result) = result { - Ok(HttpResponse::Ok() + return match get_child_version(txn, client_key, client, parent_version_id) + .map_err(failure_to_ise)? + { + GetVersionResult::Success { + version_id, + parent_version_id, + history_segment, + } => Ok(HttpResponse::Ok() .content_type(HISTORY_SEGMENT_CONTENT_TYPE) - .header(VERSION_ID_HEADER, result.version_id.to_string()) - .header( - PARENT_VERSION_ID_HEADER, - result.parent_version_id.to_string(), - ) - .body(result.history_segment)) - } else { - Err(error::ErrorNotFound("no such version")) - } + .header(VERSION_ID_HEADER, version_id.to_string()) + .header(PARENT_VERSION_ID_HEADER, parent_version_id.to_string()) + .body(history_segment)), + GetVersionResult::NotFound => Err(error::ErrorNotFound("no such version")), + GetVersionResult::Gone => Err(error::ErrorGone("version has been deleted")), + }; } #[cfg(test)] mod test { + use crate::server::NO_VERSION_ID; use crate::storage::{InMemoryStorage, Storage}; use crate::Server; use actix_web::{http::StatusCode, test, App}; @@ -113,7 +117,7 @@ mod test { } #[actix_rt::test] - async fn test_version_not_found() { + async fn test_version_not_found_and_gone() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); @@ -126,12 +130,26 @@ mod test { let server = Server::new(storage); let mut app = test::init_service(App::new().service(server.service())).await; + // the child of an unknown parent_version_id is GONE let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() .uri(&uri) .header("X-Client-Key", client_key.to_string()) .to_request(); let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::GONE); + assert_eq!(resp.headers().get("X-Version-Id"), None); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); + + // but the child of the nil parent_version_id is NOT FOUND, since + // there is no snapshot. The tests in crate::server test more + // corner cases. + let uri = format!("/v1/client/get-child-version/{}", NO_VERSION_ID); + let req = test::TestRequest::get() + .uri(&uri) + .header("X-Client-Key", client_key.to_string()) + .to_request(); + let resp = test::call_service(&mut app, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.headers().get("X-Version-Id"), None); assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index c20d0b955..2b8f92445 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -1,5 +1,6 @@ //! This module implements the core logic of the server: handling transactions, upholding -//! invariants, and so on. +//! invariants, and so on. This does not implement the HTTP-specific portions; those +//! are in [`crate::api`]. See the protocol documentation for details. use crate::storage::{Client, StorageTxn}; use uuid::Uuid; @@ -10,26 +11,53 @@ pub(crate) type HistorySegment = Vec; pub(crate) type ClientKey = Uuid; pub(crate) type VersionId = Uuid; -/// Response to get_child_version +/// Response to get_child_version. See the protocol documentation. #[derive(Clone, PartialEq, Debug)] -pub(crate) struct GetVersionResult { - pub(crate) version_id: Uuid, - pub(crate) parent_version_id: Uuid, - pub(crate) history_segment: HistorySegment, +pub(crate) enum GetVersionResult { + NotFound, + Gone, + Success { + version_id: Uuid, + parent_version_id: Uuid, + history_segment: HistorySegment, + }, } +/// Implementation of the GetChildVersion protocol transaction pub(crate) fn get_child_version<'a>( mut txn: Box, client_key: ClientKey, + client: Client, parent_version_id: VersionId, -) -> anyhow::Result> { - Ok(txn - .get_version_by_parent(client_key, parent_version_id)? - .map(|version| GetVersionResult { +) -> anyhow::Result { + // If a version with parentVersionId equal to the requested parentVersionId exists, it is returned. + if let Some(version) = txn.get_version_by_parent(client_key, parent_version_id)? { + return Ok(GetVersionResult::Success { version_id: version.version_id, parent_version_id: version.parent_version_id, history_segment: version.history_segment, - })) + }); + } + + // If the requested parentVersionId is the nil UUID .. + if parent_version_id == NO_VERSION_ID { + return Ok(match client.snapshot { + // ..and snapshotVersionId is nil, the response is _not-found_ (the client has no + // versions). + None => GetVersionResult::NotFound, + // ..and snapshotVersionId is not nil, the response is _gone_ (the first version has + // been deleted). + Some(_) => GetVersionResult::Gone, + }); + } + + // If a version with versionId equal to the requested parentVersionId exists, the response is _not-found_ (the client is up-to-date) + if txn.get_version(client_key, parent_version_id)?.is_some() { + return Ok(GetVersionResult::NotFound); + } + + // Otherwise, the response is _gone_ (the requested version has been deleted). + Ok(GetVersionResult::Gone) } /// Response to add_version @@ -41,6 +69,7 @@ pub(crate) enum AddVersionResult { ExpectedParentVersion(VersionId), } +/// Implementation of the AddVersion protocol transaction pub(crate) fn add_version<'a>( mut txn: Box, client_key: ClientKey, @@ -79,16 +108,87 @@ pub(crate) fn add_version<'a>( #[cfg(test)] mod test { use super::*; - use crate::storage::{InMemoryStorage, Storage}; + use crate::storage::{InMemoryStorage, Snapshot, Storage}; + use chrono::{TimeZone, Utc}; use pretty_assertions::assert_eq; #[test] - fn gcv_not_found() -> anyhow::Result<()> { + fn gcv_not_found_initial() -> anyhow::Result<()> { let storage = InMemoryStorage::new(); - let txn = storage.txn()?; + let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); + txn.new_client(client_key, NO_VERSION_ID)?; + + // when no snapshot exists, the first version is NotFound + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!( + get_child_version(txn, client_key, client, NO_VERSION_ID)?, + GetVersionResult::NotFound + ); + Ok(()) + } + + #[test] + fn gcv_gone_initial() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + + txn.new_client(client_key, Uuid::new_v4())?; + txn.set_snapshot( + client_key, + Snapshot { + version_id: Uuid::new_v4(), + versions_since: 0, + timestamp: Utc.ymd(2001, 9, 9).and_hms(1, 46, 40), + }, + vec![1, 2, 3], + )?; + + // when a snapshot exists, the first version is GONE + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!( + get_child_version(txn, client_key, client, NO_VERSION_ID)?, + GetVersionResult::Gone + ); + Ok(()) + } + + #[test] + fn gcv_not_found_up_to_date() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + + // add a parent version, but not the requested child version let parent_version_id = Uuid::new_v4(); - assert_eq!(get_child_version(txn, client_key, parent_version_id)?, None); + txn.new_client(client_key, parent_version_id)?; + txn.add_version(client_key, parent_version_id, NO_VERSION_ID, vec![])?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!( + get_child_version(txn, client_key, client, parent_version_id)?, + GetVersionResult::NotFound + ); + Ok(()) + } + + #[test] + fn gcv_gone() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + + // make up a parent version id, but neither that version + // nor its child exists (presumed to have been deleted) + let parent_version_id = Uuid::new_v4(); + txn.new_client(client_key, Uuid::new_v4())?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!( + get_child_version(txn, client_key, client, parent_version_id)?, + GetVersionResult::Gone + ); Ok(()) } @@ -109,13 +209,14 @@ mod test { history_segment.clone(), )?; + let client = txn.get_client(client_key)?.unwrap(); assert_eq!( - get_child_version(txn, client_key, parent_version_id)?, - Some(GetVersionResult { + get_child_version(txn, client_key, client, parent_version_id)?, + GetVersionResult::Success { version_id, parent_version_id, history_segment, - }) + } ); Ok(()) } From e2f79edad6fe805bccd67e8127f3032563870002 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 29 Sep 2021 23:52:58 +0000 Subject: [PATCH 358/548] add get_snapshot API method --- sync-server/src/api/get_snapshot.rs | 111 ++++++++++++++++++++++++++++ sync-server/src/api/mod.rs | 5 ++ sync-server/src/server.rs | 56 ++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 sync-server/src/api/get_snapshot.rs diff --git a/sync-server/src/api/get_snapshot.rs b/sync-server/src/api/get_snapshot.rs new file mode 100644 index 000000000..6de0be427 --- /dev/null +++ b/sync-server/src/api/get_snapshot.rs @@ -0,0 +1,111 @@ +use crate::api::{ + client_key_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE, VERSION_ID_HEADER, +}; +use crate::server::get_snapshot; +use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; + +/// Get a snapshot. +/// +/// If a snapshot for this client exists, it is returned with content-type +/// `application/vnd.taskchampion.snapshot`. The `X-Version-Id` header contains the version of the +/// snapshot. +/// +/// If no snapshot exists, returns a 404 with no content. Returns other 4xx or 5xx responses on +/// other errors. +#[get("/v1/client/snapshot")] +pub(crate) async fn service( + req: HttpRequest, + server_state: web::Data, +) -> Result { + let mut txn = server_state.txn().map_err(failure_to_ise)?; + + let client_key = client_key_header(&req)?; + + let client = txn + .get_client(client_key) + .map_err(failure_to_ise)? + .ok_or_else(|| error::ErrorNotFound("no such client"))?; + + if let Some((version_id, data)) = + get_snapshot(txn, client_key, client).map_err(failure_to_ise)? + { + Ok(HttpResponse::Ok() + .content_type(SNAPSHOT_CONTENT_TYPE) + .header(VERSION_ID_HEADER, version_id.to_string()) + .body(data)) + } else { + Err(error::ErrorNotFound("no snapshot")) + } +} + +#[cfg(test)] +mod test { + use crate::storage::{InMemoryStorage, Snapshot, Storage}; + use crate::Server; + use actix_web::{http::StatusCode, test, App}; + use chrono::{TimeZone, Utc}; + use pretty_assertions::assert_eq; + use uuid::Uuid; + + #[actix_rt::test] + async fn test_not_found() { + let client_key = Uuid::new_v4(); + let storage: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = storage.txn().unwrap(); + txn.new_client(client_key, Uuid::new_v4()).unwrap(); + } + + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; + + let uri = "/v1/client/snapshot"; + let req = test::TestRequest::get() + .uri(uri) + .header("X-Client-Key", client_key.to_string()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_success() { + let client_key = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let snapshot_data = vec![1, 2, 3, 4]; + let storage: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = storage.txn().unwrap(); + txn.new_client(client_key, Uuid::new_v4()).unwrap(); + txn.set_snapshot( + client_key, + Snapshot { + version_id, + versions_since: 3, + timestamp: Utc.ymd(2001, 9, 9).and_hms(1, 46, 40), + }, + snapshot_data.clone(), + ) + .unwrap(); + } + + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; + + let uri = "/v1/client/snapshot"; + let req = test::TestRequest::get() + .uri(uri) + .header("X-Client-Key", client_key.to_string()) + .to_request(); + let mut resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + use futures::StreamExt; + let (bytes, _) = resp.take_body().into_future().await; + assert_eq!(bytes.unwrap().unwrap().as_ref(), snapshot_data); + } +} diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index 917dda83e..87666a35a 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -5,11 +5,15 @@ use std::sync::Arc; mod add_version; mod get_child_version; +mod get_snapshot; /// The content-type for history segments (opaque blobs of bytes) pub(crate) const HISTORY_SEGMENT_CONTENT_TYPE: &str = "application/vnd.taskchampion.history-segment"; +/// The content-type for snapshots (opaque blobs of bytes) +pub(crate) const SNAPSHOT_CONTENT_TYPE: &str = "application/vnd.taskchampion.snapshot"; + /// The header name for version ID pub(crate) const VERSION_ID_HEADER: &str = "X-Version-Id"; @@ -26,6 +30,7 @@ pub(crate) fn api_scope() -> Scope { web::scope("") .service(get_child_version::service) .service(add_version::service) + .service(get_snapshot::service) } /// Convert a failure::Error to an Actix ISE diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 2b8f92445..0ed3f4e7b 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -105,6 +105,20 @@ pub(crate) fn add_version<'a>( Ok(AddVersionResult::Ok(version_id)) } +/// Implementation of the GetSnapshot protocol transaction +pub(crate) fn get_snapshot<'a>( + mut txn: Box, + client_key: ClientKey, + client: Client, +) -> anyhow::Result)>> { + Ok(if let Some(snap) = client.snapshot { + txn.get_snapshot_data(client_key, snap.version_id)? + .map(|data| (snap.version_id, data)) + } else { + None + }) +} + #[cfg(test)] mod test { use super::*; @@ -302,4 +316,46 @@ mod test { fn av_success_nil_latest_version_id() -> anyhow::Result<()> { test_av_success(false) } + + #[test] + fn get_snapshot_found() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + let data = vec![1, 2, 3]; + let snapshot_version_id = Uuid::new_v4(); + + txn.new_client(client_key, snapshot_version_id)?; + txn.set_snapshot( + client_key, + Snapshot { + version_id: snapshot_version_id, + versions_since: 3, + timestamp: Utc.ymd(2001, 9, 9).and_hms(1, 46, 40), + }, + data.clone(), + )?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!( + get_snapshot(txn, client_key, client)?, + Some((snapshot_version_id, data.clone())) + ); + + Ok(()) + } + + #[test] + fn get_snapshot_not_found() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + + txn.new_client(client_key, NO_VERSION_ID)?; + let client = txn.get_client(client_key)?.unwrap(); + + assert_eq!(get_snapshot(txn, client_key, client)?, None); + + Ok(()) + } } From d1da8eee52d51f8752fbc92362f7963417a3b4a1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 30 Sep 2021 02:45:59 +0000 Subject: [PATCH 359/548] Add add_snapshot API method --- sync-server/src/api/add_snapshot.rs | 191 +++++++++++++++++ sync-server/src/api/mod.rs | 2 + sync-server/src/lib.rs | 7 + sync-server/src/server.rs | 308 +++++++++++++++++++++++++++- 4 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 sync-server/src/api/add_snapshot.rs diff --git a/sync-server/src/api/add_snapshot.rs b/sync-server/src/api/add_snapshot.rs new file mode 100644 index 000000000..49de42cf5 --- /dev/null +++ b/sync-server/src/api/add_snapshot.rs @@ -0,0 +1,191 @@ +use crate::api::{client_key_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE}; +use crate::server::{add_snapshot, VersionId, NO_VERSION_ID}; +use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; +use futures::StreamExt; + +/// Max snapshot size: 100MB +const MAX_SIZE: usize = 100 * 1024 * 1024; + +/// Add a new snapshot, after checking prerequisites. The snapshot should be transmitted in the +/// request entity body and must have content-type `application/vnd.taskchampion.snapshot`. The +/// content can be encoded in any of the formats supported by actix-web. +/// +/// On success, the response is a 200 OK. Even in a 200 OK, the snapshot may not appear in a +/// subsequent `GetSnapshot` call. +/// +/// Returns other 4xx or 5xx responses on other errors. +#[post("/v1/client/add-snapshot/{version_id}")] +pub(crate) async fn service( + req: HttpRequest, + server_state: web::Data, + web::Path((version_id,)): web::Path<(VersionId,)>, + mut payload: web::Payload, +) -> Result { + // check content-type + if req.content_type() != SNAPSHOT_CONTENT_TYPE { + return Err(error::ErrorBadRequest("Bad content-type")); + } + + let client_key = client_key_header(&req)?; + + // read the body in its entirety + let mut body = web::BytesMut::new(); + while let Some(chunk) = payload.next().await { + let chunk = chunk?; + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + return Err(error::ErrorBadRequest("Snapshot over maximum allowed size")); + } + body.extend_from_slice(&chunk); + } + + if body.is_empty() { + return Err(error::ErrorBadRequest("No snapshot supplied")); + } + + // note that we do not open the transaction until the body has been read + // completely, to avoid blocking other storage access while that data is + // in transit. + let mut txn = server_state.txn().map_err(failure_to_ise)?; + + // get, or create, the client + let client = match txn.get_client(client_key).map_err(failure_to_ise)? { + Some(client) => client, + None => { + txn.new_client(client_key, NO_VERSION_ID) + .map_err(failure_to_ise)?; + txn.get_client(client_key).map_err(failure_to_ise)?.unwrap() + } + }; + + add_snapshot(txn, client_key, client, version_id, body.to_vec()).map_err(failure_to_ise)?; + Ok(HttpResponse::Ok().body("")) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::storage::{InMemoryStorage, Storage}; + use crate::Server; + use actix_web::{http::StatusCode, test, App}; + use pretty_assertions::assert_eq; + use uuid::Uuid; + + #[actix_rt::test] + async fn test_success() -> anyhow::Result<()> { + let client_key = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let storage: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = storage.txn().unwrap(); + txn.new_client(client_key, version_id).unwrap(); + txn.add_version(client_key, version_id, NO_VERSION_ID, vec![])?; + } + + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; + + let uri = format!("/v1/client/add-snapshot/{}", version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header("Content-Type", "application/vnd.taskchampion.snapshot") + .header("X-Client-Key", client_key.to_string()) + .set_payload(b"abcd".to_vec()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + // read back that snapshot + let uri = "/v1/client/snapshot"; + let req = test::TestRequest::get() + .uri(uri) + .header("X-Client-Key", client_key.to_string()) + .to_request(); + let mut resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + use futures::StreamExt; + let (bytes, _) = resp.take_body().into_future().await; + assert_eq!(bytes.unwrap().unwrap().as_ref(), b"abcd"); + + Ok(()) + } + + #[actix_rt::test] + async fn test_not_added_200() { + let client_key = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let storage: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = storage.txn().unwrap(); + txn.new_client(client_key, NO_VERSION_ID).unwrap(); + } + + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; + + // add a snapshot for a nonexistent version + let uri = format!("/v1/client/add-snapshot/{}", version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header("Content-Type", "application/vnd.taskchampion.snapshot") + .header("X-Client-Key", client_key.to_string()) + .set_payload(b"abcd".to_vec()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + // read back, seeing no snapshot + let uri = "/v1/client/snapshot"; + let req = test::TestRequest::get() + .uri(uri) + .header("X-Client-Key", client_key.to_string()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_bad_content_type() { + let client_key = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; + + let uri = format!("/v1/client/add-snapshot/{}", version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header("Content-Type", "not/correct") + .header("X-Client-Key", client_key.to_string()) + .set_payload(b"abcd".to_vec()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[actix_rt::test] + async fn test_empty_body() { + let client_key = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; + + let uri = format!("/v1/client/add-snapshot/{}", version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header( + "Content-Type", + "application/vnd.taskchampion.history-segment", + ) + .header("X-Client-Key", client_key.to_string()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } +} diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index 87666a35a..1c8dd48d5 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -3,6 +3,7 @@ use crate::storage::Storage; use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope}; use std::sync::Arc; +mod add_snapshot; mod add_version; mod get_child_version; mod get_snapshot; @@ -31,6 +32,7 @@ pub(crate) fn api_scope() -> Scope { .service(get_child_version::service) .service(add_version::service) .service(get_snapshot::service) + .service(add_snapshot::service) } /// Convert a failure::Error to an Actix ISE diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs index c219f2e1b..c817da29e 100644 --- a/sync-server/src/lib.rs +++ b/sync-server/src/lib.rs @@ -35,3 +35,10 @@ impl Server { .service(api_scope()) } } + +#[cfg(test)] +mod test { + pub(crate) fn init_logging() { + let _ = env_logger::builder().is_test(true).try_init(); + } +} diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 0ed3f4e7b..3a4455847 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -1,12 +1,18 @@ //! This module implements the core logic of the server: handling transactions, upholding //! invariants, and so on. This does not implement the HTTP-specific portions; those //! are in [`crate::api`]. See the protocol documentation for details. -use crate::storage::{Client, StorageTxn}; +use crate::storage::{Client, Snapshot, StorageTxn}; +use chrono::Utc; use uuid::Uuid; /// The distinguished value for "no version" pub const NO_VERSION_ID: VersionId = Uuid::nil(); +/// Number of versions to search back from the latest to find the +/// version for a newly-added snapshot. Snapshots for versions older +/// than this will be rejected. +const SNAPSHOT_SEARCH_LEN: i32 = 5; + pub(crate) type HistorySegment = Vec; pub(crate) type ClientKey = Uuid; pub(crate) type VersionId = Uuid; @@ -105,6 +111,90 @@ pub(crate) fn add_version<'a>( Ok(AddVersionResult::Ok(version_id)) } +/// Implementation of the AddSnapshot protocol transaction +pub(crate) fn add_snapshot<'a>( + mut txn: Box, + client_key: ClientKey, + client: Client, + version_id: VersionId, + data: Vec, +) -> anyhow::Result<()> { + log::debug!( + "add_snapshot(client_key: {}, version_id: {})", + client_key, + version_id, + ); + + // NOTE: if the snapshot is rejected, this function logs about it and returns + // Ok(()), as there's no reason to report an errot to the client / user. + + let last_snapshot = client.snapshot.map(|snap| snap.version_id); + if Some(version_id) == last_snapshot { + log::debug!( + "rejecting snapshot for version {}: already exists", + version_id + ); + return Ok(()); + } + + // look for this version in the history of this client, starting at the latest version, and + // only iterating for a limited number of versions. + let mut search_len = SNAPSHOT_SEARCH_LEN; + let mut vid = client.latest_version_id; + + loop { + if vid == version_id && version_id != NO_VERSION_ID { + // the new snapshot is for a recent version, so proceed + break; + } + + if Some(vid) == last_snapshot { + // the new snapshot is older than the last snapshot, so ignore it + log::debug!( + "rejecting snapshot for version {}: newer snapshot already exists or no such version", + version_id + ); + return Ok(()); + } + + search_len -= 1; + if search_len <= 0 || vid == NO_VERSION_ID { + // this should not happen in normal operation, so warn about it + log::warn!( + "rejecting snapshot for version {}: version is too old or no such version", + version_id + ); + return Ok(()); + } + + // get the parent version ID + if let Some(parent) = txn.get_version(client_key, vid)? { + vid = parent.parent_version_id; + } else { + // this version does not exist; "this should not happen" but if it does, + // we don't need a snapshot earlier than the missing version. + log::warn!( + "rejecting snapshot for version {}: newer versions have already been deleted", + version_id + ); + return Ok(()); + } + } + + log::warn!("accepting snapshot for version {}", version_id); + txn.set_snapshot( + client_key, + Snapshot { + version_id, + timestamp: Utc::now(), + versions_since: 0, + }, + data, + )?; + txn.commit()?; + Ok(()) +} + /// Implementation of the GetSnapshot protocol transaction pub(crate) fn get_snapshot<'a>( mut txn: Box, @@ -123,11 +213,14 @@ pub(crate) fn get_snapshot<'a>( mod test { use super::*; use crate::storage::{InMemoryStorage, Snapshot, Storage}; + use crate::test::init_logging; use chrono::{TimeZone, Utc}; use pretty_assertions::assert_eq; #[test] fn gcv_not_found_initial() -> anyhow::Result<()> { + init_logging(); + let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -144,6 +237,8 @@ mod test { #[test] fn gcv_gone_initial() -> anyhow::Result<()> { + init_logging(); + let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -170,6 +265,8 @@ mod test { #[test] fn gcv_not_found_up_to_date() -> anyhow::Result<()> { + init_logging(); + let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -189,6 +286,8 @@ mod test { #[test] fn gcv_gone() -> anyhow::Result<()> { + init_logging(); + let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -208,6 +307,8 @@ mod test { #[test] fn gcv_found() -> anyhow::Result<()> { + init_logging(); + let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -237,6 +338,8 @@ mod test { #[test] fn av_conflict() -> anyhow::Result<()> { + init_logging(); + let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -265,6 +368,8 @@ mod test { } fn test_av_success(latest_version_id_nil: bool) -> anyhow::Result<()> { + init_logging(); + let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -317,8 +422,207 @@ mod test { test_av_success(false) } + #[test] + fn add_snapshot_success_latest() -> anyhow::Result<()> { + init_logging(); + + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + + // set up a task DB with one version in it + txn.new_client(client_key, version_id)?; + txn.add_version(client_key, version_id, NO_VERSION_ID, vec![])?; + + // add a snapshot for that version + let client = txn.get_client(client_key)?.unwrap(); + add_snapshot(txn, client_key, client, version_id, vec![1, 2, 3])?; + + // verify the snapshot + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + let snapshot = client.snapshot.unwrap(); + assert_eq!(snapshot.version_id, version_id); + assert_eq!(snapshot.versions_since, 0); + assert_eq!( + txn.get_snapshot_data(client_key, version_id).unwrap(), + Some(vec![1, 2, 3]) + ); + + Ok(()) + } + + #[test] + fn add_snapshot_success_older() -> anyhow::Result<()> { + init_logging(); + + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + let version_id_1 = Uuid::new_v4(); + let version_id_2 = Uuid::new_v4(); + + // set up a task DB with two versions in it + txn.new_client(client_key, version_id_2)?; + txn.add_version(client_key, version_id_1, NO_VERSION_ID, vec![])?; + txn.add_version(client_key, version_id_2, version_id_1, vec![])?; + + // add a snapshot for version 1 + let client = txn.get_client(client_key)?.unwrap(); + add_snapshot(txn, client_key, client, version_id_1, vec![1, 2, 3])?; + + // verify the snapshot + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + let snapshot = client.snapshot.unwrap(); + assert_eq!(snapshot.version_id, version_id_1); + assert_eq!(snapshot.versions_since, 0); + assert_eq!( + txn.get_snapshot_data(client_key, version_id_1).unwrap(), + Some(vec![1, 2, 3]) + ); + + Ok(()) + } + + #[test] + fn add_snapshot_fails_no_such() -> anyhow::Result<()> { + init_logging(); + + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + let version_id_1 = Uuid::new_v4(); + let version_id_2 = Uuid::new_v4(); + + // set up a task DB with two versions in it + txn.new_client(client_key, version_id_2)?; + txn.add_version(client_key, version_id_1, NO_VERSION_ID, vec![])?; + txn.add_version(client_key, version_id_2, version_id_1, vec![])?; + + // add a snapshot for unknown version + let client = txn.get_client(client_key)?.unwrap(); + let version_id_unk = Uuid::new_v4(); + add_snapshot(txn, client_key, client, version_id_unk, vec![1, 2, 3])?; + + // verify the snapshot does not exist + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + assert!(client.snapshot.is_none()); + + Ok(()) + } + + #[test] + fn add_snapshot_fails_too_old() -> anyhow::Result<()> { + init_logging(); + + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + let mut version_id = Uuid::new_v4(); + let mut parent_version_id = Uuid::nil(); + let mut version_ids = vec![]; + + // set up a task DB with 10 versions in it (oldest to newest) + txn.new_client(client_key, Uuid::nil())?; + for _ in 0..10 { + txn.add_version(client_key, version_id, parent_version_id, vec![])?; + version_ids.push(version_id); + parent_version_id = version_id; + version_id = Uuid::new_v4(); + } + + // add a snapshot for the earliest of those + let client = txn.get_client(client_key)?.unwrap(); + add_snapshot(txn, client_key, client, version_ids[0], vec![1, 2, 3])?; + + // verify the snapshot does not exist + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + assert!(client.snapshot.is_none()); + + Ok(()) + } + + #[test] + fn add_snapshot_fails_newer_exists() -> anyhow::Result<()> { + init_logging(); + + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + let mut version_id = Uuid::new_v4(); + let mut parent_version_id = Uuid::nil(); + let mut version_ids = vec![]; + + // set up a task DB with 5 versions in it (oldest to newest) and a snapshot of the middle + // one + txn.new_client(client_key, Uuid::nil())?; + for _ in 0..5 { + txn.add_version(client_key, version_id, parent_version_id, vec![])?; + version_ids.push(version_id); + parent_version_id = version_id; + version_id = Uuid::new_v4(); + } + txn.set_snapshot( + client_key, + Snapshot { + version_id: version_ids[2], + versions_since: 2, + timestamp: Utc.ymd(2001, 9, 9).and_hms(1, 46, 40), + }, + vec![1, 2, 3], + )?; + + // add a snapshot for the earliest of those + let client = txn.get_client(client_key)?.unwrap(); + add_snapshot(txn, client_key, client, version_ids[0], vec![9, 9, 9])?; + + println!("{:?}", version_ids); + + // verify the snapshot was not replaced + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + let snapshot = client.snapshot.unwrap(); + assert_eq!(snapshot.version_id, version_ids[2]); + assert_eq!(snapshot.versions_since, 2); + assert_eq!( + txn.get_snapshot_data(client_key, version_ids[2]).unwrap(), + Some(vec![1, 2, 3]) + ); + + Ok(()) + } + + #[test] + fn add_snapshot_fails_nil_version() -> anyhow::Result<()> { + init_logging(); + + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_key = Uuid::new_v4(); + + // just set up the client + txn.new_client(client_key, NO_VERSION_ID)?; + + // add a snapshot for the nil version + let client = txn.get_client(client_key)?.unwrap(); + add_snapshot(txn, client_key, client, NO_VERSION_ID, vec![9, 9, 9])?; + + // verify the snapshot does not exist + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + assert!(client.snapshot.is_none()); + + Ok(()) + } + #[test] fn get_snapshot_found() -> anyhow::Result<()> { + init_logging(); + let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); @@ -347,6 +651,8 @@ mod test { #[test] fn get_snapshot_not_found() -> anyhow::Result<()> { + init_logging(); + let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); From 7bb6ea6865e101041ae0d154a1e9f2ae970d8bc2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 3 Oct 2021 02:14:36 +0000 Subject: [PATCH 360/548] Request snapshots in AddVersion --- sync-server/src/api/add_version.rs | 42 +++- sync-server/src/api/mod.rs | 3 + sync-server/src/server.rs | 361 +++++++++++++++++++++++------ 3 files changed, 328 insertions(+), 78 deletions(-) diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index c6d7314d0..92d86ef52 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -1,8 +1,8 @@ use crate::api::{ client_key_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, - PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, + PARENT_VERSION_ID_HEADER, SNAPSHOT_REQUEST_HEADER, VERSION_ID_HEADER, }; -use crate::server::{add_version, AddVersionResult, VersionId, NO_VERSION_ID}; +use crate::server::{add_version, AddVersionResult, SnapshotUrgency, VersionId, NO_VERSION_ID}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use futures::StreamExt; @@ -18,6 +18,9 @@ const MAX_SIZE: usize = 100 * 1024 * 1024; /// the version cannot be added due to a conflict, the response is a 409 CONFLICT with the expected /// parent version ID in the `X-Parent-Version-Id` header. /// +/// If included, a snapshot request appears in the `X-Snapshot-Request` header with value +/// `urgency=low` or `urgency=high`. +/// /// Returns other 4xx or 5xx responses on other errors. #[post("/v1/client/add-version/{parent_version_id}")] pub(crate) async fn service( @@ -63,15 +66,30 @@ pub(crate) async fn service( } }; - let result = add_version(txn, client_key, client, parent_version_id, body.to_vec()) - .map_err(failure_to_ise)?; + let (result, snap_urgency) = + add_version(txn, client_key, client, parent_version_id, body.to_vec()) + .map_err(failure_to_ise)?; + Ok(match result { - AddVersionResult::Ok(version_id) => HttpResponse::Ok() - .header(VERSION_ID_HEADER, version_id.to_string()) - .body(""), - AddVersionResult::ExpectedParentVersion(parent_version_id) => HttpResponse::Conflict() - .header(PARENT_VERSION_ID_HEADER, parent_version_id.to_string()) - .body(""), + AddVersionResult::Ok(version_id) => { + let mut rb = HttpResponse::Ok(); + rb.header(VERSION_ID_HEADER, version_id.to_string()); + match snap_urgency { + SnapshotUrgency::None => {} + SnapshotUrgency::Low => { + rb.header(SNAPSHOT_REQUEST_HEADER, "urgency=low"); + } + SnapshotUrgency::High => { + rb.header(SNAPSHOT_REQUEST_HEADER, "urgency=high"); + } + }; + rb.finish() + } + AddVersionResult::ExpectedParentVersion(parent_version_id) => { + let mut rb = HttpResponse::Conflict(); + rb.header(PARENT_VERSION_ID_HEADER, parent_version_id.to_string()); + rb.finish() + } }) } @@ -117,6 +135,10 @@ mod test { let new_version_id = resp.headers().get("X-Version-Id").unwrap(); assert!(new_version_id != &version_id.to_string()); + // Shapshot should be requested, since there is no existing snapshot + let snapshot_request = resp.headers().get("X-Snapshot-Request").unwrap(); + assert_eq!(snapshot_request, "urgency=high"); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); } diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index 1c8dd48d5..b3157438a 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -24,6 +24,9 @@ pub(crate) const CLIENT_KEY_HEADER: &str = "X-Client-Key"; /// The header name for parent version ID pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; +/// The header name for parent version ID +pub(crate) const SNAPSHOT_REQUEST_HEADER: &str = "X-Snapshot-Request"; + /// The type containing a reference to the Storage object in the Actix state. pub(crate) type ServerState = Arc; diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 3a4455847..69a65a088 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -13,6 +13,12 @@ pub const NO_VERSION_ID: VersionId = Uuid::nil(); /// than this will be rejected. const SNAPSHOT_SEARCH_LEN: i32 = 5; +/// Maximum number of days between snapshots +const SNAPSHOT_DAYS: i64 = 14; + +/// Maximum number of versions between snapshots +const SNAPSHOT_VERSIONS: u32 = 30; + pub(crate) type HistorySegment = Vec; pub(crate) type ClientKey = Uuid; pub(crate) type VersionId = Uuid; @@ -75,6 +81,43 @@ pub(crate) enum AddVersionResult { ExpectedParentVersion(VersionId), } +/// Urgency of a snapshot for a client; used to create the `X-Snapshot-Request` header. +#[derive(PartialEq, Debug, Clone, Copy, Eq, PartialOrd, Ord)] +pub(crate) enum SnapshotUrgency { + /// Don't need a snapshot right now. + None, + + /// A snapshot would be good, but can wait for other replicas to provide it. + Low, + + /// A snapshot is needed right now. + High, +} + +impl SnapshotUrgency { + /// Calculate the urgency for a snapshot based on its age in days + fn for_days(days: i64) -> Self { + if days >= SNAPSHOT_DAYS * 3 / 2 { + SnapshotUrgency::High + } else if days >= SNAPSHOT_DAYS { + SnapshotUrgency::Low + } else { + SnapshotUrgency::None + } + } + + /// Calculate the urgency for a snapshot based on its age in versions + fn for_versions_since(versions_since: u32) -> Self { + if versions_since >= SNAPSHOT_VERSIONS * 3 / 2 { + SnapshotUrgency::High + } else if versions_since >= SNAPSHOT_VERSIONS { + SnapshotUrgency::Low + } else { + SnapshotUrgency::None + } + } +} + /// Implementation of the AddVersion protocol transaction pub(crate) fn add_version<'a>( mut txn: Box, @@ -82,7 +125,7 @@ pub(crate) fn add_version<'a>( client: Client, parent_version_id: VersionId, history_segment: HistorySegment, -) -> anyhow::Result { +) -> anyhow::Result<(AddVersionResult, SnapshotUrgency)> { log::debug!( "add_version(client_key: {}, parent_version_id: {})", client_key, @@ -92,8 +135,9 @@ pub(crate) fn add_version<'a>( // check if this version is acceptable, under the protection of the transaction if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { log::debug!("add_version request rejected: mismatched latest_version_id"); - return Ok(AddVersionResult::ExpectedParentVersion( - client.latest_version_id, + return Ok(( + AddVersionResult::ExpectedParentVersion(client.latest_version_id), + SnapshotUrgency::None, )); } @@ -108,7 +152,26 @@ pub(crate) fn add_version<'a>( txn.add_version(client_key, version_id, parent_version_id, history_segment)?; txn.commit()?; - Ok(AddVersionResult::Ok(version_id)) + // calculate the urgency + let time_urgency = match client.snapshot { + None => SnapshotUrgency::High, + Some(Snapshot { timestamp, .. }) => { + SnapshotUrgency::for_days((Utc::now() - timestamp).num_days()) + } + }; + + println!("{:?}", client.snapshot); + let version_urgency = match client.snapshot { + None => SnapshotUrgency::High, + Some(Snapshot { versions_since, .. }) => { + SnapshotUrgency::for_versions_since(versions_since) + } + }; + + Ok(( + AddVersionResult::Ok(version_id), + std::cmp::max(time_urgency, version_urgency), + )) } /// Implementation of the AddSnapshot protocol transaction @@ -214,11 +277,44 @@ mod test { use super::*; use crate::storage::{InMemoryStorage, Snapshot, Storage}; use crate::test::init_logging; - use chrono::{TimeZone, Utc}; + use chrono::{Duration, TimeZone, Utc}; use pretty_assertions::assert_eq; #[test] - fn gcv_not_found_initial() -> anyhow::Result<()> { + fn snapshot_urgency_max() { + use SnapshotUrgency::*; + assert_eq!(std::cmp::max(None, None), None); + assert_eq!(std::cmp::max(None, Low), Low); + assert_eq!(std::cmp::max(None, High), High); + assert_eq!(std::cmp::max(Low, None), Low); + assert_eq!(std::cmp::max(Low, Low), Low); + assert_eq!(std::cmp::max(Low, High), High); + assert_eq!(std::cmp::max(High, None), High); + assert_eq!(std::cmp::max(High, Low), High); + assert_eq!(std::cmp::max(High, High), High); + } + + #[test] + fn snapshot_urgency_for_days() { + use SnapshotUrgency::*; + assert_eq!(SnapshotUrgency::for_days(0), None); + assert_eq!(SnapshotUrgency::for_days(SNAPSHOT_DAYS), Low); + assert_eq!(SnapshotUrgency::for_days(SNAPSHOT_DAYS * 2), High); + } + + #[test] + fn snapshot_urgency_for_versions_since() { + use SnapshotUrgency::*; + assert_eq!(SnapshotUrgency::for_versions_since(0), None); + assert_eq!(SnapshotUrgency::for_versions_since(SNAPSHOT_VERSIONS), Low); + assert_eq!( + SnapshotUrgency::for_versions_since(SNAPSHOT_VERSIONS * 2), + High + ); + } + + #[test] + fn get_child_version_not_found_initial() -> anyhow::Result<()> { init_logging(); let storage = InMemoryStorage::new(); @@ -236,7 +332,7 @@ mod test { } #[test] - fn gcv_gone_initial() -> anyhow::Result<()> { + fn get_child_version_gone_initial() -> anyhow::Result<()> { init_logging(); let storage = InMemoryStorage::new(); @@ -264,7 +360,7 @@ mod test { } #[test] - fn gcv_not_found_up_to_date() -> anyhow::Result<()> { + fn get_child_version_not_found_up_to_date() -> anyhow::Result<()> { init_logging(); let storage = InMemoryStorage::new(); @@ -285,7 +381,7 @@ mod test { } #[test] - fn gcv_gone() -> anyhow::Result<()> { + fn get_child_version_gone() -> anyhow::Result<()> { init_logging(); let storage = InMemoryStorage::new(); @@ -306,7 +402,7 @@ mod test { } #[test] - fn gcv_found() -> anyhow::Result<()> { + fn get_child_version_found() -> anyhow::Result<()> { init_logging(); let storage = InMemoryStorage::new(); @@ -336,90 +432,221 @@ mod test { Ok(()) } - #[test] - fn av_conflict() -> anyhow::Result<()> { + /// Utility setup function for add_version tests + fn av_setup( + storage: &InMemoryStorage, + num_versions: u32, + snapshot_version: Option, + snapshot_days_ago: Option, + ) -> anyhow::Result<(Uuid, Vec)> { init_logging(); - - let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); - let parent_version_id = Uuid::new_v4(); - let history_segment = b"abcd".to_vec(); - let existing_parent_version_id = Uuid::new_v4(); - let client = Client { - latest_version_id: existing_parent_version_id, - snapshot: None, - }; + let mut versions = vec![]; + let mut version_id = Uuid::nil(); + txn.new_client(client_key, Uuid::nil())?; + for vnum in 0..num_versions { + let parent_version_id = version_id; + version_id = Uuid::new_v4(); + versions.push(version_id); + txn.add_version( + client_key, + version_id, + parent_version_id, + vec![0, 0, vnum as u8], + )?; + if Some(vnum) == snapshot_version { + txn.set_snapshot( + client_key, + Snapshot { + version_id, + versions_since: 0, + timestamp: Utc::now() - Duration::days(snapshot_days_ago.unwrap_or(0)), + }, + vec![vnum as u8], + )?; + } + } + + Ok((client_key, versions)) + } + + /// Utility function to check the results of an add_version call + fn av_success_check( + storage: &InMemoryStorage, + client_key: Uuid, + existing_versions: &[Uuid], + result: (AddVersionResult, SnapshotUrgency), + expected_history: Vec, + expected_urgency: SnapshotUrgency, + ) -> anyhow::Result<()> { + if let AddVersionResult::Ok(new_version_id) = result.0 { + // check that it invented a new version ID + for v in existing_versions { + assert_ne!(&new_version_id, v); + } + + // verify that the storage was updated + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!(client.latest_version_id, new_version_id); + + let parent_version_id = existing_versions.last().cloned().unwrap_or_else(Uuid::nil); + let version = txn.get_version(client_key, new_version_id)?.unwrap(); + assert_eq!(version.version_id, new_version_id); + assert_eq!(version.parent_version_id, parent_version_id); + assert_eq!(version.history_segment, expected_history); + } else { + panic!("did not get Ok from add_version: {:?}", result); + } + + assert_eq!(result.1, expected_urgency); + + Ok(()) + } + + #[test] + fn add_version_conflict() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let (client_key, versions) = av_setup(&storage, 3, None, None)?; + + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + + // try to add a child of a version other than the latest assert_eq!( - add_version(txn, client_key, client, parent_version_id, history_segment)?, - AddVersionResult::ExpectedParentVersion(existing_parent_version_id) + add_version(txn, client_key, client, versions[1], vec![3, 6, 9])?.0, + AddVersionResult::ExpectedParentVersion(versions[2]) ); // verify that the storage wasn't updated txn = storage.txn()?; - assert_eq!(txn.get_client(client_key)?, None); assert_eq!( - txn.get_version_by_parent(client_key, parent_version_id)?, - None + txn.get_client(client_key)?.unwrap().latest_version_id, + versions[2] ); + assert_eq!(txn.get_version_by_parent(client_key, versions[2])?, None); Ok(()) } - fn test_av_success(latest_version_id_nil: bool) -> anyhow::Result<()> { - init_logging(); - + #[test] + fn add_version_with_existing_history() -> anyhow::Result<()> { let storage = InMemoryStorage::new(); - let mut txn = storage.txn()?; - let client_key = Uuid::new_v4(); - let parent_version_id = Uuid::new_v4(); - let history_segment = b"abcd".to_vec(); - let latest_version_id = if latest_version_id_nil { - Uuid::nil() - } else { - parent_version_id - }; + let (client_key, versions) = av_setup(&storage, 1, None, None)?; - txn.new_client(client_key, latest_version_id)?; + let mut txn = storage.txn()?; let client = txn.get_client(client_key)?.unwrap(); - let result = add_version( - txn, - client_key, - client, - parent_version_id, - history_segment.clone(), - )?; - if let AddVersionResult::Ok(new_version_id) = result { - // check that it invented a new version ID - assert!(new_version_id != parent_version_id); + let result = add_version(txn, client_key, client, versions[0], vec![3, 6, 9])?; - // verify that the storage was updated - txn = storage.txn()?; - let client = txn.get_client(client_key)?.unwrap(); - assert_eq!(client.latest_version_id, new_version_id); - let version = txn - .get_version_by_parent(client_key, parent_version_id)? - .unwrap(); - assert_eq!(version.version_id, new_version_id); - assert_eq!(version.parent_version_id, parent_version_id); - assert_eq!(version.history_segment, history_segment); - } else { - panic!("did not get Ok from add_version"); - } + av_success_check( + &storage, + client_key, + &versions, + result, + vec![3, 6, 9], + // urgency=high because there are no snapshots yet + SnapshotUrgency::High, + )?; Ok(()) } #[test] - fn av_success_with_existing_history() -> anyhow::Result<()> { - test_av_success(true) + fn add_version_with_no_history() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let (client_key, versions) = av_setup(&storage, 0, None, None)?; + + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + + let parent_version_id = Uuid::nil(); + let result = add_version(txn, client_key, client, parent_version_id, vec![3, 6, 9])?; + + av_success_check( + &storage, + client_key, + &versions, + result, + vec![3, 6, 9], + // urgency=high because there are no snapshots yet + SnapshotUrgency::High, + )?; + + Ok(()) } #[test] - fn av_success_nil_latest_version_id() -> anyhow::Result<()> { - test_av_success(false) + fn add_version_success_recent_snapshot() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + let (client_key, versions) = av_setup(&storage, 1, Some(0), None)?; + + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + + let result = add_version(txn, client_key, client, versions[0], vec![1, 2, 3])?; + + av_success_check( + &storage, + client_key, + &versions, + result, + vec![1, 2, 3], + // no snapshot request since the previous version has a snapshot + SnapshotUrgency::None, + )?; + + Ok(()) + } + + #[test] + fn add_version_success_aged_snapshot() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + // one snapshot, but it was 50 days ago + let (client_key, versions) = av_setup(&storage, 1, Some(0), Some(50))?; + + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + + let result = add_version(txn, client_key, client, versions[0], vec![1, 2, 3])?; + + av_success_check( + &storage, + client_key, + &versions, + result, + vec![1, 2, 3], + // urgency=high due to days since the snapshot + SnapshotUrgency::High, + )?; + + Ok(()) + } + + #[test] + fn add_version_success_snapshot_many_versions_ago() -> anyhow::Result<()> { + let storage = InMemoryStorage::new(); + // one snapshot, but it was 50 versions ago + let (client_key, versions) = av_setup(&storage, 50, Some(0), None)?; + + let mut txn = storage.txn()?; + let client = txn.get_client(client_key)?.unwrap(); + + let result = add_version(txn, client_key, client, versions[49], vec![1, 2, 3])?; + + av_success_check( + &storage, + client_key, + &versions, + result, + vec![1, 2, 3], + // urgency=high due to number of versions since the snapshot + SnapshotUrgency::High, + )?; + + Ok(()) } #[test] @@ -580,8 +807,6 @@ mod test { let client = txn.get_client(client_key)?.unwrap(); add_snapshot(txn, client_key, client, version_ids[0], vec![9, 9, 9])?; - println!("{:?}", version_ids); - // verify the snapshot was not replaced let mut txn = storage.txn()?; let client = txn.get_client(client_key)?.unwrap(); From a0a3f36a16335fdb42f9ca53352b557d015fbc4a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 7 Oct 2021 20:15:48 -0400 Subject: [PATCH 361/548] factor taskdb into multiple modules --- taskchampion/src/{taskdb.rs => taskdb/mod.rs} | 191 +----------------- taskchampion/src/taskdb/ops.rs | 37 ++++ taskchampion/src/taskdb/sync.rs | 145 +++++++++++++ 3 files changed, 192 insertions(+), 181 deletions(-) rename taskchampion/src/{taskdb.rs => taskdb/mod.rs} (71%) create mode 100644 taskchampion/src/taskdb/ops.rs create mode 100644 taskchampion/src/taskdb/sync.rs diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb/mod.rs similarity index 71% rename from taskchampion/src/taskdb.rs rename to taskchampion/src/taskdb/mod.rs index d611433f9..1b84f2c9b 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -1,12 +1,11 @@ -use crate::errors::Error; -use crate::server::{AddVersionResult, GetVersionResult, Server}; -use crate::storage::{Operation, Storage, StorageTxn, TaskMap}; -use log::{info, trace, warn}; -use serde::{Deserialize, Serialize}; +use crate::server::Server; +use crate::storage::{Operation, Storage, TaskMap}; use std::collections::HashSet; -use std::str; use uuid::Uuid; +mod ops; +mod sync; + /// A TaskDb is the backend for a replica. It manages the storage, operations, synchronization, /// and so on, and all the invariants that come with it. It leaves the meaning of particular task /// properties to the replica and task implementations. @@ -14,11 +13,6 @@ pub struct TaskDb { storage: Box, } -#[derive(Serialize, Deserialize, Debug)] -struct Version { - operations: Vec, -} - impl TaskDb { /// Create a new TaskDb with the given backend storage pub fn new(storage: Box) -> TaskDb { @@ -36,7 +30,7 @@ impl TaskDb { pub fn apply(&mut self, op: Operation) -> anyhow::Result<()> { // TODO: differentiate error types here? let mut txn = self.storage.txn()?; - if let err @ Err(_) = TaskDb::apply_op(txn.as_mut(), &op) { + if let err @ Err(_) = ops::apply_op(txn.as_mut(), &op) { return err; } txn.add_operation(op)?; @@ -44,41 +38,6 @@ impl TaskDb { Ok(()) } - fn apply_op(txn: &mut dyn StorageTxn, op: &Operation) -> anyhow::Result<()> { - match op { - Operation::Create { uuid } => { - // insert if the task does not already exist - if !txn.create_task(*uuid)? { - return Err(Error::Database(format!("Task {} already exists", uuid)).into()); - } - } - Operation::Delete { ref uuid } => { - if !txn.delete_task(*uuid)? { - return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); - } - } - Operation::Update { - ref uuid, - ref property, - ref value, - timestamp: _, - } => { - // update if this task exists, otherwise ignore - if let Some(mut task) = txn.get_task(*uuid)? { - match value { - Some(ref val) => task.insert(property.to_string(), val.clone()), - None => task.remove(property), - }; - txn.set_task(*uuid, task)?; - } else { - return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); - } - } - } - - Ok(()) - } - /// Get all tasks. pub fn all_tasks(&mut self) -> anyhow::Result> { let mut txn = self.storage.txn()?; @@ -188,137 +147,7 @@ impl TaskDb { /// Sync to the given server, pulling remote changes and pushing local changes. pub fn sync(&mut self, server: &mut Box) -> anyhow::Result<()> { let mut txn = self.storage.txn()?; - - // retry synchronizing until the server accepts our version (this allows for races between - // replicas trying to sync to the same server). If the server insists on the same base - // version twice, then we have diverged. - let mut requested_parent_version_id = None; - loop { - trace!("beginning sync outer loop"); - let mut base_version_id = txn.base_version()?; - - // first pull changes and "rebase" on top of them - loop { - trace!("beginning sync inner loop"); - if let GetVersionResult::Version { - version_id, - history_segment, - .. - } = server.get_child_version(base_version_id)? - { - let version_str = str::from_utf8(&history_segment).unwrap(); - let version: Version = serde_json::from_str(version_str).unwrap(); - - // apply this verison and update base_version in storage - info!("applying version {:?} from server", version_id); - TaskDb::apply_version(txn.as_mut(), version)?; - txn.set_base_version(version_id)?; - base_version_id = version_id; - } else { - info!("no child versions of {:?}", base_version_id); - // at the moment, no more child versions, so we can try adding our own - break; - } - } - - let operations: Vec = txn.operations()?.to_vec(); - if operations.is_empty() { - info!("no changes to push to server"); - // nothing to sync back to the server.. - break; - } - - trace!("sending {} operations to the server", operations.len()); - - // now make a version of our local changes and push those - let new_version = Version { operations }; - let history_segment = serde_json::to_string(&new_version).unwrap().into(); - info!("sending new version to server"); - match server.add_version(base_version_id, history_segment)? { - AddVersionResult::Ok(new_version_id) => { - info!("version {:?} received by server", new_version_id); - txn.set_base_version(new_version_id)?; - txn.set_operations(vec![])?; - break; - } - AddVersionResult::ExpectedParentVersion(parent_version_id) => { - info!( - "new version rejected; must be based on {:?}", - parent_version_id - ); - if let Some(requested) = requested_parent_version_id { - if parent_version_id == requested { - anyhow::bail!("Server's task history has diverged from this replica"); - } - } - requested_parent_version_id = Some(parent_version_id); - } - } - } - - txn.commit()?; - Ok(()) - } - - fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Result<()> { - // The situation here is that the server has already applied all server operations, and we - // have already applied all local operations, so states have diverged by several - // operations. We need to figure out what operations to apply locally and on the server in - // order to return to the same state. - // - // Operational transforms provide this on an operation-by-operation basis. To break this - // down, we treat each server operation individually, in order. For each such operation, - // we start in this state: - // - // - // base state-* - // / \-server op - // * * - // local / \ / - // ops * * - // / \ / new - // * * local - // local / \ / ops - // state-* * - // new-\ / - // server op *-new local state - // - // This is slightly complicated by the fact that the transform function can return None, - // indicating no operation is required. If this happens for a local op, we can just omit - // it. If it happens for server op, then we must copy the remaining local ops. - let mut local_operations: Vec = txn.operations()?; - for server_op in version.operations.drain(..) { - trace!( - "rebasing local operations onto server operation {:?}", - server_op - ); - let mut new_local_ops = Vec::with_capacity(local_operations.len()); - let mut svr_op = Some(server_op); - for local_op in local_operations.drain(..) { - if let Some(o) = svr_op { - let (new_server_op, new_local_op) = Operation::transform(o, local_op.clone()); - trace!("local operation {:?} -> {:?}", local_op, new_local_op); - svr_op = new_server_op; - if let Some(o) = new_local_op { - new_local_ops.push(o); - } - } else { - trace!( - "local operation {:?} unchanged (server operation consumed)", - local_op - ); - new_local_ops.push(local_op); - } - } - if let Some(o) = svr_op { - if let Err(e) = TaskDb::apply_op(txn, &o) { - warn!("Invalid operation when syncing: {} (ignored)", e); - } - } - local_operations = new_local_ops; - } - txn.set_operations(local_operations)?; - Ok(()) + sync::sync(server, txn.as_mut()) } // functions for supporting tests @@ -675,7 +504,7 @@ mod tests { let uuid = Uuid::new_v4(); db1.apply(Operation::Create { uuid }).unwrap(); db1.apply(Operation::Update { - uuid: uuid, + uuid, property: "title".into(), value: Some("my first task".into()), timestamp: Utc::now(), @@ -692,7 +521,7 @@ mod tests { db1.apply(Operation::Delete { uuid }).unwrap(); db1.apply(Operation::Create { uuid }).unwrap(); db1.apply(Operation::Update { - uuid: uuid, + uuid, property: "title".into(), value: Some("my second task".into()), timestamp: Utc::now(), @@ -701,7 +530,7 @@ mod tests { // and on db2, update a property of the task db2.apply(Operation::Update { - uuid: uuid, + uuid, property: "project".into(), value: Some("personal".into()), timestamp: Utc::now(), diff --git a/taskchampion/src/taskdb/ops.rs b/taskchampion/src/taskdb/ops.rs new file mode 100644 index 000000000..8bfd003e0 --- /dev/null +++ b/taskchampion/src/taskdb/ops.rs @@ -0,0 +1,37 @@ +use crate::errors::Error; +use crate::storage::{Operation, StorageTxn}; + +pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &Operation) -> anyhow::Result<()> { + match op { + Operation::Create { uuid } => { + // insert if the task does not already exist + if !txn.create_task(*uuid)? { + return Err(Error::Database(format!("Task {} already exists", uuid)).into()); + } + } + Operation::Delete { ref uuid } => { + if !txn.delete_task(*uuid)? { + return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); + } + } + Operation::Update { + ref uuid, + ref property, + ref value, + timestamp: _, + } => { + // update if this task exists, otherwise ignore + if let Some(mut task) = txn.get_task(*uuid)? { + match value { + Some(ref val) => task.insert(property.to_string(), val.clone()), + None => task.remove(property), + }; + txn.set_task(*uuid, task)?; + } else { + return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); + } + } + } + + Ok(()) +} diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs new file mode 100644 index 000000000..af076056e --- /dev/null +++ b/taskchampion/src/taskdb/sync.rs @@ -0,0 +1,145 @@ +use super::ops; +use crate::server::{AddVersionResult, GetVersionResult, Server}; +use crate::storage::{Operation, StorageTxn}; +use log::{info, trace, warn}; +use serde::{Deserialize, Serialize}; +use std::str; + +#[derive(Serialize, Deserialize, Debug)] +struct Version { + operations: Vec, +} + +/// Sync to the given server, pulling remote changes and pushing local changes. +pub(super) fn sync(server: &mut Box, txn: &mut dyn StorageTxn) -> anyhow::Result<()> { + // retry synchronizing until the server accepts our version (this allows for races between + // replicas trying to sync to the same server). If the server insists on the same base + // version twice, then we have diverged. + let mut requested_parent_version_id = None; + loop { + trace!("beginning sync outer loop"); + let mut base_version_id = txn.base_version()?; + + // first pull changes and "rebase" on top of them + loop { + trace!("beginning sync inner loop"); + if let GetVersionResult::Version { + version_id, + history_segment, + .. + } = server.get_child_version(base_version_id)? + { + let version_str = str::from_utf8(&history_segment).unwrap(); + let version: Version = serde_json::from_str(version_str).unwrap(); + + // apply this verison and update base_version in storage + info!("applying version {:?} from server", version_id); + apply_version(txn, version)?; + txn.set_base_version(version_id)?; + base_version_id = version_id; + } else { + info!("no child versions of {:?}", base_version_id); + // at the moment, no more child versions, so we can try adding our own + break; + } + } + + let operations: Vec = txn.operations()?.to_vec(); + if operations.is_empty() { + info!("no changes to push to server"); + // nothing to sync back to the server.. + break; + } + + trace!("sending {} operations to the server", operations.len()); + + // now make a version of our local changes and push those + let new_version = Version { operations }; + let history_segment = serde_json::to_string(&new_version).unwrap().into(); + info!("sending new version to server"); + match server.add_version(base_version_id, history_segment)? { + AddVersionResult::Ok(new_version_id) => { + info!("version {:?} received by server", new_version_id); + txn.set_base_version(new_version_id)?; + txn.set_operations(vec![])?; + break; + } + AddVersionResult::ExpectedParentVersion(parent_version_id) => { + info!( + "new version rejected; must be based on {:?}", + parent_version_id + ); + if let Some(requested) = requested_parent_version_id { + if parent_version_id == requested { + anyhow::bail!("Server's task history has diverged from this replica"); + } + } + requested_parent_version_id = Some(parent_version_id); + } + } + } + + txn.commit()?; + Ok(()) +} + +fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Result<()> { + // The situation here is that the server has already applied all server operations, and we + // have already applied all local operations, so states have diverged by several + // operations. We need to figure out what operations to apply locally and on the server in + // order to return to the same state. + // + // Operational transforms provide this on an operation-by-operation basis. To break this + // down, we treat each server operation individually, in order. For each such operation, + // we start in this state: + // + // + // base state-* + // / \-server op + // * * + // local / \ / + // ops * * + // / \ / new + // * * local + // local / \ / ops + // state-* * + // new-\ / + // server op *-new local state + // + // This is slightly complicated by the fact that the transform function can return None, + // indicating no operation is required. If this happens for a local op, we can just omit + // it. If it happens for server op, then we must copy the remaining local ops. + let mut local_operations: Vec = txn.operations()?; + for server_op in version.operations.drain(..) { + trace!( + "rebasing local operations onto server operation {:?}", + server_op + ); + let mut new_local_ops = Vec::with_capacity(local_operations.len()); + let mut svr_op = Some(server_op); + for local_op in local_operations.drain(..) { + if let Some(o) = svr_op { + let (new_server_op, new_local_op) = Operation::transform(o, local_op.clone()); + trace!("local operation {:?} -> {:?}", local_op, new_local_op); + svr_op = new_server_op; + if let Some(o) = new_local_op { + new_local_ops.push(o); + } + } else { + trace!( + "local operation {:?} unchanged (server operation consumed)", + local_op + ); + new_local_ops.push(local_op); + } + } + if let Some(o) = svr_op { + if let Err(e) = ops::apply_op(txn, &o) { + warn!("Invalid operation when syncing: {} (ignored)", e); + } + } + local_operations = new_local_ops; + } + txn.set_operations(local_operations)?; + Ok(()) +} From 536b88c8f4704aeabf6620231b051f0ba8c0634a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 9 Oct 2021 17:59:09 -0400 Subject: [PATCH 362/548] Replace NO_VERSION_ID with NIL_VERSION_ID The docs refer to this as the "nil version ID" so let's do the same. This started out more ambitiously, to change this to `VersionId::NIL`, but that required making VersionId a newtype and all of the implicit conversions from VersionId to Uuid would have to be explicit. That didn't seem wortht the trouble. --- sync-server/src/api/add_snapshot.rs | 8 +++---- sync-server/src/api/add_version.rs | 4 ++-- sync-server/src/api/get_child_version.rs | 4 ++-- sync-server/src/server.rs | 30 ++++++++++++------------ 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/sync-server/src/api/add_snapshot.rs b/sync-server/src/api/add_snapshot.rs index 49de42cf5..2663a8c02 100644 --- a/sync-server/src/api/add_snapshot.rs +++ b/sync-server/src/api/add_snapshot.rs @@ -1,5 +1,5 @@ use crate::api::{client_key_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE}; -use crate::server::{add_snapshot, VersionId, NO_VERSION_ID}; +use crate::server::{add_snapshot, VersionId, NIL_VERSION_ID}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use futures::StreamExt; @@ -52,7 +52,7 @@ pub(crate) async fn service( let client = match txn.get_client(client_key).map_err(failure_to_ise)? { Some(client) => client, None => { - txn.new_client(client_key, NO_VERSION_ID) + txn.new_client(client_key, NIL_VERSION_ID) .map_err(failure_to_ise)?; txn.get_client(client_key).map_err(failure_to_ise)?.unwrap() } @@ -81,7 +81,7 @@ mod test { { let mut txn = storage.txn().unwrap(); txn.new_client(client_key, version_id).unwrap(); - txn.add_version(client_key, version_id, NO_VERSION_ID, vec![])?; + txn.add_version(client_key, version_id, NIL_VERSION_ID, vec![])?; } let server = Server::new(storage); @@ -122,7 +122,7 @@ mod test { // set up the storage contents.. { let mut txn = storage.txn().unwrap(); - txn.new_client(client_key, NO_VERSION_ID).unwrap(); + txn.new_client(client_key, NIL_VERSION_ID).unwrap(); } let server = Server::new(storage); diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 92d86ef52..005fade31 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -2,7 +2,7 @@ use crate::api::{ client_key_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, SNAPSHOT_REQUEST_HEADER, VERSION_ID_HEADER, }; -use crate::server::{add_version, AddVersionResult, SnapshotUrgency, VersionId, NO_VERSION_ID}; +use crate::server::{add_version, AddVersionResult, SnapshotUrgency, VersionId, NIL_VERSION_ID}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use futures::StreamExt; @@ -60,7 +60,7 @@ pub(crate) async fn service( let client = match txn.get_client(client_key).map_err(failure_to_ise)? { Some(client) => client, None => { - txn.new_client(client_key, NO_VERSION_ID) + txn.new_client(client_key, NIL_VERSION_ID) .map_err(failure_to_ise)?; txn.get_client(client_key).map_err(failure_to_ise)?.unwrap() } diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index fd12e1243..b478b2f3e 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -47,7 +47,7 @@ pub(crate) async fn service( #[cfg(test)] mod test { - use crate::server::NO_VERSION_ID; + use crate::server::NIL_VERSION_ID; use crate::storage::{InMemoryStorage, Storage}; use crate::Server; use actix_web::{http::StatusCode, test, App}; @@ -144,7 +144,7 @@ mod test { // but the child of the nil parent_version_id is NOT FOUND, since // there is no snapshot. The tests in crate::server test more // corner cases. - let uri = format!("/v1/client/get-child-version/{}", NO_VERSION_ID); + let uri = format!("/v1/client/get-child-version/{}", NIL_VERSION_ID); let req = test::TestRequest::get() .uri(&uri) .header("X-Client-Key", client_key.to_string()) diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 69a65a088..78399b5a1 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -6,7 +6,7 @@ use chrono::Utc; use uuid::Uuid; /// The distinguished value for "no version" -pub const NO_VERSION_ID: VersionId = Uuid::nil(); +pub const NIL_VERSION_ID: VersionId = Uuid::nil(); /// Number of versions to search back from the latest to find the /// version for a newly-added snapshot. Snapshots for versions older @@ -52,7 +52,7 @@ pub(crate) fn get_child_version<'a>( } // If the requested parentVersionId is the nil UUID .. - if parent_version_id == NO_VERSION_ID { + if parent_version_id == NIL_VERSION_ID { return Ok(match client.snapshot { // ..and snapshotVersionId is nil, the response is _not-found_ (the client has no // versions). @@ -133,7 +133,7 @@ pub(crate) fn add_version<'a>( ); // check if this version is acceptable, under the protection of the transaction - if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { + if client.latest_version_id != NIL_VERSION_ID && parent_version_id != client.latest_version_id { log::debug!("add_version request rejected: mismatched latest_version_id"); return Ok(( AddVersionResult::ExpectedParentVersion(client.latest_version_id), @@ -206,7 +206,7 @@ pub(crate) fn add_snapshot<'a>( let mut vid = client.latest_version_id; loop { - if vid == version_id && version_id != NO_VERSION_ID { + if vid == version_id && version_id != NIL_VERSION_ID { // the new snapshot is for a recent version, so proceed break; } @@ -221,7 +221,7 @@ pub(crate) fn add_snapshot<'a>( } search_len -= 1; - if search_len <= 0 || vid == NO_VERSION_ID { + if search_len <= 0 || vid == NIL_VERSION_ID { // this should not happen in normal operation, so warn about it log::warn!( "rejecting snapshot for version {}: version is too old or no such version", @@ -320,12 +320,12 @@ mod test { let storage = InMemoryStorage::new(); let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); - txn.new_client(client_key, NO_VERSION_ID)?; + txn.new_client(client_key, NIL_VERSION_ID)?; // when no snapshot exists, the first version is NotFound let client = txn.get_client(client_key)?.unwrap(); assert_eq!( - get_child_version(txn, client_key, client, NO_VERSION_ID)?, + get_child_version(txn, client_key, client, NIL_VERSION_ID)?, GetVersionResult::NotFound ); Ok(()) @@ -353,7 +353,7 @@ mod test { // when a snapshot exists, the first version is GONE let client = txn.get_client(client_key)?.unwrap(); assert_eq!( - get_child_version(txn, client_key, client, NO_VERSION_ID)?, + get_child_version(txn, client_key, client, NIL_VERSION_ID)?, GetVersionResult::Gone ); Ok(()) @@ -370,7 +370,7 @@ mod test { // add a parent version, but not the requested child version let parent_version_id = Uuid::new_v4(); txn.new_client(client_key, parent_version_id)?; - txn.add_version(client_key, parent_version_id, NO_VERSION_ID, vec![])?; + txn.add_version(client_key, parent_version_id, NIL_VERSION_ID, vec![])?; let client = txn.get_client(client_key)?.unwrap(); assert_eq!( @@ -660,7 +660,7 @@ mod test { // set up a task DB with one version in it txn.new_client(client_key, version_id)?; - txn.add_version(client_key, version_id, NO_VERSION_ID, vec![])?; + txn.add_version(client_key, version_id, NIL_VERSION_ID, vec![])?; // add a snapshot for that version let client = txn.get_client(client_key)?.unwrap(); @@ -692,7 +692,7 @@ mod test { // set up a task DB with two versions in it txn.new_client(client_key, version_id_2)?; - txn.add_version(client_key, version_id_1, NO_VERSION_ID, vec![])?; + txn.add_version(client_key, version_id_1, NIL_VERSION_ID, vec![])?; txn.add_version(client_key, version_id_2, version_id_1, vec![])?; // add a snapshot for version 1 @@ -725,7 +725,7 @@ mod test { // set up a task DB with two versions in it txn.new_client(client_key, version_id_2)?; - txn.add_version(client_key, version_id_1, NO_VERSION_ID, vec![])?; + txn.add_version(client_key, version_id_1, NIL_VERSION_ID, vec![])?; txn.add_version(client_key, version_id_2, version_id_1, vec![])?; // add a snapshot for unknown version @@ -830,11 +830,11 @@ mod test { let client_key = Uuid::new_v4(); // just set up the client - txn.new_client(client_key, NO_VERSION_ID)?; + txn.new_client(client_key, NIL_VERSION_ID)?; // add a snapshot for the nil version let client = txn.get_client(client_key)?.unwrap(); - add_snapshot(txn, client_key, client, NO_VERSION_ID, vec![9, 9, 9])?; + add_snapshot(txn, client_key, client, NIL_VERSION_ID, vec![9, 9, 9])?; // verify the snapshot does not exist let mut txn = storage.txn()?; @@ -882,7 +882,7 @@ mod test { let mut txn = storage.txn()?; let client_key = Uuid::new_v4(); - txn.new_client(client_key, NO_VERSION_ID)?; + txn.new_client(client_key, NIL_VERSION_ID)?; let client = txn.get_client(client_key)?.unwrap(); assert_eq!(get_snapshot(txn, client_key, client)?, None); From aaac1c33561c23dea94454b2d398fe60d81d0dd1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 9 Oct 2021 17:53:36 -0400 Subject: [PATCH 363/548] Use App::configure to set up actix This avoids the need for the messy cache-control-header macro. Otherwise, it has no effect. --- replica-server-tests/tests/cross-sync.rs | 2 +- sync-server/src/api/add_snapshot.rs | 12 ++++-- sync-server/src/api/add_version.rs | 12 ++++-- sync-server/src/api/get_child_version.rs | 9 +++-- sync-server/src/api/get_snapshot.rs | 6 ++- .../src/bin/taskchampion-sync-server.rs | 24 ++---------- sync-server/src/lib.rs | 38 ++++++++++++++++--- 7 files changed, 63 insertions(+), 40 deletions(-) diff --git a/replica-server-tests/tests/cross-sync.rs b/replica-server-tests/tests/cross-sync.rs index 67e02e10c..ea2aa5fd9 100644 --- a/replica-server-tests/tests/cross-sync.rs +++ b/replica-server-tests/tests/cross-sync.rs @@ -7,7 +7,7 @@ use taskchampion_sync_server::{storage::InMemoryStorage, Server}; async fn cross_sync() -> anyhow::Result<()> { let server = Server::new(Box::new(InMemoryStorage::new())); let httpserver = - HttpServer::new(move || App::new().service(server.service())).bind("0.0.0.0:0")?; + HttpServer::new(move || App::new().configure(|sc| server.config(sc))).bind("0.0.0.0:0")?; // bind was to :0, so the kernel will have selected an unused port let port = httpserver.addrs()[0].port(); diff --git a/sync-server/src/api/add_snapshot.rs b/sync-server/src/api/add_snapshot.rs index 49de42cf5..e86ea31b8 100644 --- a/sync-server/src/api/add_snapshot.rs +++ b/sync-server/src/api/add_snapshot.rs @@ -85,7 +85,8 @@ mod test { } let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = format!("/v1/client/add-snapshot/{}", version_id); let req = test::TestRequest::post() @@ -126,7 +127,8 @@ mod test { } let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; // add a snapshot for a nonexistent version let uri = format!("/v1/client/add-snapshot/{}", version_id); @@ -155,7 +157,8 @@ mod test { let version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = format!("/v1/client/add-snapshot/{}", version_id); let req = test::TestRequest::post() @@ -174,7 +177,8 @@ mod test { let version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = format!("/v1/client/add-snapshot/{}", version_id); let req = test::TestRequest::post() diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 92d86ef52..ed5804932 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -115,7 +115,8 @@ mod test { } let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -156,7 +157,8 @@ mod test { } let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -183,7 +185,8 @@ mod test { let parent_version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -202,7 +205,8 @@ mod test { let parent_version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index fd12e1243..6fabcdd47 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -70,7 +70,8 @@ mod test { } let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() @@ -103,7 +104,8 @@ mod test { let parent_version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() @@ -128,7 +130,8 @@ mod test { txn.new_client(client_key, Uuid::new_v4()).unwrap(); } let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; // the child of an unknown parent_version_id is GONE let uri = format!("/v1/client/get-child-version/{}", parent_version_id); diff --git a/sync-server/src/api/get_snapshot.rs b/sync-server/src/api/get_snapshot.rs index 6de0be427..5d1086610 100644 --- a/sync-server/src/api/get_snapshot.rs +++ b/sync-server/src/api/get_snapshot.rs @@ -59,7 +59,8 @@ mod test { } let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = "/v1/client/snapshot"; let req = test::TestRequest::get() @@ -94,7 +95,8 @@ mod test { } let server = Server::new(storage); - let mut app = test::init_service(App::new().service(server.service())).await; + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; let uri = "/v1/client/snapshot"; let req = test::TestRequest::get() diff --git a/sync-server/src/bin/taskchampion-sync-server.rs b/sync-server/src/bin/taskchampion-sync-server.rs index ef6f5a977..d0ca6c9af 100644 --- a/sync-server/src/bin/taskchampion-sync-server.rs +++ b/sync-server/src/bin/taskchampion-sync-server.rs @@ -1,21 +1,10 @@ #![deny(clippy::all)] -use actix_web::{middleware, middleware::Logger, App, HttpServer}; +use actix_web::{middleware::Logger, App, HttpServer}; use clap::Arg; use taskchampion_sync_server::storage::SqliteStorage; use taskchampion_sync_server::Server; -// The `.wrap` method returns an opaque type, meaning that we can't easily return it from -// functions. So, we must apply these default headers when the app is created, which occurs both -// in `main` and in the tests. To check that those are both doing precisely the same thing, we use -// a macro. This is ugly, and will go away when actix-web is no longer the framework in use. -macro_rules! cache_control_headers { - ($wrapped:expr) => { - $wrapped - .wrap(middleware::DefaultHeaders::new().header("Cache-Control", "no-store, max-age=0")) - }; -} - #[actix_web::main] async fn main() -> anyhow::Result<()> { env_logger::init(); @@ -51,9 +40,9 @@ async fn main() -> anyhow::Result<()> { log::warn!("Serving on port {}", port); HttpServer::new(move || { - cache_control_headers!(App::new()) + App::new() .wrap(Logger::default()) - .service(server.service()) + .configure(|cfg| server.config(cfg)) }) .bind(format!("0.0.0.0:{}", port))? .run() @@ -65,21 +54,16 @@ async fn main() -> anyhow::Result<()> { mod test { use super::*; use actix_web::{test, App}; - use pretty_assertions::assert_eq; use taskchampion_sync_server::storage::InMemoryStorage; #[actix_rt::test] async fn test_index_get() { let server = Server::new(Box::new(InMemoryStorage::new())); - let app = cache_control_headers!(App::new()).service(server.service()); + let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; let req = test::TestRequest::get().uri("/").to_request(); let resp = test::call_service(&mut app, req).await; assert!(resp.status().is_success()); - assert_eq!( - resp.headers().get("Cache-Control").unwrap(), - &"no-store, max-age=0".to_string() - ) } } diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs index c817da29e..535f5b39b 100644 --- a/sync-server/src/lib.rs +++ b/sync-server/src/lib.rs @@ -5,7 +5,7 @@ mod server; pub mod storage; use crate::storage::Storage; -use actix_web::{get, web, Responder, Scope}; +use actix_web::{get, middleware, web, Responder}; use api::{api_scope, ServerState}; #[get("/")] @@ -28,17 +28,43 @@ impl Server { } /// Get an Actix-web service for this server. - pub fn service(&self) -> Scope { - web::scope("") - .data(self.storage.clone()) - .service(index) - .service(api_scope()) + pub fn config(&self, cfg: &mut web::ServiceConfig) { + cfg.service( + web::scope("") + .data(self.storage.clone()) + .wrap( + middleware::DefaultHeaders::new() + .header("Cache-Control", "no-store, max-age=0"), + ) + .service(index) + .service(api_scope()), + ); } } #[cfg(test)] mod test { + use super::*; + use crate::storage::InMemoryStorage; + use actix_web::{test, App}; + use pretty_assertions::assert_eq; + pub(crate) fn init_logging() { let _ = env_logger::builder().is_test(true).try_init(); } + + #[actix_rt::test] + async fn test_cache_control() { + let server = Server::new(Box::new(InMemoryStorage::new())); + let app = App::new().configure(|sc| server.config(sc)); + let mut app = test::init_service(app).await; + + let req = test::TestRequest::get().uri("/").to_request(); + let resp = test::call_service(&mut app, req).await; + assert!(resp.status().is_success()); + assert_eq!( + resp.headers().get("Cache-Control").unwrap(), + &"no-store, max-age=0".to_string() + ) + } } From 4d19ca7bdb39a5ab7d3d21f05d637d19d6e6a0b1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 6 Oct 2021 04:01:22 +0000 Subject: [PATCH 364/548] add server-side config --snapshot-{days,versions} --- docs/src/running-sync-server.md | 3 + replica-server-tests/tests/cross-sync.rs | 2 +- sync-server/src/api/add_snapshot.rs | 23 +- sync-server/src/api/add_version.rs | 25 ++- sync-server/src/api/get_child_version.rs | 21 +- sync-server/src/api/get_snapshot.rs | 11 +- sync-server/src/api/mod.rs | 9 +- .../src/bin/taskchampion-sync-server.rs | 27 ++- sync-server/src/lib.rs | 42 +++- sync-server/src/server.rs | 209 ++++++++++++++---- taskchampion/src/server/local.rs | 14 +- taskchampion/src/server/test.rs | 6 +- taskchampion/src/server/types.rs | 2 +- taskchampion/src/storage/mod.rs | 2 +- 14 files changed, 305 insertions(+), 91 deletions(-) diff --git a/docs/src/running-sync-server.md b/docs/src/running-sync-server.md index 2cc3b7454..e8b8c56ce 100644 --- a/docs/src/running-sync-server.md +++ b/docs/src/running-sync-server.md @@ -6,3 +6,6 @@ Run `taskchampion-sync-server` to start the sync server. Use `--port` to specify the port it should listen on, and `--data-dir` to specify the directory which it should store its data. It only serves HTTP; the expectation is that a frontend proxy will be used for HTTPS support. + +The server has optional parameters `--snapshot-days` and `--snapshot-version`, giving the target number of days and versions, respectively, between snapshots of the client state. +The default values for these parameters are generally adequate. diff --git a/replica-server-tests/tests/cross-sync.rs b/replica-server-tests/tests/cross-sync.rs index ea2aa5fd9..8ade694da 100644 --- a/replica-server-tests/tests/cross-sync.rs +++ b/replica-server-tests/tests/cross-sync.rs @@ -5,7 +5,7 @@ use taskchampion_sync_server::{storage::InMemoryStorage, Server}; #[actix_rt::test] async fn cross_sync() -> anyhow::Result<()> { - let server = Server::new(Box::new(InMemoryStorage::new())); + let server = Server::new(Default::default(), Box::new(InMemoryStorage::new())); let httpserver = HttpServer::new(move || App::new().configure(|sc| server.config(sc))).bind("0.0.0.0:0")?; diff --git a/sync-server/src/api/add_snapshot.rs b/sync-server/src/api/add_snapshot.rs index e04fc0872..5c47b6c38 100644 --- a/sync-server/src/api/add_snapshot.rs +++ b/sync-server/src/api/add_snapshot.rs @@ -2,6 +2,7 @@ use crate::api::{client_key_header, failure_to_ise, ServerState, SNAPSHOT_CONTEN use crate::server::{add_snapshot, VersionId, NIL_VERSION_ID}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use futures::StreamExt; +use std::sync::Arc; /// Max snapshot size: 100MB const MAX_SIZE: usize = 100 * 1024 * 1024; @@ -17,7 +18,7 @@ const MAX_SIZE: usize = 100 * 1024 * 1024; #[post("/v1/client/add-snapshot/{version_id}")] pub(crate) async fn service( req: HttpRequest, - server_state: web::Data, + server_state: web::Data>, web::Path((version_id,)): web::Path<(VersionId,)>, mut payload: web::Payload, ) -> Result { @@ -46,7 +47,7 @@ pub(crate) async fn service( // note that we do not open the transaction until the body has been read // completely, to avoid blocking other storage access while that data is // in transit. - let mut txn = server_state.txn().map_err(failure_to_ise)?; + let mut txn = server_state.storage.txn().map_err(failure_to_ise)?; // get, or create, the client let client = match txn.get_client(client_key).map_err(failure_to_ise)? { @@ -58,7 +59,15 @@ pub(crate) async fn service( } }; - add_snapshot(txn, client_key, client, version_id, body.to_vec()).map_err(failure_to_ise)?; + add_snapshot( + txn, + &server_state.config, + client_key, + client, + version_id, + body.to_vec(), + ) + .map_err(failure_to_ise)?; Ok(HttpResponse::Ok().body("")) } @@ -84,7 +93,7 @@ mod test { txn.add_version(client_key, version_id, NIL_VERSION_ID, vec![])?; } - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; @@ -126,7 +135,7 @@ mod test { txn.new_client(client_key, NIL_VERSION_ID).unwrap(); } - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; @@ -156,7 +165,7 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; @@ -176,7 +185,7 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 8c46f1458..ecf8a18dc 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -5,6 +5,7 @@ use crate::api::{ use crate::server::{add_version, AddVersionResult, SnapshotUrgency, VersionId, NIL_VERSION_ID}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use futures::StreamExt; +use std::sync::Arc; /// Max history segment size: 100MB const MAX_SIZE: usize = 100 * 1024 * 1024; @@ -25,7 +26,7 @@ const MAX_SIZE: usize = 100 * 1024 * 1024; #[post("/v1/client/add-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, - server_state: web::Data, + server_state: web::Data>, web::Path((parent_version_id,)): web::Path<(VersionId,)>, mut payload: web::Payload, ) -> Result { @@ -54,7 +55,7 @@ pub(crate) async fn service( // note that we do not open the transaction until the body has been read // completely, to avoid blocking other storage access while that data is // in transit. - let mut txn = server_state.txn().map_err(failure_to_ise)?; + let mut txn = server_state.storage.txn().map_err(failure_to_ise)?; // get, or create, the client let client = match txn.get_client(client_key).map_err(failure_to_ise)? { @@ -66,9 +67,15 @@ pub(crate) async fn service( } }; - let (result, snap_urgency) = - add_version(txn, client_key, client, parent_version_id, body.to_vec()) - .map_err(failure_to_ise)?; + let (result, snap_urgency) = add_version( + txn, + &server_state.config, + client_key, + client, + parent_version_id, + body.to_vec(), + ) + .map_err(failure_to_ise)?; Ok(match result { AddVersionResult::Ok(version_id) => { @@ -114,7 +121,7 @@ mod test { txn.new_client(client_key, Uuid::nil()).unwrap(); } - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; @@ -156,7 +163,7 @@ mod test { txn.new_client(client_key, version_id).unwrap(); } - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; @@ -184,7 +191,7 @@ mod test { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; @@ -204,7 +211,7 @@ mod test { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index d146a2024..2c2f6651e 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -4,6 +4,7 @@ use crate::api::{ }; use crate::server::{get_child_version, GetVersionResult, VersionId}; use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; +use std::sync::Arc; /// Get a child version. /// @@ -16,10 +17,10 @@ use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; #[get("/v1/client/get-child-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, - server_state: web::Data, + server_state: web::Data>, web::Path((parent_version_id,)): web::Path<(VersionId,)>, ) -> Result { - let mut txn = server_state.txn().map_err(failure_to_ise)?; + let mut txn = server_state.storage.txn().map_err(failure_to_ise)?; let client_key = client_key_header(&req)?; @@ -28,8 +29,14 @@ pub(crate) async fn service( .map_err(failure_to_ise)? .ok_or_else(|| error::ErrorNotFound("no such client"))?; - return match get_child_version(txn, client_key, client, parent_version_id) - .map_err(failure_to_ise)? + return match get_child_version( + txn, + &server_state.config, + client_key, + client, + parent_version_id, + ) + .map_err(failure_to_ise)? { GetVersionResult::Success { version_id, @@ -69,7 +76,7 @@ mod test { .unwrap(); } - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; @@ -103,7 +110,7 @@ mod test { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); let storage: Box = Box::new(InMemoryStorage::new()); - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; @@ -129,7 +136,7 @@ mod test { let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap(); } - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; diff --git a/sync-server/src/api/get_snapshot.rs b/sync-server/src/api/get_snapshot.rs index 5d1086610..cf76655c8 100644 --- a/sync-server/src/api/get_snapshot.rs +++ b/sync-server/src/api/get_snapshot.rs @@ -3,6 +3,7 @@ use crate::api::{ }; use crate::server::get_snapshot; use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; +use std::sync::Arc; /// Get a snapshot. /// @@ -15,9 +16,9 @@ use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; #[get("/v1/client/snapshot")] pub(crate) async fn service( req: HttpRequest, - server_state: web::Data, + server_state: web::Data>, ) -> Result { - let mut txn = server_state.txn().map_err(failure_to_ise)?; + let mut txn = server_state.storage.txn().map_err(failure_to_ise)?; let client_key = client_key_header(&req)?; @@ -27,7 +28,7 @@ pub(crate) async fn service( .ok_or_else(|| error::ErrorNotFound("no such client"))?; if let Some((version_id, data)) = - get_snapshot(txn, client_key, client).map_err(failure_to_ise)? + get_snapshot(txn, &server_state.config, client_key, client).map_err(failure_to_ise)? { Ok(HttpResponse::Ok() .content_type(SNAPSHOT_CONTENT_TYPE) @@ -58,7 +59,7 @@ mod test { txn.new_client(client_key, Uuid::new_v4()).unwrap(); } - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; @@ -94,7 +95,7 @@ mod test { .unwrap(); } - let server = Server::new(storage); + let server = Server::new(Default::default(), storage); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index b3157438a..1a12b2826 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -1,7 +1,7 @@ use crate::server::ClientKey; use crate::storage::Storage; +use crate::ServerConfig; use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope}; -use std::sync::Arc; mod add_snapshot; mod add_version; @@ -27,8 +27,11 @@ pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; /// The header name for parent version ID pub(crate) const SNAPSHOT_REQUEST_HEADER: &str = "X-Snapshot-Request"; -/// The type containing a reference to the Storage object in the Actix state. -pub(crate) type ServerState = Arc; +/// The type containing a reference to the persistent state for the server +pub(crate) struct ServerState { + pub(crate) storage: Box, + pub(crate) config: ServerConfig, +} pub(crate) fn api_scope() -> Scope { web::scope("") diff --git a/sync-server/src/bin/taskchampion-sync-server.rs b/sync-server/src/bin/taskchampion-sync-server.rs index d0ca6c9af..80ca56d68 100644 --- a/sync-server/src/bin/taskchampion-sync-server.rs +++ b/sync-server/src/bin/taskchampion-sync-server.rs @@ -3,7 +3,7 @@ use actix_web::{middleware::Logger, App, HttpServer}; use clap::Arg; use taskchampion_sync_server::storage::SqliteStorage; -use taskchampion_sync_server::Server; +use taskchampion_sync_server::{Server, ServerConfig}; #[actix_web::main] async fn main() -> anyhow::Result<()> { @@ -31,12 +31,33 @@ async fn main() -> anyhow::Result<()> { .takes_value(true) .required(true), ) + .arg( + Arg::with_name("snapshot-versions") + .long("snapshot-versions") + .value_name("NUM") + .help("Target number of versions between snapshots") + .default_value("100") + .takes_value(true) + .required(false), + ) + .arg( + Arg::with_name("snapshot-days") + .long("snapshot-days") + .value_name("NUM") + .help("Target number of days between snapshots") + .default_value("14") + .takes_value(true) + .required(false), + ) .get_matches(); let data_dir = matches.value_of("data-dir").unwrap(); let port = matches.value_of("port").unwrap(); + let snapshot_versions = matches.value_of("snapshot-versions").unwrap(); + let snapshot_days = matches.value_of("snapshot-versions").unwrap(); - let server = Server::new(Box::new(SqliteStorage::new(data_dir)?)); + let config = ServerConfig::from_args(snapshot_days, snapshot_versions)?; + let server = Server::new(config, Box::new(SqliteStorage::new(data_dir)?)); log::warn!("Serving on port {}", port); HttpServer::new(move || { @@ -58,7 +79,7 @@ mod test { #[actix_rt::test] async fn test_index_get() { - let server = Server::new(Box::new(InMemoryStorage::new())); + let server = Server::new(Default::default(), Box::new(InMemoryStorage::new())); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs index 535f5b39b..012f0c43f 100644 --- a/sync-server/src/lib.rs +++ b/sync-server/src/lib.rs @@ -6,7 +6,9 @@ pub mod storage; use crate::storage::Storage; use actix_web::{get, middleware, web, Responder}; +use anyhow::Context; use api::{api_scope, ServerState}; +use std::sync::Arc; #[get("/")] async fn index() -> impl Responder { @@ -16,14 +18,44 @@ async fn index() -> impl Responder { /// A Server represents a sync server. #[derive(Clone)] pub struct Server { - storage: ServerState, + server_state: Arc, +} + +/// ServerConfig contains configuration parameters for the server. +pub struct ServerConfig { + /// Target number of days between snapshots. + pub snapshot_days: i64, + + /// Target number of versions between snapshots. + pub snapshot_versions: u32, +} + +impl Default for ServerConfig { + fn default() -> Self { + ServerConfig { + snapshot_days: 14, + snapshot_versions: 100, + } + } +} +impl ServerConfig { + pub fn from_args(snapshot_days: &str, snapshot_versions: &str) -> anyhow::Result { + Ok(ServerConfig { + snapshot_days: snapshot_days + .parse() + .context("--snapshot-days must be a number")?, + snapshot_versions: snapshot_versions + .parse() + .context("--snapshot-days must be a number")?, + }) + } } impl Server { /// Create a new sync server with the given storage implementation. - pub fn new(storage: Box) -> Self { + pub fn new(config: ServerConfig, storage: Box) -> Self { Self { - storage: storage.into(), + server_state: Arc::new(ServerState { config, storage }), } } @@ -31,7 +63,7 @@ impl Server { pub fn config(&self, cfg: &mut web::ServiceConfig) { cfg.service( web::scope("") - .data(self.storage.clone()) + .data(self.server_state.clone()) .wrap( middleware::DefaultHeaders::new() .header("Cache-Control", "no-store, max-age=0"), @@ -55,7 +87,7 @@ mod test { #[actix_rt::test] async fn test_cache_control() { - let server = Server::new(Box::new(InMemoryStorage::new())); + let server = Server::new(Default::default(), Box::new(InMemoryStorage::new())); let app = App::new().configure(|sc| server.config(sc)); let mut app = test::init_service(app).await; diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index 78399b5a1..c1947e0ca 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -2,6 +2,7 @@ //! invariants, and so on. This does not implement the HTTP-specific portions; those //! are in [`crate::api`]. See the protocol documentation for details. use crate::storage::{Client, Snapshot, StorageTxn}; +use crate::ServerConfig; // TODO: move here use chrono::Utc; use uuid::Uuid; @@ -13,12 +14,6 @@ pub const NIL_VERSION_ID: VersionId = Uuid::nil(); /// than this will be rejected. const SNAPSHOT_SEARCH_LEN: i32 = 5; -/// Maximum number of days between snapshots -const SNAPSHOT_DAYS: i64 = 14; - -/// Maximum number of versions between snapshots -const SNAPSHOT_VERSIONS: u32 = 30; - pub(crate) type HistorySegment = Vec; pub(crate) type ClientKey = Uuid; pub(crate) type VersionId = Uuid; @@ -38,6 +33,7 @@ pub(crate) enum GetVersionResult { /// Implementation of the GetChildVersion protocol transaction pub(crate) fn get_child_version<'a>( mut txn: Box, + _config: &ServerConfig, client_key: ClientKey, client: Client, parent_version_id: VersionId, @@ -96,10 +92,10 @@ pub(crate) enum SnapshotUrgency { impl SnapshotUrgency { /// Calculate the urgency for a snapshot based on its age in days - fn for_days(days: i64) -> Self { - if days >= SNAPSHOT_DAYS * 3 / 2 { + fn for_days(config: &ServerConfig, days: i64) -> Self { + if days >= config.snapshot_days * 3 / 2 { SnapshotUrgency::High - } else if days >= SNAPSHOT_DAYS { + } else if days >= config.snapshot_days { SnapshotUrgency::Low } else { SnapshotUrgency::None @@ -107,10 +103,10 @@ impl SnapshotUrgency { } /// Calculate the urgency for a snapshot based on its age in versions - fn for_versions_since(versions_since: u32) -> Self { - if versions_since >= SNAPSHOT_VERSIONS * 3 / 2 { + fn for_versions_since(config: &ServerConfig, versions_since: u32) -> Self { + if versions_since >= config.snapshot_versions * 3 / 2 { SnapshotUrgency::High - } else if versions_since >= SNAPSHOT_VERSIONS { + } else if versions_since >= config.snapshot_versions { SnapshotUrgency::Low } else { SnapshotUrgency::None @@ -121,6 +117,7 @@ impl SnapshotUrgency { /// Implementation of the AddVersion protocol transaction pub(crate) fn add_version<'a>( mut txn: Box, + config: &ServerConfig, client_key: ClientKey, client: Client, parent_version_id: VersionId, @@ -156,7 +153,7 @@ pub(crate) fn add_version<'a>( let time_urgency = match client.snapshot { None => SnapshotUrgency::High, Some(Snapshot { timestamp, .. }) => { - SnapshotUrgency::for_days((Utc::now() - timestamp).num_days()) + SnapshotUrgency::for_days(config, (Utc::now() - timestamp).num_days()) } }; @@ -164,7 +161,7 @@ pub(crate) fn add_version<'a>( let version_urgency = match client.snapshot { None => SnapshotUrgency::High, Some(Snapshot { versions_since, .. }) => { - SnapshotUrgency::for_versions_since(versions_since) + SnapshotUrgency::for_versions_since(config, versions_since) } }; @@ -177,6 +174,7 @@ pub(crate) fn add_version<'a>( /// Implementation of the AddSnapshot protocol transaction pub(crate) fn add_snapshot<'a>( mut txn: Box, + _config: &ServerConfig, client_key: ClientKey, client: Client, version_id: VersionId, @@ -261,6 +259,7 @@ pub(crate) fn add_snapshot<'a>( /// Implementation of the GetSnapshot protocol transaction pub(crate) fn get_snapshot<'a>( mut txn: Box, + _config: &ServerConfig, client_key: ClientKey, client: Client, ) -> anyhow::Result)>> { @@ -297,18 +296,29 @@ mod test { #[test] fn snapshot_urgency_for_days() { use SnapshotUrgency::*; - assert_eq!(SnapshotUrgency::for_days(0), None); - assert_eq!(SnapshotUrgency::for_days(SNAPSHOT_DAYS), Low); - assert_eq!(SnapshotUrgency::for_days(SNAPSHOT_DAYS * 2), High); + let config = ServerConfig::default(); + assert_eq!(SnapshotUrgency::for_days(&config, 0), None); + assert_eq!( + SnapshotUrgency::for_days(&config, config.snapshot_days), + Low + ); + assert_eq!( + SnapshotUrgency::for_days(&config, config.snapshot_days * 2), + High + ); } #[test] fn snapshot_urgency_for_versions_since() { use SnapshotUrgency::*; - assert_eq!(SnapshotUrgency::for_versions_since(0), None); - assert_eq!(SnapshotUrgency::for_versions_since(SNAPSHOT_VERSIONS), Low); + let config = ServerConfig::default(); + assert_eq!(SnapshotUrgency::for_versions_since(&config, 0), None); assert_eq!( - SnapshotUrgency::for_versions_since(SNAPSHOT_VERSIONS * 2), + SnapshotUrgency::for_versions_since(&config, config.snapshot_versions), + Low + ); + assert_eq!( + SnapshotUrgency::for_versions_since(&config, config.snapshot_versions * 2), High ); } @@ -325,7 +335,13 @@ mod test { // when no snapshot exists, the first version is NotFound let client = txn.get_client(client_key)?.unwrap(); assert_eq!( - get_child_version(txn, client_key, client, NIL_VERSION_ID)?, + get_child_version( + txn, + &ServerConfig::default(), + client_key, + client, + NIL_VERSION_ID + )?, GetVersionResult::NotFound ); Ok(()) @@ -353,7 +369,13 @@ mod test { // when a snapshot exists, the first version is GONE let client = txn.get_client(client_key)?.unwrap(); assert_eq!( - get_child_version(txn, client_key, client, NIL_VERSION_ID)?, + get_child_version( + txn, + &ServerConfig::default(), + client_key, + client, + NIL_VERSION_ID + )?, GetVersionResult::Gone ); Ok(()) @@ -374,7 +396,13 @@ mod test { let client = txn.get_client(client_key)?.unwrap(); assert_eq!( - get_child_version(txn, client_key, client, parent_version_id)?, + get_child_version( + txn, + &ServerConfig::default(), + client_key, + client, + parent_version_id + )?, GetVersionResult::NotFound ); Ok(()) @@ -395,7 +423,13 @@ mod test { let client = txn.get_client(client_key)?.unwrap(); assert_eq!( - get_child_version(txn, client_key, client, parent_version_id)?, + get_child_version( + txn, + &ServerConfig::default(), + client_key, + client, + parent_version_id + )?, GetVersionResult::Gone ); Ok(()) @@ -422,7 +456,13 @@ mod test { let client = txn.get_client(client_key)?.unwrap(); assert_eq!( - get_child_version(txn, client_key, client, parent_version_id)?, + get_child_version( + txn, + &ServerConfig::default(), + client_key, + client, + parent_version_id + )?, GetVersionResult::Success { version_id, parent_version_id, @@ -516,7 +556,15 @@ mod test { // try to add a child of a version other than the latest assert_eq!( - add_version(txn, client_key, client, versions[1], vec![3, 6, 9])?.0, + add_version( + txn, + &ServerConfig::default(), + client_key, + client, + versions[1], + vec![3, 6, 9] + )? + .0, AddVersionResult::ExpectedParentVersion(versions[2]) ); @@ -539,7 +587,14 @@ mod test { let mut txn = storage.txn()?; let client = txn.get_client(client_key)?.unwrap(); - let result = add_version(txn, client_key, client, versions[0], vec![3, 6, 9])?; + let result = add_version( + txn, + &ServerConfig::default(), + client_key, + client, + versions[0], + vec![3, 6, 9], + )?; av_success_check( &storage, @@ -563,7 +618,14 @@ mod test { let client = txn.get_client(client_key)?.unwrap(); let parent_version_id = Uuid::nil(); - let result = add_version(txn, client_key, client, parent_version_id, vec![3, 6, 9])?; + let result = add_version( + txn, + &ServerConfig::default(), + client_key, + client, + parent_version_id, + vec![3, 6, 9], + )?; av_success_check( &storage, @@ -586,7 +648,14 @@ mod test { let mut txn = storage.txn()?; let client = txn.get_client(client_key)?.unwrap(); - let result = add_version(txn, client_key, client, versions[0], vec![1, 2, 3])?; + let result = add_version( + txn, + &ServerConfig::default(), + client_key, + client, + versions[0], + vec![1, 2, 3], + )?; av_success_check( &storage, @@ -610,7 +679,14 @@ mod test { let mut txn = storage.txn()?; let client = txn.get_client(client_key)?.unwrap(); - let result = add_version(txn, client_key, client, versions[0], vec![1, 2, 3])?; + let result = add_version( + txn, + &ServerConfig::default(), + client_key, + client, + versions[0], + vec![1, 2, 3], + )?; av_success_check( &storage, @@ -634,7 +710,17 @@ mod test { let mut txn = storage.txn()?; let client = txn.get_client(client_key)?.unwrap(); - let result = add_version(txn, client_key, client, versions[49], vec![1, 2, 3])?; + let result = add_version( + txn, + &ServerConfig { + snapshot_versions: 30, + ..ServerConfig::default() + }, + client_key, + client, + versions[49], + vec![1, 2, 3], + )?; av_success_check( &storage, @@ -664,7 +750,14 @@ mod test { // add a snapshot for that version let client = txn.get_client(client_key)?.unwrap(); - add_snapshot(txn, client_key, client, version_id, vec![1, 2, 3])?; + add_snapshot( + txn, + &ServerConfig::default(), + client_key, + client, + version_id, + vec![1, 2, 3], + )?; // verify the snapshot let mut txn = storage.txn()?; @@ -697,7 +790,14 @@ mod test { // add a snapshot for version 1 let client = txn.get_client(client_key)?.unwrap(); - add_snapshot(txn, client_key, client, version_id_1, vec![1, 2, 3])?; + add_snapshot( + txn, + &ServerConfig::default(), + client_key, + client, + version_id_1, + vec![1, 2, 3], + )?; // verify the snapshot let mut txn = storage.txn()?; @@ -731,7 +831,14 @@ mod test { // add a snapshot for unknown version let client = txn.get_client(client_key)?.unwrap(); let version_id_unk = Uuid::new_v4(); - add_snapshot(txn, client_key, client, version_id_unk, vec![1, 2, 3])?; + add_snapshot( + txn, + &ServerConfig::default(), + client_key, + client, + version_id_unk, + vec![1, 2, 3], + )?; // verify the snapshot does not exist let mut txn = storage.txn()?; @@ -763,7 +870,14 @@ mod test { // add a snapshot for the earliest of those let client = txn.get_client(client_key)?.unwrap(); - add_snapshot(txn, client_key, client, version_ids[0], vec![1, 2, 3])?; + add_snapshot( + txn, + &ServerConfig::default(), + client_key, + client, + version_ids[0], + vec![1, 2, 3], + )?; // verify the snapshot does not exist let mut txn = storage.txn()?; @@ -805,7 +919,14 @@ mod test { // add a snapshot for the earliest of those let client = txn.get_client(client_key)?.unwrap(); - add_snapshot(txn, client_key, client, version_ids[0], vec![9, 9, 9])?; + add_snapshot( + txn, + &ServerConfig::default(), + client_key, + client, + version_ids[0], + vec![9, 9, 9], + )?; // verify the snapshot was not replaced let mut txn = storage.txn()?; @@ -834,7 +955,14 @@ mod test { // add a snapshot for the nil version let client = txn.get_client(client_key)?.unwrap(); - add_snapshot(txn, client_key, client, NIL_VERSION_ID, vec![9, 9, 9])?; + add_snapshot( + txn, + &ServerConfig::default(), + client_key, + client, + NIL_VERSION_ID, + vec![9, 9, 9], + )?; // verify the snapshot does not exist let mut txn = storage.txn()?; @@ -867,7 +995,7 @@ mod test { let client = txn.get_client(client_key)?.unwrap(); assert_eq!( - get_snapshot(txn, client_key, client)?, + get_snapshot(txn, &ServerConfig::default(), client_key, client)?, Some((snapshot_version_id, data.clone())) ); @@ -885,7 +1013,10 @@ mod test { txn.new_client(client_key, NIL_VERSION_ID)?; let client = txn.get_client(client_key)?.unwrap(); - assert_eq!(get_snapshot(txn, client_key, client)?, None); + assert_eq!( + get_snapshot(txn, &ServerConfig::default(), client_key, client)?, + None + ); Ok(()) } diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 966b93bd6..ccd2c8d3c 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -1,5 +1,5 @@ use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, + AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NIL_VERSION_ID, }; use crate::storage::sqlite::StoredUuid; use anyhow::Context; @@ -53,7 +53,7 @@ impl LocalServer { |r| r.get(0), ) .optional()?; - Ok(result.map(|x| x.0).unwrap_or(NO_VERSION_ID)) + Ok(result.map(|x| x.0).unwrap_or(NIL_VERSION_ID)) } fn set_latest_version_id(&mut self, version_id: VersionId) -> anyhow::Result<()> { @@ -122,7 +122,7 @@ impl Server for LocalServer { // check the parent_version_id for linearity let latest_version_id = self.get_latest_version_id()?; - if latest_version_id != NO_VERSION_ID && parent_version_id != latest_version_id { + if latest_version_id != NIL_VERSION_ID && parent_version_id != latest_version_id { return Ok(AddVersionResult::ExpectedParentVersion(latest_version_id)); } @@ -166,7 +166,7 @@ mod test { fn test_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; let mut server = LocalServer::new(&tmp_dir.path())?; - let child_version = server.get_child_version(NO_VERSION_ID)?; + let child_version = server.get_child_version(NIL_VERSION_ID)?; assert_eq!(child_version, GetVersionResult::NoSuchVersion); Ok(()) } @@ -176,17 +176,17 @@ mod test { let tmp_dir = TempDir::new()?; let mut server = LocalServer::new(&tmp_dir.path())?; let history = b"1234".to_vec(); - match server.add_version(NO_VERSION_ID, history.clone())? { + match server.add_version(NIL_VERSION_ID, history.clone())? { AddVersionResult::ExpectedParentVersion(_) => { panic!("should have accepted the version") } AddVersionResult::Ok(version_id) => { - let new_version = server.get_child_version(NO_VERSION_ID)?; + let new_version = server.get_child_version(NIL_VERSION_ID)?; assert_eq!( new_version, GetVersionResult::Version { version_id, - parent_version_id: NO_VERSION_ID, + parent_version_id: NIL_VERSION_ID, history_segment: history, } ); diff --git a/taskchampion/src/server/test.rs b/taskchampion/src/server/test.rs index 798a54c1e..18a7e62bd 100644 --- a/taskchampion/src/server/test.rs +++ b/taskchampion/src/server/test.rs @@ -1,5 +1,5 @@ use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, + AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NIL_VERSION_ID, }; use std::collections::HashMap; use uuid::Uuid; @@ -20,7 +20,7 @@ impl TestServer { /// A test server has no notion of clients, signatures, encryption, etc. pub fn new() -> TestServer { TestServer { - latest_version_id: NO_VERSION_ID, + latest_version_id: NIL_VERSION_ID, versions: HashMap::new(), } } @@ -38,7 +38,7 @@ impl Server for TestServer { // no signature validation // check the parent_version_id for linearity - if self.latest_version_id != NO_VERSION_ID { + if self.latest_version_id != NIL_VERSION_ID { if parent_version_id != self.latest_version_id { return Ok(AddVersionResult::ExpectedParentVersion( self.latest_version_id, diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index 466e286a5..5995dfed5 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -4,7 +4,7 @@ use uuid::Uuid; pub type VersionId = Uuid; /// The distinguished value for "no version" -pub const NO_VERSION_ID: VersionId = Uuid::nil(); +pub const NIL_VERSION_ID: VersionId = Uuid::nil(); /// A segment in the history of this task database, in the form of a sequence of operations. This /// data is pre-encoded, and from the protocol level appears as a sequence of bytes. diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index 18e64a9bb..0b3e3da32 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -36,7 +36,7 @@ fn taskmap_with(mut properties: Vec<(String, String)>) -> TaskMap { pub use crate::server::VersionId; /// The default for base_version. -pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NO_VERSION_ID; +pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NIL_VERSION_ID; /// A Storage transaction, in which storage operations are performed. /// From 329c0d0aefc1c1c5d0c3058a05a27ce03648677f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Oct 2021 01:33:45 +0000 Subject: [PATCH 365/548] move ServerConfig to crate::server --- sync-server/src/lib.rs | 33 ++------------------------------- sync-server/src/server.rs | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs index 012f0c43f..0ce33dbfe 100644 --- a/sync-server/src/lib.rs +++ b/sync-server/src/lib.rs @@ -6,10 +6,11 @@ pub mod storage; use crate::storage::Storage; use actix_web::{get, middleware, web, Responder}; -use anyhow::Context; use api::{api_scope, ServerState}; use std::sync::Arc; +pub use server::ServerConfig; + #[get("/")] async fn index() -> impl Responder { format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION")) @@ -21,36 +22,6 @@ pub struct Server { server_state: Arc, } -/// ServerConfig contains configuration parameters for the server. -pub struct ServerConfig { - /// Target number of days between snapshots. - pub snapshot_days: i64, - - /// Target number of versions between snapshots. - pub snapshot_versions: u32, -} - -impl Default for ServerConfig { - fn default() -> Self { - ServerConfig { - snapshot_days: 14, - snapshot_versions: 100, - } - } -} -impl ServerConfig { - pub fn from_args(snapshot_days: &str, snapshot_versions: &str) -> anyhow::Result { - Ok(ServerConfig { - snapshot_days: snapshot_days - .parse() - .context("--snapshot-days must be a number")?, - snapshot_versions: snapshot_versions - .parse() - .context("--snapshot-days must be a number")?, - }) - } -} - impl Server { /// Create a new sync server with the given storage implementation. pub fn new(config: ServerConfig, storage: Box) -> Self { diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index c1947e0ca..f7b40a16a 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -2,7 +2,7 @@ //! invariants, and so on. This does not implement the HTTP-specific portions; those //! are in [`crate::api`]. See the protocol documentation for details. use crate::storage::{Client, Snapshot, StorageTxn}; -use crate::ServerConfig; // TODO: move here +use anyhow::Context; use chrono::Utc; use uuid::Uuid; @@ -18,6 +18,37 @@ pub(crate) type HistorySegment = Vec; pub(crate) type ClientKey = Uuid; pub(crate) type VersionId = Uuid; +/// ServerConfig contains configuration parameters for the server. +pub struct ServerConfig { + /// Target number of days between snapshots. + pub snapshot_days: i64, + + /// Target number of versions between snapshots. + pub snapshot_versions: u32, +} + +impl Default for ServerConfig { + fn default() -> Self { + ServerConfig { + snapshot_days: 14, + snapshot_versions: 100, + } + } +} + +impl ServerConfig { + pub fn from_args(snapshot_days: &str, snapshot_versions: &str) -> anyhow::Result { + Ok(ServerConfig { + snapshot_days: snapshot_days + .parse() + .context("--snapshot-days must be a number")?, + snapshot_versions: snapshot_versions + .parse() + .context("--snapshot-days must be a number")?, + }) + } +} + /// Response to get_child_version. See the protocol documentation. #[derive(Clone, PartialEq, Debug)] pub(crate) enum GetVersionResult { From ef1d8f37a89c07daeb6d1574d18ec8ff1e849505 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Oct 2021 01:43:30 +0000 Subject: [PATCH 366/548] write config defaults once --- sync-server/src/bin/taskchampion-sync-server.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sync-server/src/bin/taskchampion-sync-server.rs b/sync-server/src/bin/taskchampion-sync-server.rs index 80ca56d68..8ee1e2390 100644 --- a/sync-server/src/bin/taskchampion-sync-server.rs +++ b/sync-server/src/bin/taskchampion-sync-server.rs @@ -8,6 +8,9 @@ use taskchampion_sync_server::{Server, ServerConfig}; #[actix_web::main] async fn main() -> anyhow::Result<()> { env_logger::init(); + let defaults = ServerConfig::default(); + let default_snapshot_versions = defaults.snapshot_versions.to_string(); + let default_snapshot_days = defaults.snapshot_days.to_string(); let matches = clap::App::new("taskchampion-sync-server") .version(env!("CARGO_PKG_VERSION")) .about("Server for TaskChampion") @@ -36,7 +39,7 @@ async fn main() -> anyhow::Result<()> { .long("snapshot-versions") .value_name("NUM") .help("Target number of versions between snapshots") - .default_value("100") + .default_value(&default_snapshot_versions) .takes_value(true) .required(false), ) @@ -45,7 +48,7 @@ async fn main() -> anyhow::Result<()> { .long("snapshot-days") .value_name("NUM") .help("Target number of days between snapshots") - .default_value("14") + .default_value(&default_snapshot_days) .takes_value(true) .required(false), ) From 79f07b57ade1153d078c9f903b47c44706bb9f71 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 11 Oct 2021 20:56:10 -0400 Subject: [PATCH 367/548] more taskdb refactoring --- taskchampion/src/taskdb/mod.rs | 394 +------------------------ taskchampion/src/taskdb/ops.rs | 196 ++++++++++++ taskchampion/src/taskdb/sync.rs | 131 ++++++++ taskchampion/src/taskdb/working_set.rs | 167 +++++++++++ 4 files changed, 499 insertions(+), 389 deletions(-) create mode 100644 taskchampion/src/taskdb/working_set.rs diff --git a/taskchampion/src/taskdb/mod.rs b/taskchampion/src/taskdb/mod.rs index 1b84f2c9b..850628719 100644 --- a/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -1,10 +1,10 @@ use crate::server::Server; use crate::storage::{Operation, Storage, TaskMap}; -use std::collections::HashSet; use uuid::Uuid; mod ops; mod sync; +mod working_set; /// A TaskDb is the backend for a replica. It manages the storage, operations, synchronization, /// and so on, and all the invariants that come with it. It leaves the meaning of particular task @@ -74,57 +74,7 @@ impl TaskDb { where F: Fn(&TaskMap) -> bool, { - let mut txn = self.storage.txn()?; - - let mut new_ws = vec![None]; // index 0 is always None - let mut seen = HashSet::new(); - - // The goal here is for existing working-set items to be "compressed' down to index 1, so - // we begin by scanning the current working set and inserting any tasks that should still - // be in the set into new_ws, implicitly dropping any tasks that are no longer in the - // working set. - for elt in txn.get_working_set()?.drain(1..) { - if let Some(uuid) = elt { - if let Some(task) = txn.get_task(uuid)? { - if in_working_set(&task) { - new_ws.push(Some(uuid)); - seen.insert(uuid); - continue; - } - } - } - - // if we are not renumbering, then insert a blank working-set entry here - if !renumber { - new_ws.push(None); - } - } - - // if renumbering, clear the working set and re-add - if renumber { - txn.clear_working_set()?; - for elt in new_ws.drain(1..new_ws.len()).flatten() { - txn.add_to_working_set(elt)?; - } - } else { - // ..otherwise, just clear the None items determined above from the working set - for (i, elt) in new_ws.iter().enumerate().skip(1) { - if elt.is_none() { - txn.set_working_set_item(i, None)?; - } - } - } - - // Now go hunting for tasks that should be in this list but are not, adding them at the - // end of the list, whether renumbering or not - for (uuid, task) in txn.all_tasks()? { - if !seen.contains(&uuid) && in_working_set(&task) { - txn.add_to_working_set(uuid)?; - } - } - - txn.commit()?; - Ok(()) + working_set::rebuild(self.storage.txn()?.as_mut(), in_working_set, renumber) } /// Add the given uuid to the working set and return its index; if it is already in the working @@ -190,11 +140,12 @@ mod tests { use chrono::Utc; use pretty_assertions::assert_eq; use proptest::prelude::*; - use std::collections::HashMap; use uuid::Uuid; #[test] - fn test_apply_create() { + fn test_apply() { + // this verifies that the operation is both applied and included in the list of + // operations; more detailed tests are in the `ops` module. let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; @@ -204,345 +155,10 @@ mod tests { assert_eq!(db.operations(), vec![op]); } - #[test] - fn test_apply_create_exists() { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = Operation::Create { uuid }; - db.apply(op.clone()).unwrap(); - assert_eq!( - db.apply(op.clone()).err().unwrap().to_string(), - format!("Task Database Error: Task {} already exists", uuid) - ); - - assert_eq!(db.sorted_tasks(), vec![(uuid, vec![])]); - assert_eq!(db.operations(), vec![op]); - } - - #[test] - fn test_apply_create_update() { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op1 = Operation::Create { uuid }; - db.apply(op1.clone()).unwrap(); - let op2 = Operation::Update { - uuid, - property: String::from("title"), - value: Some("my task".into()), - timestamp: Utc::now(), - }; - db.apply(op2.clone()).unwrap(); - - assert_eq!( - db.sorted_tasks(), - vec![(uuid, vec![("title".into(), "my task".into())])] - ); - assert_eq!(db.operations(), vec![op1, op2]); - } - - #[test] - fn test_apply_create_update_delete_prop() { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op1 = Operation::Create { uuid }; - db.apply(op1.clone()).unwrap(); - - let op2 = Operation::Update { - uuid, - property: String::from("title"), - value: Some("my task".into()), - timestamp: Utc::now(), - }; - db.apply(op2.clone()).unwrap(); - - let op3 = Operation::Update { - uuid, - property: String::from("priority"), - value: Some("H".into()), - timestamp: Utc::now(), - }; - db.apply(op3.clone()).unwrap(); - - let op4 = Operation::Update { - uuid, - property: String::from("title"), - value: None, - timestamp: Utc::now(), - }; - db.apply(op4.clone()).unwrap(); - - let mut exp = HashMap::new(); - let mut task = HashMap::new(); - task.insert(String::from("priority"), String::from("H")); - exp.insert(uuid, task); - assert_eq!( - db.sorted_tasks(), - vec![(uuid, vec![("priority".into(), "H".into())])] - ); - assert_eq!(db.operations(), vec![op1, op2, op3, op4]); - } - - #[test] - fn test_apply_update_does_not_exist() { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = Operation::Update { - uuid, - property: String::from("title"), - value: Some("my task".into()), - timestamp: Utc::now(), - }; - assert_eq!( - db.apply(op).err().unwrap().to_string(), - format!("Task Database Error: Task {} does not exist", uuid) - ); - - assert_eq!(db.sorted_tasks(), vec![]); - assert_eq!(db.operations(), vec![]); - } - - #[test] - fn test_apply_create_delete() { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op1 = Operation::Create { uuid }; - db.apply(op1.clone()).unwrap(); - - let op2 = Operation::Delete { uuid }; - db.apply(op2.clone()).unwrap(); - - assert_eq!(db.sorted_tasks(), vec![]); - assert_eq!(db.operations(), vec![op1, op2]); - } - - #[test] - fn test_apply_delete_not_present() { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - - let op1 = Operation::Delete { uuid }; - assert_eq!( - db.apply(op1).err().unwrap().to_string(), - format!("Task Database Error: Task {} does not exist", uuid) - ); - - assert_eq!(db.sorted_tasks(), vec![]); - assert_eq!(db.operations(), vec![]); - } - - #[test] - fn rebuild_working_set_renumber() -> anyhow::Result<()> { - rebuild_working_set(true) - } - - #[test] - fn rebuild_working_set_no_renumber() -> anyhow::Result<()> { - rebuild_working_set(false) - } - - fn rebuild_working_set(renumber: bool) -> anyhow::Result<()> { - let mut db = TaskDb::new_inmemory(); - let mut uuids = vec![]; - uuids.push(Uuid::new_v4()); - println!("uuids[0]: {:?} - pending, not in working set", uuids[0]); - uuids.push(Uuid::new_v4()); - println!("uuids[1]: {:?} - pending, in working set", uuids[1]); - uuids.push(Uuid::new_v4()); - println!("uuids[2]: {:?} - not pending, not in working set", uuids[2]); - uuids.push(Uuid::new_v4()); - println!("uuids[3]: {:?} - not pending, in working set", uuids[3]); - uuids.push(Uuid::new_v4()); - println!("uuids[4]: {:?} - pending, in working set", uuids[4]); - - // add everything to the TaskDb - for uuid in &uuids { - db.apply(Operation::Create { uuid: *uuid })?; - } - for i in &[0usize, 1, 4] { - db.apply(Operation::Update { - uuid: uuids[*i].clone(), - property: String::from("status"), - value: Some("pending".into()), - timestamp: Utc::now(), - })?; - } - - // set the existing working_set as we want it - { - let mut txn = db.storage.txn()?; - txn.clear_working_set()?; - - for i in &[1usize, 3, 4] { - txn.add_to_working_set(uuids[*i])?; - } - - txn.commit()?; - } - - assert_eq!( - db.working_set()?, - vec![ - None, - Some(uuids[1].clone()), - Some(uuids[3].clone()), - Some(uuids[4].clone()) - ] - ); - - db.rebuild_working_set( - |t| { - if let Some(status) = t.get("status") { - status == "pending" - } else { - false - } - }, - renumber, - )?; - - let exp = if renumber { - // uuids[1] and uuids[4] are already in the working set, so are compressed - // to the top, and then uuids[0] is added. - vec![ - None, - Some(uuids[1].clone()), - Some(uuids[4].clone()), - Some(uuids[0].clone()), - ] - } else { - // uuids[1] and uuids[4] are already in the working set, at indexes 1 and 3, - // and then uuids[0] is added. - vec![ - None, - Some(uuids[1].clone()), - None, - Some(uuids[4].clone()), - Some(uuids[0].clone()), - ] - }; - - assert_eq!(db.working_set()?, exp); - - Ok(()) - } - fn newdb() -> TaskDb { TaskDb::new(Box::new(InMemoryStorage::new())) } - #[test] - fn test_sync() { - let mut server: Box = Box::new(TestServer::new()); - - let mut db1 = newdb(); - db1.sync(&mut server).unwrap(); - - let mut db2 = newdb(); - db2.sync(&mut server).unwrap(); - - // make some changes in parallel to db1 and db2.. - let uuid1 = Uuid::new_v4(); - db1.apply(Operation::Create { uuid: uuid1 }).unwrap(); - db1.apply(Operation::Update { - uuid: uuid1, - property: "title".into(), - value: Some("my first task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - let uuid2 = Uuid::new_v4(); - db2.apply(Operation::Create { uuid: uuid2 }).unwrap(); - db2.apply(Operation::Update { - uuid: uuid2, - property: "title".into(), - value: Some("my second task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and synchronize those around - db1.sync(&mut server).unwrap(); - db2.sync(&mut server).unwrap(); - db1.sync(&mut server).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - - // now make updates to the same task on both sides - db1.apply(Operation::Update { - uuid: uuid2, - property: "priority".into(), - value: Some("H".into()), - timestamp: Utc::now(), - }) - .unwrap(); - db2.apply(Operation::Update { - uuid: uuid2, - property: "project".into(), - value: Some("personal".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and synchronize those around - db1.sync(&mut server).unwrap(); - db2.sync(&mut server).unwrap(); - db1.sync(&mut server).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - } - - #[test] - fn test_sync_create_delete() { - let mut server: Box = Box::new(TestServer::new()); - - let mut db1 = newdb(); - db1.sync(&mut server).unwrap(); - - let mut db2 = newdb(); - db2.sync(&mut server).unwrap(); - - // create and update a task.. - let uuid = Uuid::new_v4(); - db1.apply(Operation::Create { uuid }).unwrap(); - db1.apply(Operation::Update { - uuid, - property: "title".into(), - value: Some("my first task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and synchronize those around - db1.sync(&mut server).unwrap(); - db2.sync(&mut server).unwrap(); - db1.sync(&mut server).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - - // delete and re-create the task on db1 - db1.apply(Operation::Delete { uuid }).unwrap(); - db1.apply(Operation::Create { uuid }).unwrap(); - db1.apply(Operation::Update { - uuid, - property: "title".into(), - value: Some("my second task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and on db2, update a property of the task - db2.apply(Operation::Update { - uuid, - property: "project".into(), - value: Some("personal".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - db1.sync(&mut server).unwrap(); - db2.sync(&mut server).unwrap(); - db1.sync(&mut server).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - } - #[derive(Debug)] enum Action { Op(Operation), diff --git a/taskchampion/src/taskdb/ops.rs b/taskchampion/src/taskdb/ops.rs index 8bfd003e0..7e23d04ce 100644 --- a/taskchampion/src/taskdb/ops.rs +++ b/taskchampion/src/taskdb/ops.rs @@ -35,3 +35,199 @@ pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &Operation) -> anyhow::Resu Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::taskdb::TaskDb; + use chrono::Utc; + use pretty_assertions::assert_eq; + use std::collections::HashMap; + use uuid::Uuid; + + #[test] + fn test_apply_create() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op = Operation::Create { uuid }; + + { + let mut txn = db.storage.txn()?; + apply_op(txn.as_mut(), &op)?; + txn.commit()?; + } + + assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]); + Ok(()) + } + + #[test] + fn test_apply_create_exists() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op = Operation::Create { uuid }; + { + let mut txn = db.storage.txn()?; + apply_op(txn.as_mut(), &op)?; + assert_eq!( + apply_op(txn.as_mut(), &op).err().unwrap().to_string(), + format!("Task Database Error: Task {} already exists", uuid) + ); + txn.commit()?; + } + + // first op was applied + assert_eq!(db.sorted_tasks(), vec![(uuid, vec![])]); + + Ok(()) + } + + #[test] + fn test_apply_create_update() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op1 = Operation::Create { uuid }; + + { + let mut txn = db.storage.txn()?; + apply_op(txn.as_mut(), &op1)?; + txn.commit()?; + } + + let op2 = Operation::Update { + uuid, + property: String::from("title"), + value: Some("my task".into()), + timestamp: Utc::now(), + }; + { + let mut txn = db.storage.txn()?; + apply_op(txn.as_mut(), &op2)?; + txn.commit()?; + } + + assert_eq!( + db.sorted_tasks(), + vec![(uuid, vec![("title".into(), "my task".into())])] + ); + + Ok(()) + } + + #[test] + fn test_apply_create_update_delete_prop() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op1 = Operation::Create { uuid }; + { + let mut txn = db.storage.txn()?; + apply_op(txn.as_mut(), &op1)?; + txn.commit()?; + } + + let op2 = Operation::Update { + uuid, + property: String::from("title"), + value: Some("my task".into()), + timestamp: Utc::now(), + }; + { + let mut txn = db.storage.txn()?; + apply_op(txn.as_mut(), &op2)?; + txn.commit()?; + } + + let op3 = Operation::Update { + uuid, + property: String::from("priority"), + value: Some("H".into()), + timestamp: Utc::now(), + }; + { + let mut txn = db.storage.txn()?; + apply_op(txn.as_mut(), &op3)?; + txn.commit()?; + } + + let op4 = Operation::Update { + uuid, + property: String::from("title"), + value: None, + timestamp: Utc::now(), + }; + { + let mut txn = db.storage.txn()?; + apply_op(txn.as_mut(), &op4)?; + txn.commit()?; + } + + let mut exp = HashMap::new(); + let mut task = HashMap::new(); + task.insert(String::from("priority"), String::from("H")); + exp.insert(uuid, task); + assert_eq!( + db.sorted_tasks(), + vec![(uuid, vec![("priority".into(), "H".into())])] + ); + + Ok(()) + } + + #[test] + fn test_apply_update_does_not_exist() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op = Operation::Update { + uuid, + property: String::from("title"), + value: Some("my task".into()), + timestamp: Utc::now(), + }; + { + let mut txn = db.storage.txn()?; + assert_eq!( + apply_op(txn.as_mut(), &op).err().unwrap().to_string(), + format!("Task Database Error: Task {} does not exist", uuid) + ); + txn.commit()?; + } + + Ok(()) + } + + #[test] + fn test_apply_create_delete() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op1 = Operation::Create { uuid }; + let op2 = Operation::Delete { uuid }; + + { + let mut txn = db.storage.txn()?; + apply_op(txn.as_mut(), &op1)?; + apply_op(txn.as_mut(), &op2)?; + txn.commit()?; + } + + assert_eq!(db.sorted_tasks(), vec![]); + + Ok(()) + } + + #[test] + fn test_apply_delete_not_present() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op = Operation::Delete { uuid }; + { + let mut txn = db.storage.txn()?; + assert_eq!( + apply_op(txn.as_mut(), &op).err().unwrap().to_string(), + format!("Task Database Error: Task {} does not exist", uuid) + ); + txn.commit()?; + } + + Ok(()) + } +} diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index af076056e..d8c145857 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -143,3 +143,134 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu txn.set_operations(local_operations)?; Ok(()) } + +#[cfg(test)] +mod test { + use super::*; + use crate::server::test::TestServer; + use crate::storage::{InMemoryStorage, Operation}; + use crate::taskdb::TaskDb; + use chrono::Utc; + use uuid::Uuid; + + fn newdb() -> TaskDb { + TaskDb::new(Box::new(InMemoryStorage::new())) + } + + #[test] + fn test_sync() -> anyhow::Result<()> { + let mut server: Box = Box::new(TestServer::new()); + + let mut db1 = newdb(); + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + + let mut db2 = newdb(); + sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); + + // make some changes in parallel to db1 and db2.. + let uuid1 = Uuid::new_v4(); + db1.apply(Operation::Create { uuid: uuid1 }).unwrap(); + db1.apply(Operation::Update { + uuid: uuid1, + property: "title".into(), + value: Some("my first task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + let uuid2 = Uuid::new_v4(); + db2.apply(Operation::Create { uuid: uuid2 }).unwrap(); + db2.apply(Operation::Update { + uuid: uuid2, + property: "title".into(), + value: Some("my second task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and synchronize those around + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + + // now make updates to the same task on both sides + db1.apply(Operation::Update { + uuid: uuid2, + property: "priority".into(), + value: Some("H".into()), + timestamp: Utc::now(), + }) + .unwrap(); + db2.apply(Operation::Update { + uuid: uuid2, + property: "project".into(), + value: Some("personal".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and synchronize those around + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + + Ok(()) + } + + #[test] + fn test_sync_create_delete() -> anyhow::Result<()> { + let mut server: Box = Box::new(TestServer::new()); + + let mut db1 = newdb(); + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + + let mut db2 = newdb(); + sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); + + // create and update a task.. + let uuid = Uuid::new_v4(); + db1.apply(Operation::Create { uuid }).unwrap(); + db1.apply(Operation::Update { + uuid, + property: "title".into(), + value: Some("my first task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and synchronize those around + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + + // delete and re-create the task on db1 + db1.apply(Operation::Delete { uuid }).unwrap(); + db1.apply(Operation::Create { uuid }).unwrap(); + db1.apply(Operation::Update { + uuid, + property: "title".into(), + value: Some("my second task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + // and on db2, update a property of the task + db2.apply(Operation::Update { + uuid, + property: "project".into(), + value: Some("personal".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + + Ok(()) + } +} diff --git a/taskchampion/src/taskdb/working_set.rs b/taskchampion/src/taskdb/working_set.rs new file mode 100644 index 000000000..d5e0774b0 --- /dev/null +++ b/taskchampion/src/taskdb/working_set.rs @@ -0,0 +1,167 @@ +use crate::storage::{StorageTxn, TaskMap}; +use std::collections::HashSet; + +/// Rebuild the working set using a function to identify tasks that should be in the set. This +/// renumbers the existing working-set tasks to eliminate gaps, and also adds any tasks that +/// are not already in the working set but should be. The rebuild occurs in a single +/// trasnsaction against the storage backend. +pub fn rebuild(txn: &mut dyn StorageTxn, in_working_set: F, renumber: bool) -> anyhow::Result<()> +where + F: Fn(&TaskMap) -> bool, +{ + let mut new_ws = vec![None]; // index 0 is always None + let mut seen = HashSet::new(); + + // The goal here is for existing working-set items to be "compressed' down to index 1, so + // we begin by scanning the current working set and inserting any tasks that should still + // be in the set into new_ws, implicitly dropping any tasks that are no longer in the + // working set. + for elt in txn.get_working_set()?.drain(1..) { + if let Some(uuid) = elt { + if let Some(task) = txn.get_task(uuid)? { + if in_working_set(&task) { + new_ws.push(Some(uuid)); + seen.insert(uuid); + continue; + } + } + } + + // if we are not renumbering, then insert a blank working-set entry here + if !renumber { + new_ws.push(None); + } + } + + // if renumbering, clear the working set and re-add + if renumber { + txn.clear_working_set()?; + for elt in new_ws.drain(1..new_ws.len()).flatten() { + txn.add_to_working_set(elt)?; + } + } else { + // ..otherwise, just clear the None items determined above from the working set + for (i, elt) in new_ws.iter().enumerate().skip(1) { + if elt.is_none() { + txn.set_working_set_item(i, None)?; + } + } + } + + // Now go hunting for tasks that should be in this list but are not, adding them at the + // end of the list, whether renumbering or not + for (uuid, task) in txn.all_tasks()? { + if !seen.contains(&uuid) && in_working_set(&task) { + txn.add_to_working_set(uuid)?; + } + } + + txn.commit()?; + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::storage::Operation; + use crate::taskdb::TaskDb; + use chrono::Utc; + use uuid::Uuid; + + #[test] + fn rebuild_working_set_renumber() -> anyhow::Result<()> { + rebuild_working_set(true) + } + + #[test] + fn rebuild_working_set_no_renumber() -> anyhow::Result<()> { + rebuild_working_set(false) + } + + fn rebuild_working_set(renumber: bool) -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let mut uuids = vec![]; + uuids.push(Uuid::new_v4()); + println!("uuids[0]: {:?} - pending, not in working set", uuids[0]); + uuids.push(Uuid::new_v4()); + println!("uuids[1]: {:?} - pending, in working set", uuids[1]); + uuids.push(Uuid::new_v4()); + println!("uuids[2]: {:?} - not pending, not in working set", uuids[2]); + uuids.push(Uuid::new_v4()); + println!("uuids[3]: {:?} - not pending, in working set", uuids[3]); + uuids.push(Uuid::new_v4()); + println!("uuids[4]: {:?} - pending, in working set", uuids[4]); + + // add everything to the TaskDb + for uuid in &uuids { + db.apply(Operation::Create { uuid: *uuid })?; + } + for i in &[0usize, 1, 4] { + db.apply(Operation::Update { + uuid: uuids[*i].clone(), + property: String::from("status"), + value: Some("pending".into()), + timestamp: Utc::now(), + })?; + } + + // set the existing working_set as we want it + { + let mut txn = db.storage.txn()?; + txn.clear_working_set()?; + + for i in &[1usize, 3, 4] { + txn.add_to_working_set(uuids[*i])?; + } + + txn.commit()?; + } + + assert_eq!( + db.working_set()?, + vec![ + None, + Some(uuids[1].clone()), + Some(uuids[3].clone()), + Some(uuids[4].clone()) + ] + ); + + rebuild( + db.storage.txn()?.as_mut(), + |t| { + if let Some(status) = t.get("status") { + status == "pending" + } else { + false + } + }, + renumber, + )?; + + let exp = if renumber { + // uuids[1] and uuids[4] are already in the working set, so are compressed + // to the top, and then uuids[0] is added. + vec![ + None, + Some(uuids[1].clone()), + Some(uuids[4].clone()), + Some(uuids[0].clone()), + ] + } else { + // uuids[1] and uuids[4] are already in the working set, at indexes 1 and 3, + // and then uuids[0] is added. + vec![ + None, + Some(uuids[1].clone()), + None, + Some(uuids[4].clone()), + Some(uuids[0].clone()), + ] + }; + + assert_eq!(db.working_set()?, exp); + + Ok(()) + } +} From b8d892878cd4fa69fe3711a4c6da502f1db4ea6c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 11 Oct 2021 09:01:37 -0400 Subject: [PATCH 368/548] document sync data formats --- docs/src/sync-protocol.md | 35 ++++++++++++++ taskchampion/src/storage/operation.rs | 66 +++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md index 437f44b1e..1f111312c 100644 --- a/docs/src/sync-protocol.md +++ b/docs/src/sync-protocol.md @@ -38,6 +38,41 @@ This observation allows the server to discard older versions. The third invariant prevents the server from discarding versions if there is no snapshot. The fourth invariant prevents the server from discarding versions newer than the snapshot. +## Data Formats + +### Encryption + +TBD (#299) + +### Version + +The decrypted form of a version is a JSON array containing operations in the order they should be applied. +Each operation has the form `{TYPE: DATA}`, for example: + + * `{"Create":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7"}}` + * `{"Delete":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7"}}` + * `{"Update":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7","property":"prop","value":"v","timestamp":"2021-10-11T12:47:07.188090948Z"}}` + * `{"Update":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7","property":"prop","value":null,"timestamp":"2021-10-11T12:47:07.188090948Z"}}` (to delete a property) + +Timestamps are in RFC3339 format with a `Z` suffix. + +### Snapshot + +The decrypted form of a snapshot is a JSON object mapping task IDs to task properties. +For example (pretty-printed for clarity): + +```json +{ + "56e0be07-c61f-494c-a54c-bdcfdd52d2a7": { + "description": "a task", + "priority": "H" + }, + "4b7ed904-f7b0-4293-8a10-ad452422c7b3": { + "description": "another task" + } +} +``` + ## Transactions ### AddVersion diff --git a/taskchampion/src/storage/operation.rs b/taskchampion/src/storage/operation.rs index 68f5fe7d0..ed9a5182d 100644 --- a/taskchampion/src/storage/operation.rs +++ b/taskchampion/src/storage/operation.rs @@ -274,6 +274,72 @@ mod test { ); } + #[test] + fn test_json_create() -> anyhow::Result<()> { + let uuid = Uuid::new_v4(); + let op = Create { uuid }; + assert_eq!( + serde_json::to_string(&op)?, + format!(r#"{{"Create":{{"uuid":"{}"}}}}"#, uuid), + ); + Ok(()) + } + + #[test] + fn test_json_delete() -> anyhow::Result<()> { + let uuid = Uuid::new_v4(); + let op = Delete { uuid }; + assert_eq!( + serde_json::to_string(&op)?, + format!(r#"{{"Delete":{{"uuid":"{}"}}}}"#, uuid), + ); + Ok(()) + } + + #[test] + fn test_json_update() -> anyhow::Result<()> { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + let op = Update { + uuid, + property: "abc".into(), + value: Some("false".into()), + timestamp, + }; + + assert_eq!( + serde_json::to_string(&op)?, + format!( + r#"{{"Update":{{"uuid":"{}","property":"abc","value":"false","timestamp":"{:?}"}}}}"#, + uuid, timestamp, + ), + ); + Ok(()) + } + + #[test] + fn test_json_update_none() -> anyhow::Result<()> { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + let op = Update { + uuid, + property: "abc".into(), + value: None, + timestamp, + }; + + assert_eq!( + serde_json::to_string(&op)?, + format!( + r#"{{"Update":{{"uuid":"{}","property":"abc","value":null,"timestamp":"{:?}"}}}}"#, + uuid, timestamp, + ), + ); + Ok(()) + } + fn uuid_strategy() -> impl Strategy { prop_oneof![ Just(Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap()), From bde19d7f07b6764128bfd6f5e0e1949a45f065fb Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 7 Oct 2021 19:35:06 -0400 Subject: [PATCH 369/548] Return SnapshotUrgency from AddVersion --- taskchampion/src/server/local.rs | 22 +++++++++++++-------- taskchampion/src/server/remote/mod.rs | 28 +++++++++++++++++++++++---- taskchampion/src/server/test.rs | 12 +++++++----- taskchampion/src/server/types.rs | 15 ++++++++++++-- taskchampion/src/taskdb/sync.rs | 3 ++- 5 files changed, 60 insertions(+), 20 deletions(-) diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index ccd2c8d3c..3c2632cda 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -1,5 +1,6 @@ use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NIL_VERSION_ID, + AddVersionResult, GetVersionResult, HistorySegment, Server, SnapshotUrgency, VersionId, + NIL_VERSION_ID, }; use crate::storage::sqlite::StoredUuid; use anyhow::Context; @@ -116,14 +117,17 @@ impl Server for LocalServer { &mut self, parent_version_id: VersionId, history_segment: HistorySegment, - ) -> anyhow::Result { + ) -> anyhow::Result<(AddVersionResult, SnapshotUrgency)> { // no client lookup // no signature validation // check the parent_version_id for linearity let latest_version_id = self.get_latest_version_id()?; if latest_version_id != NIL_VERSION_ID && parent_version_id != latest_version_id { - return Ok(AddVersionResult::ExpectedParentVersion(latest_version_id)); + return Ok(( + AddVersionResult::ExpectedParentVersion(latest_version_id), + SnapshotUrgency::None, + )); } // invent a new ID for this version @@ -136,7 +140,7 @@ impl Server for LocalServer { })?; self.set_latest_version_id(version_id)?; - Ok(AddVersionResult::Ok(version_id)) + Ok((AddVersionResult::Ok(version_id), SnapshotUrgency::None)) } /// Get a vector of all versions after `since_version` @@ -176,7 +180,7 @@ mod test { let tmp_dir = TempDir::new()?; let mut server = LocalServer::new(&tmp_dir.path())?; let history = b"1234".to_vec(); - match server.add_version(NIL_VERSION_ID, history.clone())? { + match server.add_version(NIL_VERSION_ID, history.clone())?.0 { AddVersionResult::ExpectedParentVersion(_) => { panic!("should have accepted the version") } @@ -204,7 +208,7 @@ mod test { let parent_version_id = Uuid::new_v4() as VersionId; // This is OK because the server has no latest_version_id yet - match server.add_version(parent_version_id, history.clone())? { + match server.add_version(parent_version_id, history.clone())?.0 { AddVersionResult::ExpectedParentVersion(_) => { panic!("should have accepted the version") } @@ -232,14 +236,16 @@ mod test { let parent_version_id = Uuid::new_v4() as VersionId; // add a version - if let AddVersionResult::ExpectedParentVersion(_) = + if let (AddVersionResult::ExpectedParentVersion(_), SnapshotUrgency::None) = server.add_version(parent_version_id, history.clone())? { panic!("should have accepted the version") } // then add another, not based on that one - if let AddVersionResult::Ok(_) = server.add_version(parent_version_id, history.clone())? { + if let (AddVersionResult::Ok(_), SnapshotUrgency::None) = + server.add_version(parent_version_id, history.clone())? + { panic!("should not have accepted the version") } diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index f6fbb4b0a..9551a448a 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -1,4 +1,6 @@ -use crate::server::{AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId}; +use crate::server::{ + AddVersionResult, GetVersionResult, HistorySegment, Server, SnapshotUrgency, VersionId, +}; use std::convert::TryInto; use std::time::Duration; use uuid::Uuid; @@ -43,12 +45,24 @@ fn get_uuid_header(resp: &ureq::Response, name: &str) -> anyhow::Result { Ok(value) } +/// Read the X-Snapshot-Request header and return a SnapshotUrgency +fn get_snapshot_urgency(resp: &ureq::Response) -> SnapshotUrgency { + match resp.header("X-Snapshot-Request") { + None => SnapshotUrgency::None, + Some(hdr) => match hdr { + "urgency=low" => SnapshotUrgency::Low, + "urgency=high" => SnapshotUrgency::High, + _ => SnapshotUrgency::None, + }, + } +} + impl Server for RemoteServer { fn add_version( &mut self, parent_version_id: VersionId, history_segment: HistorySegment, - ) -> anyhow::Result { + ) -> anyhow::Result<(AddVersionResult, SnapshotUrgency)> { let url = format!( "{}/v1/client/add-version/{}", self.origin, parent_version_id @@ -70,11 +84,17 @@ impl Server for RemoteServer { { Ok(resp) => { let version_id = get_uuid_header(&resp, "X-Version-Id")?; - Ok(AddVersionResult::Ok(version_id)) + Ok(( + AddVersionResult::Ok(version_id), + get_snapshot_urgency(&resp), + )) } Err(ureq::Error::Status(status, resp)) if status == 409 => { let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; - Ok(AddVersionResult::ExpectedParentVersion(parent_version_id)) + Ok(( + AddVersionResult::ExpectedParentVersion(parent_version_id), + SnapshotUrgency::None, + )) } Err(err) => Err(err.into()), } diff --git a/taskchampion/src/server/test.rs b/taskchampion/src/server/test.rs index 18a7e62bd..aca9e1b94 100644 --- a/taskchampion/src/server/test.rs +++ b/taskchampion/src/server/test.rs @@ -1,5 +1,6 @@ use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NIL_VERSION_ID, + AddVersionResult, GetVersionResult, HistorySegment, Server, SnapshotUrgency, VersionId, + NIL_VERSION_ID, }; use std::collections::HashMap; use uuid::Uuid; @@ -33,15 +34,16 @@ impl Server for TestServer { &mut self, parent_version_id: VersionId, history_segment: HistorySegment, - ) -> anyhow::Result { + ) -> anyhow::Result<(AddVersionResult, SnapshotUrgency)> { // no client lookup // no signature validation // check the parent_version_id for linearity if self.latest_version_id != NIL_VERSION_ID { if parent_version_id != self.latest_version_id { - return Ok(AddVersionResult::ExpectedParentVersion( - self.latest_version_id, + return Ok(( + AddVersionResult::ExpectedParentVersion(self.latest_version_id), + SnapshotUrgency::None, )); } } @@ -59,7 +61,7 @@ impl Server for TestServer { ); self.latest_version_id = version_id; - Ok(AddVersionResult::Ok(version_id)) + Ok((AddVersionResult::Ok(version_id), SnapshotUrgency::None)) } /// Get a vector of all versions after `since_version` diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index 5995dfed5..35169c5cd 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -10,7 +10,7 @@ pub const NIL_VERSION_ID: VersionId = Uuid::nil(); /// data is pre-encoded, and from the protocol level appears as a sequence of bytes. pub type HistorySegment = Vec; -/// VersionAdd is the response type from [`crate::server::Server::add_version`]. +/// AddVersionResult is the response type from [`crate::server::Server::add_version`]. #[derive(Debug, PartialEq)] pub enum AddVersionResult { /// OK, version added with the given ID @@ -19,6 +19,17 @@ pub enum AddVersionResult { ExpectedParentVersion(VersionId), } +/// SnapshotUrgency indicates how much the server would like this replica to send a snapshot. +#[derive(PartialEq, Debug, Clone, Copy, Eq, PartialOrd, Ord)] +pub enum SnapshotUrgency { + /// Don't need a snapshot right now. + None, + /// A snapshot would be good, but can wait for other replicas to provide it. + Low, + /// A snapshot is needed right now. + High, +} + /// A version as downloaded from the server #[derive(Debug, PartialEq)] pub enum GetVersionResult { @@ -40,7 +51,7 @@ pub trait Server { &mut self, parent_version_id: VersionId, history_segment: HistorySegment, - ) -> anyhow::Result; + ) -> anyhow::Result<(AddVersionResult, SnapshotUrgency)>; /// Get the version with the given parent VersionId fn get_child_version( diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index d8c145857..91dd40f24 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -57,7 +57,8 @@ pub(super) fn sync(server: &mut Box, txn: &mut dyn StorageTxn) -> an let new_version = Version { operations }; let history_segment = serde_json::to_string(&new_version).unwrap().into(); info!("sending new version to server"); - match server.add_version(base_version_id, history_segment)? { + let (res, _snapshot_urgency) = server.add_version(base_version_id, history_segment)?; + match res { AddVersionResult::Ok(new_version_id) => { info!("version {:?} received by server", new_version_id); txn.set_base_version(new_version_id)?; From 13a96efacbf2068b2ed019a393132950fe3762ab Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 7 Oct 2021 21:24:27 -0400 Subject: [PATCH 370/548] Add snapshot encoding / decoding --- Cargo.lock | 1 + taskchampion/Cargo.toml | 1 + taskchampion/src/taskdb/mod.rs | 1 + taskchampion/src/taskdb/snapshot.rs | 186 ++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 taskchampion/src/taskdb/snapshot.rs diff --git a/Cargo.lock b/Cargo.lock index bb79505da..10f51edf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2962,6 +2962,7 @@ version = "0.4.1" dependencies = [ "anyhow", "chrono", + "flate2", "log", "pretty_assertions", "proptest", diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index 168d4ba1f..c5da9898c 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -23,6 +23,7 @@ tindercrypt = { version = "^0.2.2", default-features = false } rusqlite = { version = "0.25", features = ["bundled"] } strum = "0.21" strum_macros = "0.21" +flate2 = "1" [dev-dependencies] proptest = "^1.0.0" diff --git a/taskchampion/src/taskdb/mod.rs b/taskchampion/src/taskdb/mod.rs index 850628719..0394b8c85 100644 --- a/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -3,6 +3,7 @@ use crate::storage::{Operation, Storage, TaskMap}; use uuid::Uuid; mod ops; +mod snapshot; mod sync; mod working_set; diff --git a/taskchampion/src/taskdb/snapshot.rs b/taskchampion/src/taskdb/snapshot.rs new file mode 100644 index 000000000..e054612b3 --- /dev/null +++ b/taskchampion/src/taskdb/snapshot.rs @@ -0,0 +1,186 @@ +use crate::storage::{StorageTxn, TaskMap, VersionId}; +use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression}; +use serde::de::{Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeMap, Serializer}; +use std::fmt; +use uuid::Uuid; + +/// A newtype to wrap the result of [`crate::storage::StorageTxn::all_tasks`] +pub(super) struct SnapshotTasks(Vec<(Uuid, TaskMap)>); + +impl Serialize for SnapshotTasks { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(self.0.len()))?; + for (k, v) in &self.0 { + map.serialize_entry(k, v)?; + } + map.end() + } +} + +struct TaskDbVisitor; + +impl<'de> Visitor<'de> for TaskDbVisitor { + type Value = SnapshotTasks; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map representing a task snapshot") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut map = SnapshotTasks(Vec::with_capacity(access.size_hint().unwrap_or(0))); + + while let Some((key, value)) = access.next_entry()? { + map.0.push((key, value)); + } + + Ok(map) + } +} + +impl<'de> Deserialize<'de> for SnapshotTasks { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(TaskDbVisitor) + } +} + +impl SnapshotTasks { + pub(super) fn encode(&self) -> anyhow::Result> { + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); + serde_json::to_writer(&mut encoder, &self)?; + Ok(encoder.finish()?) + } + + pub(super) fn decode(snapshot: &[u8]) -> anyhow::Result { + let decoder = ZlibDecoder::new(snapshot); + Ok(serde_json::from_reader(decoder)?) + } + + pub(super) fn into_inner(self) -> Vec<(Uuid, TaskMap)> { + self.0 + } +} + +#[allow(dead_code)] +/// Generate a snapshot (compressed, unencrypted) for the current state of the taskdb in the given +/// storage. +pub(super) fn make_snapshot(txn: &mut dyn StorageTxn) -> anyhow::Result> { + let all_tasks = SnapshotTasks(txn.all_tasks()?); + all_tasks.encode() +} + +#[allow(dead_code)] +/// Apply the given snapshot (compressed, unencrypted) to the taskdb's storage. +pub(super) fn apply_snapshot( + txn: &mut dyn StorageTxn, + version: VersionId, + snapshot: &[u8], +) -> anyhow::Result<()> { + let all_tasks = SnapshotTasks::decode(snapshot)?; + + // first, verify that the taskdb truly is empty + let mut empty = true; + empty = empty && txn.all_tasks()?.is_empty(); + empty = empty && txn.get_working_set()? == vec![None]; + empty = empty && txn.base_version()? == Uuid::nil(); + empty = empty && txn.operations()?.is_empty(); + + if !empty { + anyhow::bail!("Cannot apply snapshot to a non-empty task database"); + } + + for (uuid, task) in all_tasks.into_inner().drain(..) { + txn.set_task(uuid, task)?; + } + txn.set_base_version(version)?; + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::storage::{InMemoryStorage, Storage, TaskMap}; + use pretty_assertions::assert_eq; + + #[test] + fn test_serialize_empty() -> anyhow::Result<()> { + let empty = SnapshotTasks(vec![]); + assert_eq!(serde_json::to_vec(&empty)?, b"{}".to_owned()); + Ok(()) + } + + #[test] + fn test_serialize_tasks() -> anyhow::Result<()> { + let u = Uuid::new_v4(); + let m: TaskMap = vec![("description".to_owned(), "my task".to_owned())] + .drain(..) + .collect(); + let all_tasks = SnapshotTasks(vec![(u, m)]); + assert_eq!( + serde_json::to_vec(&all_tasks)?, + format!("{{\"{}\":{{\"description\":\"my task\"}}}}", u).into_bytes(), + ); + Ok(()) + } + + #[test] + fn test_round_trip() -> anyhow::Result<()> { + let mut storage = InMemoryStorage::new(); + let version = Uuid::new_v4(); + + let task1 = ( + Uuid::new_v4(), + vec![("description".to_owned(), "one".to_owned())] + .drain(..) + .collect::(), + ); + let task2 = ( + Uuid::new_v4(), + vec![("description".to_owned(), "two".to_owned())] + .drain(..) + .collect::(), + ); + + { + let mut txn = storage.txn()?; + txn.set_task(task1.0, task1.1.clone())?; + txn.set_task(task2.0, task2.1.clone())?; + txn.commit()?; + } + + let snap = { + let mut txn = storage.txn()?; + make_snapshot(txn.as_mut())? + }; + + // apply that snapshot to a fresh bit of fake + let mut storage = InMemoryStorage::new(); + { + let mut txn = storage.txn()?; + apply_snapshot(txn.as_mut(), version, &snap)?; + txn.commit()? + } + + { + let mut txn = storage.txn()?; + assert_eq!(txn.get_task(task1.0)?, Some(task1.1)); + assert_eq!(txn.get_task(task2.0)?, Some(task2.1)); + assert_eq!(txn.all_tasks()?.len(), 2); + assert_eq!(txn.base_version()?, version); + assert_eq!(txn.operations()?.len(), 0); + assert_eq!(txn.get_working_set()?.len(), 1); + } + + Ok(()) + } +} From b97f6dc4d53a31096fa3f2c8168c232dd7bb8cb1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 10 Oct 2021 17:27:29 -0400 Subject: [PATCH 371/548] Send snapshots to server --- taskchampion/src/server/local.rs | 12 ++- taskchampion/src/server/remote/crypto.rs | 115 +++++++++++------------ taskchampion/src/server/remote/mod.rs | 49 +++++++--- taskchampion/src/server/test.rs | 74 +++++++++++---- taskchampion/src/server/types.rs | 7 ++ taskchampion/src/taskdb/sync.rs | 53 +++++++++-- 6 files changed, 206 insertions(+), 104 deletions(-) diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 3c2632cda..b8c0198a2 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -1,6 +1,6 @@ use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, SnapshotUrgency, VersionId, - NIL_VERSION_ID, + AddVersionResult, GetVersionResult, HistorySegment, Server, Snapshot, SnapshotUrgency, + VersionId, NIL_VERSION_ID, }; use crate::storage::sqlite::StoredUuid; use anyhow::Context; @@ -111,8 +111,6 @@ impl Server for LocalServer { // TODO: better transaction isolation for add_version (gets and sets should be in the same // transaction) - /// Add a new version. If the given version number is incorrect, this responds with the - /// appropriate version and expects the caller to try again. fn add_version( &mut self, parent_version_id: VersionId, @@ -143,7 +141,6 @@ impl Server for LocalServer { Ok((AddVersionResult::Ok(version_id), SnapshotUrgency::None)) } - /// Get a vector of all versions after `since_version` fn get_child_version( &mut self, parent_version_id: VersionId, @@ -158,6 +155,11 @@ impl Server for LocalServer { Ok(GetVersionResult::NoSuchVersion) } } + + fn add_snapshot(&mut self, _version_id: VersionId, _snapshot: Snapshot) -> anyhow::Result<()> { + // the local server never requests a snapshot, so it should never get one + unreachable!() + } } #[cfg(test)] diff --git a/taskchampion/src/server/remote/crypto.rs b/taskchampion/src/server/remote/crypto.rs index 512846479..40103f422 100644 --- a/taskchampion/src/server/remote/crypto.rs +++ b/taskchampion/src/server/remote/crypto.rs @@ -1,5 +1,4 @@ use crate::server::HistorySegment; -use std::convert::TryFrom; use std::io::Read; use tindercrypt::cryptors::RingCryptor; use uuid::Uuid; @@ -18,45 +17,31 @@ impl AsRef<[u8]> for Secret { } } -/// A cleartext payload containing a history segment. -pub(super) struct HistoryCleartext { - pub(super) parent_version_id: Uuid, - pub(super) history_segment: HistorySegment, +/// A cleartext payload with an attached version_id. The version_id is used to +/// validate the context of the payload. +pub(super) struct Cleartext { + pub(super) version_id: Uuid, + pub(super) payload: HistorySegment, } -impl HistoryCleartext { +impl Cleartext { /// Seal the payload into its ciphertext - pub(super) fn seal(self, secret: &Secret) -> anyhow::Result { - let cryptor = RingCryptor::new().with_aad(self.parent_version_id.as_bytes()); - let ciphertext = cryptor.seal_with_passphrase(secret.as_ref(), &self.history_segment)?; - Ok(HistoryCiphertext(ciphertext)) + pub(super) fn seal(self, secret: &Secret) -> anyhow::Result { + let cryptor = RingCryptor::new().with_aad(self.version_id.as_bytes()); + let ciphertext = cryptor.seal_with_passphrase(secret.as_ref(), &self.payload)?; + Ok(Ciphertext(ciphertext)) } } -/// An ecrypted payload containing a history segment -pub(super) struct HistoryCiphertext(pub(super) Vec); +/// An ecrypted payload +pub(super) struct Ciphertext(pub(super) Vec); -impl HistoryCiphertext { - pub(super) fn open( - self, - secret: &Secret, - parent_version_id: Uuid, - ) -> anyhow::Result { - let cryptor = RingCryptor::new().with_aad(parent_version_id.as_bytes()); - let plaintext = cryptor.open(secret.as_ref(), &self.0)?; - - Ok(HistoryCleartext { - parent_version_id, - history_segment: plaintext, - }) - } -} - -impl TryFrom for HistoryCiphertext { - type Error = anyhow::Error; - - fn try_from(resp: ureq::Response) -> Result { - if let Some("application/vnd.taskchampion.history-segment") = resp.header("Content-Type") { +impl Ciphertext { + pub(super) fn from_resp( + resp: ureq::Response, + content_type: &str, + ) -> Result { + if resp.header("Content-Type") == Some(content_type) { let mut reader = resp.into_reader(); let mut bytes = vec![]; reader.read_to_end(&mut bytes)?; @@ -67,9 +52,19 @@ impl TryFrom for HistoryCiphertext { )) } } + + pub(super) fn open(self, secret: &Secret, version_id: Uuid) -> anyhow::Result { + let cryptor = RingCryptor::new().with_aad(version_id.as_bytes()); + let plaintext = cryptor.open(secret.as_ref(), &self.0)?; + + Ok(Cleartext { + version_id, + payload: plaintext, + }) + } } -impl AsRef<[u8]> for HistoryCiphertext { +impl AsRef<[u8]> for Ciphertext { fn as_ref(&self) -> &[u8] { self.0.as_ref() } @@ -82,52 +77,50 @@ mod test { #[test] fn round_trip() { - let parent_version_id = Uuid::new_v4(); - let history_segment = b"HISTORY REPEATS ITSELF".to_vec(); + let version_id = Uuid::new_v4(); + let payload = b"HISTORY REPEATS ITSELF".to_vec(); let secret = Secret(b"SEKRIT".to_vec()); - let history_cleartext = HistoryCleartext { - parent_version_id, - history_segment: history_segment.clone(), + let cleartext = Cleartext { + version_id, + payload: payload.clone(), }; - let history_ciphertext = history_cleartext.seal(&secret).unwrap(); - let history_cleartext = history_ciphertext.open(&secret, parent_version_id).unwrap(); + let ciphertext = cleartext.seal(&secret).unwrap(); + let cleartext = ciphertext.open(&secret, version_id).unwrap(); - assert_eq!(history_cleartext.history_segment, history_segment); - assert_eq!(history_cleartext.parent_version_id, parent_version_id); + assert_eq!(cleartext.payload, payload); + assert_eq!(cleartext.version_id, version_id); } #[test] fn round_trip_bad_key() { - let parent_version_id = Uuid::new_v4(); - let history_segment = b"HISTORY REPEATS ITSELF".to_vec(); + let version_id = Uuid::new_v4(); + let payload = b"HISTORY REPEATS ITSELF".to_vec(); let secret = Secret(b"SEKRIT".to_vec()); - let history_cleartext = HistoryCleartext { - parent_version_id, - history_segment: history_segment.clone(), + let cleartext = Cleartext { + version_id, + payload: payload.clone(), }; - let history_ciphertext = history_cleartext.seal(&secret).unwrap(); + let ciphertext = cleartext.seal(&secret).unwrap(); let secret = Secret(b"BADSEKRIT".to_vec()); - assert!(history_ciphertext.open(&secret, parent_version_id).is_err()); + assert!(ciphertext.open(&secret, version_id).is_err()); } #[test] - fn round_trip_bad_pvid() { - let parent_version_id = Uuid::new_v4(); - let history_segment = b"HISTORY REPEATS ITSELF".to_vec(); + fn round_trip_bad_version() { + let version_id = Uuid::new_v4(); + let payload = b"HISTORY REPEATS ITSELF".to_vec(); let secret = Secret(b"SEKRIT".to_vec()); - let history_cleartext = HistoryCleartext { - parent_version_id, - history_segment: history_segment.clone(), + let cleartext = Cleartext { + version_id, + payload: payload.clone(), }; - let history_ciphertext = history_cleartext.seal(&secret).unwrap(); + let ciphertext = cleartext.seal(&secret).unwrap(); - let bad_parent_version_id = Uuid::new_v4(); - assert!(history_ciphertext - .open(&secret, bad_parent_version_id) - .is_err()); + let bad_version_id = Uuid::new_v4(); + assert!(ciphertext.open(&secret, bad_version_id).is_err()); } } diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index 9551a448a..139f5dc9f 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -1,12 +1,12 @@ use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, SnapshotUrgency, VersionId, + AddVersionResult, GetVersionResult, HistorySegment, Server, Snapshot, SnapshotUrgency, + VersionId, }; -use std::convert::TryInto; use std::time::Duration; use uuid::Uuid; mod crypto; -use crypto::{HistoryCiphertext, HistoryCleartext, Secret}; +use crypto::{Ciphertext, Cleartext, Secret}; pub struct RemoteServer { origin: String, @@ -15,6 +15,12 @@ pub struct RemoteServer { agent: ureq::Agent, } +/// The content-type for history segments (opaque blobs of bytes) +const HISTORY_SEGMENT_CONTENT_TYPE: &str = "application/vnd.taskchampion.history-segment"; + +/// The content-type for snapshots (opaque blobs of bytes) +const SNAPSHOT_CONTENT_TYPE: &str = "application/vnd.taskchampion.snapshot"; + /// A RemoeServer communicates with a remote server over HTTP (such as with /// taskchampion-sync-server). impl RemoteServer { @@ -67,20 +73,17 @@ impl Server for RemoteServer { "{}/v1/client/add-version/{}", self.origin, parent_version_id ); - let history_cleartext = HistoryCleartext { - parent_version_id, - history_segment, + let cleartext = Cleartext { + version_id: parent_version_id, + payload: history_segment, }; - let history_ciphertext = history_cleartext.seal(&self.encryption_secret)?; + let ciphertext = cleartext.seal(&self.encryption_secret)?; match self .agent .post(&url) - .set( - "Content-Type", - "application/vnd.taskchampion.history-segment", - ) + .set("Content-Type", HISTORY_SEGMENT_CONTENT_TYPE) .set("X-Client-Key", &self.client_key.to_string()) - .send_bytes(history_ciphertext.as_ref()) + .send_bytes(ciphertext.as_ref()) { Ok(resp) => { let version_id = get_uuid_header(&resp, "X-Version-Id")?; @@ -117,10 +120,10 @@ impl Server for RemoteServer { Ok(resp) => { let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; let version_id = get_uuid_header(&resp, "X-Version-Id")?; - let history_ciphertext: HistoryCiphertext = resp.try_into()?; - let history_segment = history_ciphertext + let ciphertext = Ciphertext::from_resp(resp, HISTORY_SEGMENT_CONTENT_TYPE)?; + let history_segment = ciphertext .open(&self.encryption_secret, parent_version_id)? - .history_segment; + .payload; Ok(GetVersionResult::Version { version_id, parent_version_id, @@ -133,4 +136,20 @@ impl Server for RemoteServer { Err(err) => Err(err.into()), } } + + fn add_snapshot(&mut self, version_id: VersionId, snapshot: Snapshot) -> anyhow::Result<()> { + let url = format!("{}/v1/client/add-snapshot/{}", self.origin, version_id); + let cleartext = Cleartext { + version_id, + payload: snapshot, + }; + let ciphertext = cleartext.seal(&self.encryption_secret)?; + Ok(self + .agent + .post(&url) + .set("Content-Type", SNAPSHOT_CONTENT_TYPE) + .set("X-Client-Key", &self.client_key.to_string()) + .send_bytes(ciphertext.as_ref()) + .map(|_| ())?) + } } diff --git a/taskchampion/src/server/test.rs b/taskchampion/src/server/test.rs index aca9e1b94..3901e2fb6 100644 --- a/taskchampion/src/server/test.rs +++ b/taskchampion/src/server/test.rs @@ -1,8 +1,9 @@ use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, SnapshotUrgency, VersionId, - NIL_VERSION_ID, + AddVersionResult, GetVersionResult, HistorySegment, Server, Snapshot, SnapshotUrgency, + VersionId, NIL_VERSION_ID, }; use std::collections::HashMap; +use std::sync::{Arc, Mutex}; use uuid::Uuid; struct Version { @@ -11,19 +12,44 @@ struct Version { history_segment: HistorySegment, } -pub(crate) struct TestServer { +#[derive(Clone)] + +/// TestServer implements the Server trait with a test implementation. +pub(crate) struct TestServer(Arc>); + +pub(crate) struct Inner { latest_version_id: VersionId, // NOTE: indexed by parent_version_id! versions: HashMap, + snapshot_urgency: SnapshotUrgency, + snapshot: Option<(VersionId, Snapshot)>, } impl TestServer { /// A test server has no notion of clients, signatures, encryption, etc. - pub fn new() -> TestServer { - TestServer { + pub(crate) fn new() -> TestServer { + TestServer(Arc::new(Mutex::new(Inner { latest_version_id: NIL_VERSION_ID, versions: HashMap::new(), - } + snapshot_urgency: SnapshotUrgency::None, + snapshot: None, + }))) + } + + /// Get a boxed Server implementation referring to this TestServer + pub(crate) fn server(&self) -> Box { + Box::new(self.clone()) + } + + pub(crate) fn set_snapshot_urgency(&self, urgency: SnapshotUrgency) { + let mut inner = self.0.lock().unwrap(); + inner.snapshot_urgency = urgency; + } + + /// Get the latest snapshot added to this server + pub(crate) fn snapshot(&self) -> Option<(VersionId, Snapshot)> { + let inner = self.0.lock().unwrap(); + inner.snapshot.as_ref().cloned() } } @@ -35,23 +61,24 @@ impl Server for TestServer { parent_version_id: VersionId, history_segment: HistorySegment, ) -> anyhow::Result<(AddVersionResult, SnapshotUrgency)> { + let mut inner = self.0.lock().unwrap(); + // no client lookup // no signature validation // check the parent_version_id for linearity - if self.latest_version_id != NIL_VERSION_ID { - if parent_version_id != self.latest_version_id { - return Ok(( - AddVersionResult::ExpectedParentVersion(self.latest_version_id), - SnapshotUrgency::None, - )); - } + if inner.latest_version_id != NIL_VERSION_ID && parent_version_id != inner.latest_version_id + { + return Ok(( + AddVersionResult::ExpectedParentVersion(inner.latest_version_id), + SnapshotUrgency::None, + )); } // invent a new ID for this version let version_id = Uuid::new_v4(); - self.versions.insert( + inner.versions.insert( parent_version_id, Version { version_id, @@ -59,9 +86,12 @@ impl Server for TestServer { history_segment, }, ); - self.latest_version_id = version_id; + inner.latest_version_id = version_id; - Ok((AddVersionResult::Ok(version_id), SnapshotUrgency::None)) + // reply with the configured urgency and reset it to None + let urgency = inner.snapshot_urgency; + inner.snapshot_urgency = SnapshotUrgency::None; + Ok((AddVersionResult::Ok(version_id), urgency)) } /// Get a vector of all versions after `since_version` @@ -69,7 +99,9 @@ impl Server for TestServer { &mut self, parent_version_id: VersionId, ) -> anyhow::Result { - if let Some(version) = self.versions.get(&parent_version_id) { + let inner = self.0.lock().unwrap(); + + if let Some(version) = inner.versions.get(&parent_version_id) { Ok(GetVersionResult::Version { version_id: version.version_id, parent_version_id: version.parent_version_id, @@ -79,4 +111,12 @@ impl Server for TestServer { Ok(GetVersionResult::NoSuchVersion) } } + + fn add_snapshot(&mut self, version_id: VersionId, snapshot: Snapshot) -> anyhow::Result<()> { + let mut inner = self.0.lock().unwrap(); + + // test implementation -- does not perform any validation + inner.snapshot = Some((version_id, snapshot)); + Ok(()) + } } diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index 35169c5cd..3a1178c41 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -10,6 +10,10 @@ pub const NIL_VERSION_ID: VersionId = Uuid::nil(); /// data is pre-encoded, and from the protocol level appears as a sequence of bytes. pub type HistorySegment = Vec; +/// A snapshot of the state of the task database. This is encoded by the taskdb implementation +/// and treated as a sequence of bytes by the server implementation. +pub type Snapshot = Vec; + /// AddVersionResult is the response type from [`crate::server::Server::add_version`]. #[derive(Debug, PartialEq)] pub enum AddVersionResult { @@ -58,4 +62,7 @@ pub trait Server { &mut self, parent_version_id: VersionId, ) -> anyhow::Result; + + /// Add a snapshot on the server + fn add_snapshot(&mut self, version_id: VersionId, snapshot: Snapshot) -> anyhow::Result<()>; } diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index 91dd40f24..c0762f79b 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -1,5 +1,5 @@ -use super::ops; -use crate::server::{AddVersionResult, GetVersionResult, Server}; +use super::{ops, snapshot}; +use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency}; use crate::storage::{Operation, StorageTxn}; use log::{info, trace, warn}; use serde::{Deserialize, Serialize}; @@ -57,12 +57,19 @@ pub(super) fn sync(server: &mut Box, txn: &mut dyn StorageTxn) -> an let new_version = Version { operations }; let history_segment = serde_json::to_string(&new_version).unwrap().into(); info!("sending new version to server"); - let (res, _snapshot_urgency) = server.add_version(base_version_id, history_segment)?; + let (res, snapshot_urgency) = server.add_version(base_version_id, history_segment)?; match res { AddVersionResult::Ok(new_version_id) => { info!("version {:?} received by server", new_version_id); txn.set_base_version(new_version_id)?; txn.set_operations(vec![])?; + + // TODO: configurable urgency levels + if snapshot_urgency != SnapshotUrgency::None { + let snapshot = snapshot::make_snapshot(txn)?; + server.add_snapshot(new_version_id, snapshot)?; + } + break; } AddVersionResult::ExpectedParentVersion(parent_version_id) => { @@ -150,8 +157,9 @@ mod test { use super::*; use crate::server::test::TestServer; use crate::storage::{InMemoryStorage, Operation}; - use crate::taskdb::TaskDb; + use crate::taskdb::{snapshot::SnapshotTasks, TaskDb}; use chrono::Utc; + use pretty_assertions::assert_eq; use uuid::Uuid; fn newdb() -> TaskDb { @@ -160,7 +168,7 @@ mod test { #[test] fn test_sync() -> anyhow::Result<()> { - let mut server: Box = Box::new(TestServer::new()); + let mut server: Box = TestServer::new().server(); let mut db1 = newdb(); sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); @@ -222,7 +230,7 @@ mod test { #[test] fn test_sync_create_delete() -> anyhow::Result<()> { - let mut server: Box = Box::new(TestServer::new()); + let mut server: Box = TestServer::new().server(); let mut db1 = newdb(); sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); @@ -274,4 +282,37 @@ mod test { Ok(()) } + + #[test] + fn test_sync_adds_snapshot() -> anyhow::Result<()> { + let test_server = TestServer::new(); + + let mut server: Box = test_server.server(); + let mut db1 = newdb(); + + let uuid = Uuid::new_v4(); + db1.apply(Operation::Create { uuid }).unwrap(); + db1.apply(Operation::Update { + uuid, + property: "title".into(), + value: Some("my first task".into()), + timestamp: Utc::now(), + }) + .unwrap(); + + test_server.set_snapshot_urgency(SnapshotUrgency::High); + sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + + // assert that a snapshot was added + let base_version = db1.storage.txn()?.base_version()?; + let (v, s) = test_server + .snapshot() + .ok_or_else(|| anyhow::anyhow!("no snapshot"))?; + assert_eq!(v, base_version); + + let tasks = SnapshotTasks::decode(&s)?.into_inner(); + assert_eq!(tasks[0].0, uuid); + + Ok(()) + } } From ed3475d9eab0b2ee910d0058d34365a6502128c5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 11 Oct 2021 17:14:26 -0400 Subject: [PATCH 372/548] support avoiding snapshots --- cli/src/invocation/cmd/sync.rs | 3 +- replica-server-tests/tests/cross-sync.rs | 10 ++-- taskchampion/src/replica.rs | 17 +++++- taskchampion/src/taskdb/mod.rs | 17 ++++-- taskchampion/src/taskdb/sync.rs | 69 +++++++++++++++++------- 5 files changed, 85 insertions(+), 31 deletions(-) diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index b43b01362..f7c1151d2 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -6,7 +6,8 @@ pub(crate) fn execute( replica: &mut Replica, server: &mut Box, ) -> Result<(), crate::Error> { - replica.sync(server)?; + // TODO: configurable avoid_snapshots + replica.sync(server, false)?; writeln!(w, "sync complete.")?; Ok(()) } diff --git a/replica-server-tests/tests/cross-sync.rs b/replica-server-tests/tests/cross-sync.rs index 8ade694da..881a7481b 100644 --- a/replica-server-tests/tests/cross-sync.rs +++ b/replica-server-tests/tests/cross-sync.rs @@ -41,8 +41,8 @@ async fn cross_sync() -> anyhow::Result<()> { t1.start()?; let t1 = t1.into_immut(); - rep1.sync(&mut serv1)?; - rep2.sync(&mut serv2)?; + rep1.sync(&mut serv1, false)?; + rep2.sync(&mut serv2, false)?; // those tasks should exist on rep2 now let t12 = rep2 @@ -66,9 +66,9 @@ async fn cross_sync() -> anyhow::Result<()> { t12.set_status(Status::Completed)?; // sync those changes back and forth - rep1.sync(&mut serv1)?; // rep1 -> server - rep2.sync(&mut serv2)?; // server -> rep2, rep2 -> server - rep1.sync(&mut serv1)?; // server -> rep1 + rep1.sync(&mut serv1, false)?; // rep1 -> server + rep2.sync(&mut serv2, false)?; // server -> rep2, rep2 -> server + rep1.sync(&mut serv1, false)?; // server -> rep1 let t1 = rep1 .get_task(t1.get_uuid())? diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 89ec5ab80..cc6338ceb 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -126,8 +126,21 @@ impl Replica { /// Synchronize this replica against the given server. The working set is rebuilt after /// this occurs, but without renumbering, so any newly-pending tasks should appear in /// the working set. - pub fn sync(&mut self, server: &mut Box) -> anyhow::Result<()> { - self.taskdb.sync(server).context("Failed to synchronize")?; + /// + /// If `avoid_snapshots` is true, the sync operations produces a snapshot only when the server + /// indicate it is urgent (snapshot urgency "high"). This allows time for other replicas to + /// create a snapshot before this one does. + /// + /// Set this to true on systems more constrained in CPU, memory, or bandwidth than a typical desktop + /// system + pub fn sync( + &mut self, + server: &mut Box, + avoid_snapshots: bool, + ) -> anyhow::Result<()> { + self.taskdb + .sync(server, avoid_snapshots) + .context("Failed to synchronize")?; self.rebuild_working_set(false) .context("Failed to rebuild working set after sync")?; Ok(()) diff --git a/taskchampion/src/taskdb/mod.rs b/taskchampion/src/taskdb/mod.rs index 0394b8c85..65546c462 100644 --- a/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -96,9 +96,20 @@ impl TaskDb { } /// Sync to the given server, pulling remote changes and pushing local changes. - pub fn sync(&mut self, server: &mut Box) -> anyhow::Result<()> { + /// + /// If `avoid_snapshots` is true, the sync operations produces a snapshot only when the server + /// indicate it is urgent (snapshot urgency "high"). This allows time for other replicas to + /// create a snapshot before this one does. + /// + /// Set this to true on systems more constrained in CPU, memory, or bandwidth than a typical desktop + /// system + pub fn sync( + &mut self, + server: &mut Box, + avoid_snapshots: bool, + ) -> anyhow::Result<()> { let mut txn = self.storage.txn()?; - sync::sync(server, txn.as_mut()) + sync::sync(server, txn.as_mut(), avoid_snapshots) } // functions for supporting tests @@ -212,7 +223,7 @@ mod tests { println!(" {:?} (ignored)", e); } }, - Action::Sync => db.sync(&mut server).unwrap(), + Action::Sync => db.sync(&mut server, false).unwrap(), } } diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index c0762f79b..2cd8c2717 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -11,7 +11,11 @@ struct Version { } /// Sync to the given server, pulling remote changes and pushing local changes. -pub(super) fn sync(server: &mut Box, txn: &mut dyn StorageTxn) -> anyhow::Result<()> { +pub(super) fn sync( + server: &mut Box, + txn: &mut dyn StorageTxn, + avoid_snapshots: bool, +) -> anyhow::Result<()> { // retry synchronizing until the server accepts our version (this allows for races between // replicas trying to sync to the same server). If the server insists on the same base // version twice, then we have diverged. @@ -64,8 +68,13 @@ pub(super) fn sync(server: &mut Box, txn: &mut dyn StorageTxn) -> an txn.set_base_version(new_version_id)?; txn.set_operations(vec![])?; - // TODO: configurable urgency levels - if snapshot_urgency != SnapshotUrgency::None { + // make a snapshot if the server indicates it is urgent enough + let base_urgency = if avoid_snapshots { + SnapshotUrgency::High + } else { + SnapshotUrgency::Low + }; + if snapshot_urgency >= base_urgency { let snapshot = snapshot::make_snapshot(txn)?; server.add_snapshot(new_version_id, snapshot)?; } @@ -171,10 +180,10 @@ mod test { let mut server: Box = TestServer::new().server(); let mut db1 = newdb(); - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); let mut db2 = newdb(); - sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); // make some changes in parallel to db1 and db2.. let uuid1 = Uuid::new_v4(); @@ -198,9 +207,9 @@ mod test { .unwrap(); // and synchronize those around - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); - sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // now make updates to the same task on both sides @@ -220,9 +229,9 @@ mod test { .unwrap(); // and synchronize those around - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); - sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); Ok(()) @@ -233,10 +242,10 @@ mod test { let mut server: Box = TestServer::new().server(); let mut db1 = newdb(); - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); let mut db2 = newdb(); - sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); // create and update a task.. let uuid = Uuid::new_v4(); @@ -250,9 +259,9 @@ mod test { .unwrap(); // and synchronize those around - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); - sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // delete and re-create the task on db1 @@ -275,9 +284,9 @@ mod test { }) .unwrap(); - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); - sync(&mut server, db2.storage.txn()?.as_mut()).unwrap(); - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); + sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); Ok(()) @@ -301,7 +310,7 @@ mod test { .unwrap(); test_server.set_snapshot_urgency(SnapshotUrgency::High); - sync(&mut server, db1.storage.txn()?.as_mut()).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); // assert that a snapshot was added let base_version = db1.storage.txn()?.base_version()?; @@ -315,4 +324,24 @@ mod test { Ok(()) } + + #[test] + fn test_sync_avoids_snapshot() -> anyhow::Result<()> { + let test_server = TestServer::new(); + + let mut server: Box = test_server.server(); + let mut db1 = newdb(); + + let uuid = Uuid::new_v4(); + db1.apply(Operation::Create { uuid }).unwrap(); + + test_server.set_snapshot_urgency(SnapshotUrgency::Low); + sync(&mut server, db1.storage.txn()?.as_mut(), true).unwrap(); + + // assert that a snapshot was not added, because we indicated + // we wanted to avoid snapshots and it was only low urgency + assert_eq!(test_server.snapshot(), None); + + Ok(()) + } } From 333cb370914f09819565bf4de212c8362093c69d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 11 Oct 2021 17:25:28 -0400 Subject: [PATCH 373/548] Support `add_snapshots` on cli --- .changelogs/2021-10-11-issue23-client.md | 2 ++ cli/src/invocation/cmd/sync.rs | 8 +++++--- cli/src/invocation/mod.rs | 2 +- cli/src/settings/settings.rs | 21 +++++++++++++++++++++ docs/src/config-file.md | 6 ++++++ 5 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .changelogs/2021-10-11-issue23-client.md diff --git a/.changelogs/2021-10-11-issue23-client.md b/.changelogs/2021-10-11-issue23-client.md new file mode 100644 index 000000000..91a6b0f9e --- /dev/null +++ b/.changelogs/2021-10-11-issue23-client.md @@ -0,0 +1,2 @@ +- The `avoid_snapshots` configuration value, if set, will cause the replica to + avoid creating snapshots unless required. diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index f7c1151d2..a042a5bf4 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -1,13 +1,14 @@ +use crate::settings::Settings; use taskchampion::{server::Server, Replica}; use termcolor::WriteColor; pub(crate) fn execute( w: &mut W, replica: &mut Replica, + settings: &Settings, server: &mut Box, ) -> Result<(), crate::Error> { - // TODO: configurable avoid_snapshots - replica.sync(server, false)?; + replica.sync(server, settings.avoid_snapshots)?; writeln!(w, "sync complete.")?; Ok(()) } @@ -25,9 +26,10 @@ mod test { let mut replica = test_replica(); let server_dir = TempDir::new().unwrap(); let mut server = test_server(&server_dir); + let settings = Settings::default(); // Note that the details of the actual sync are tested thoroughly in the taskchampion crate - execute(&mut w, &mut replica, &mut server).unwrap(); + execute(&mut w, &mut replica, &settings, &mut server).unwrap(); assert_eq!(&w.into_string(), "sync complete.\n") } } diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 640925a69..2f225f86a 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -87,7 +87,7 @@ pub(crate) fn invoke(command: Command, settings: Settings) -> Result<(), crate:: .. } => { let mut server = get_server(&settings)?; - return cmd::sync::execute(&mut w, &mut replica, &mut server); + return cmd::sync::execute(&mut w, &mut replica, &settings, &mut server); } // handled in the first match, but here to ensure this match is exhaustive diff --git a/cli/src/settings/settings.rs b/cli/src/settings/settings.rs index 7e94a637b..3dba86409 100644 --- a/cli/src/settings/settings.rs +++ b/cli/src/settings/settings.rs @@ -22,6 +22,7 @@ pub(crate) struct Settings { /// replica pub(crate) data_dir: PathBuf, + pub(crate) avoid_snapshots: bool, /// remote sync server pub(crate) server_client_key: Option, @@ -91,6 +92,7 @@ impl Settings { let table_keys = [ "data_dir", "modification_count_prompt", + "avoid_snapshots", "server_client_key", "server_origin", "encryption_secret", @@ -124,6 +126,20 @@ impl Settings { Ok(()) } + fn get_bool_cfg( + table: &Table, + name: &'static str, + setter: F, + ) -> Result<()> { + if let Some(v) = table.get(name) { + setter( + v.as_bool() + .ok_or_else(|| anyhow!(".{}: not a boolean value", name))?, + ); + } + Ok(()) + } + get_str_cfg(table, "data_dir", |v| { self.data_dir = v.into(); })?; @@ -132,6 +148,10 @@ impl Settings { self.modification_count_prompt = Some(v); })?; + get_bool_cfg(table, "avoid_snapshots", |v| { + self.avoid_snapshots = v; + })?; + get_str_cfg(table, "server_client_key", |v| { self.server_client_key = Some(v); })?; @@ -313,6 +333,7 @@ impl Default for Settings { filename: None, data_dir, modification_count_prompt: None, + avoid_snapshots: false, server_client_key: None, server_origin: None, encryption_secret: None, diff --git a/docs/src/config-file.md b/docs/src/config-file.md index 03639546f..73dc28b60 100644 --- a/docs/src/config-file.md +++ b/docs/src/config-file.md @@ -46,6 +46,12 @@ If using a remote server: * `server_client_key` - Client key to identify this replica to the sync server (a UUID) If not set, then sync is done to a local server. +## Snapshots + +* `avoid_snapshots` - If running on a CPU-, memory-, or bandwidth-constrained + device, set this to true. The effect is that this replica will wait longer + to produce a snapshot, in the hopes that other replicas will do so first. + ## Reports * `reports` - a mapping of each report's name to its definition. From 1c6a5315c901aef96c451c7dea172c0f0f766d32 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 11 Oct 2021 21:29:49 -0400 Subject: [PATCH 374/548] remove unused file --- taskchampion/src/testing/mod.rs | 1 - 1 file changed, 1 deletion(-) delete mode 100644 taskchampion/src/testing/mod.rs diff --git a/taskchampion/src/testing/mod.rs b/taskchampion/src/testing/mod.rs deleted file mode 100644 index 766fbbda5..000000000 --- a/taskchampion/src/testing/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod testserver; From 636862f8c5606bb9969c6bbeea8182c9a2e3ccc4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 13 Oct 2021 17:59:58 -0400 Subject: [PATCH 375/548] update README with current relationship to TW --- README.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a4582257e..10e119460 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,11 @@ See the [documentation](https://taskchampion.github.io/taskchampion/) for more! ## Status -TC is still under development. -You are welcome to [help out!](https://github.com/djmitche/taskchampion/blob/main/CONTRIBUTING.md). -Even if you just want to get some practice with Rust, your contribution is welcome. +TaskChampion currently functions as a "testbed" for new functionality that may later be incorporated into TaskWarrior. +It can be developed without the requirements of compatibliity, allowing us to explore and fix edge-cases in things like the replica-synchronization model. -Since development of TaskChampion began, TaskWarrior developers have resumed work and made several releases. -Assuming that continues, it is unlikely that TaskChampion will ever be recommended for day-to-day use, as that would only serve to split the TaskWarrior community. - -## Goals - - * Feature parity with TaskWarrior (but not compatibility) - * Aproachable, maintainable codebase - * Active development community - * Reasonable privacy: user's task details not visible on server - * Reliable concurrency - clients do not diverge - * Storage performance O(n) with n number of tasks +While you are welcome to [help out](https://github.com/taskchampion/taskchampion/blob/main/CONTRIBUTING.md), you should do so with the awareness that your work might never be used. +But, if you just want to get some practice with Rust, we'd be happy to have you. ## Structure From ddfb327292ddf4d716ddf932abf685d1c6a8c8c4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 15 Oct 2021 02:56:46 +0000 Subject: [PATCH 376/548] WIP --- Cargo.lock | 1 + taskchampion/Cargo.toml | 1 + taskchampion/src/server/remote/crypto.rs | 70 +++++++++++++++++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 10f51edf9..9ac25caa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2961,6 +2961,7 @@ name = "taskchampion" version = "0.4.1" dependencies = [ "anyhow", + "byteorder", "chrono", "flate2", "log", diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index c5da9898c..b48ed4491 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -24,6 +24,7 @@ rusqlite = { version = "0.25", features = ["bundled"] } strum = "0.21" strum_macros = "0.21" flate2 = "1" +byteorder = "1.0" [dev-dependencies] proptest = "^1.0.0" diff --git a/taskchampion/src/server/remote/crypto.rs b/taskchampion/src/server/remote/crypto.rs index 40103f422..65bdddbfa 100644 --- a/taskchampion/src/server/remote/crypto.rs +++ b/taskchampion/src/server/remote/crypto.rs @@ -1,5 +1,6 @@ use crate::server::HistorySegment; -use std::io::Read; +use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Cursor, Read}; use tindercrypt::cryptors::RingCryptor; use uuid::Uuid; @@ -17,6 +18,60 @@ impl AsRef<[u8]> for Secret { } } +const PBKDF2_SALT_SIZE: usize = 32; +const NONCE_SIZE: usize = 12; +const ENVELOPE_VERSION: u32 = 1; + +/// Envelope for the data stored on the server, containing the information +/// required to decrypt. +#[derive(Debug, PartialEq, Eq)] +struct Envelope { + kdf_iterations: u32, + kdf_salt: [u8; PBKDF2_SALT_SIZE], + nonce: [u8; NONCE_SIZE], +} + +impl Envelope { + const SIZE: usize = 4 + 4 + PBKDF2_SALT_SIZE + NONCE_SIZE; + + fn from_bytes(buf: &[u8]) -> anyhow::Result { + if buf.len() < 4 { + anyhow::bail!("envelope is too small"); + } + + let mut rdr = Cursor::new(buf); + let version = rdr.read_u32::().unwrap(); + if version != 1 { + anyhow::bail!("unrecognized envelope version {}", version); + } + if buf.len() != Envelope::SIZE { + anyhow::bail!("envelope size {} is not {}", buf.len(), Envelope::SIZE); + } + + let kdf_iterations = rdr.read_u32::().unwrap(); + + let mut env = Envelope { + kdf_iterations, + kdf_salt: [0; PBKDF2_SALT_SIZE], + nonce: [0; NONCE_SIZE], + }; + env.kdf_salt.clone_from_slice(&buf[8..8 + PBKDF2_SALT_SIZE]); + env.nonce + .clone_from_slice(&buf[8 + PBKDF2_SALT_SIZE..8 + PBKDF2_SALT_SIZE + NONCE_SIZE]); + Ok(env) // TODO: test + } + + fn to_bytes(&self) -> Vec { + let mut buf = Vec::with_capacity(Envelope::SIZE); + + buf.write_u32::(ENVELOPE_VERSION).unwrap(); + buf.write_u32::(self.kdf_iterations).unwrap(); + buf.extend_from_slice(&self.kdf_salt); + buf.extend_from_slice(&self.nonce); + buf // TODO: test + } +} + /// A cleartext payload with an attached version_id. The version_id is used to /// validate the context of the payload. pub(super) struct Cleartext { @@ -75,6 +130,19 @@ mod test { use super::*; use pretty_assertions::assert_eq; + #[test] + fn envelope_round_trip() { + let env = Envelope { + kdf_iterations: 100, + kdf_salt: [1; 32], + nonce: [2; 12], + }; + + let bytes = env.to_bytes(); + let env2 = Envelope::from_bytes(&bytes).unwrap(); + assert_eq!(env, env2); + } + #[test] fn round_trip() { let version_id = Uuid::new_v4(); From 0f39a3f3b247d2ce8a691a879e57c862e07e84b9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 16 Oct 2021 21:05:25 +0000 Subject: [PATCH 377/548] [BREAKING CHANGE] drop use of tindercrypt, and use ring directly --- .changelogs/2021-10-16-issue299.md | 1 + Cargo.lock | 20 +- docs/src/sync-protocol.md | 27 ++- taskchampion/Cargo.toml | 2 +- taskchampion/src/server/config.rs | 2 +- taskchampion/src/server/remote/crypto.rs | 263 +++++++++++++++-------- taskchampion/src/server/remote/mod.rs | 35 +-- 7 files changed, 228 insertions(+), 122 deletions(-) create mode 100644 .changelogs/2021-10-16-issue299.md diff --git a/.changelogs/2021-10-16-issue299.md b/.changelogs/2021-10-16-issue299.md new file mode 100644 index 000000000..a74af24c5 --- /dev/null +++ b/.changelogs/2021-10-16-issue299.md @@ -0,0 +1 @@ +- The encryption format used for synchronization has changed incompatibly diff --git a/Cargo.lock b/Cargo.lock index 9ac25caa0..523501bb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2253,12 +2253,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "protobuf" -version = "2.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db50e77ae196458ccd3dc58a31ea1a90b0698ab1b7928d89f644c25d72070267" - [[package]] name = "pulldown-cmark" version = "0.7.2" @@ -2967,6 +2961,7 @@ dependencies = [ "log", "pretty_assertions", "proptest", + "ring", "rstest", "rusqlite", "serde", @@ -2975,7 +2970,6 @@ dependencies = [ "strum_macros 0.21.1", "tempfile", "thiserror", - "tindercrypt", "ureq", "uuid", ] @@ -3184,18 +3178,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tindercrypt" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34fd5cc8db265f27abf29e8a3cec5cc643ae9f3f9ae39f08a77dc4add375b6d" -dependencies = [ - "protobuf", - "rand 0.7.3", - "ring", - "thiserror", -] - [[package]] name = "tinyvec" version = "1.2.0" diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md index 1f111312c..252b16bef 100644 --- a/docs/src/sync-protocol.md +++ b/docs/src/sync-protocol.md @@ -42,7 +42,32 @@ The fourth invariant prevents the server from discarding versions newer than the ### Encryption -TBD (#299) +The client configuration includes an encryption secret of arbitrary length and a clientId to identify itself. +This section describes how that information is used to encrypt and decrypt data sent to the server (versions and snapshots). + +#### Key Derivation + +The client derives the 32-byte encryption key from the configured encryption secret using PBKDF2 with HMAC-SHA256 and 100,000 iterations. +The salt is the SHA256 hash of the 16-byte form of the client key. + +#### Encryption + +The client uses [AEAD](https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html), with algorithm AES_256_GCM. +Each encrypted payload has an associated version ID. +The 16-byte form of this UUID is used as the associated data (AAD) with the AEAD algorithm. +The client should generate a random nonce, noting that AEAD is _not secure_ if a nonce is used repeatedly for the same key. + +Although the AEAD specification distinguishes ciphertext and tags, for purposes of this specification they are considered concatenated into a single bytestring as in BoringSSL's `EVP_AEAD_CTX_seal`. + +#### Representation + +The final byte-stream is comprised of the following structure, with integers represented in network-endian format. + +* `version` (32-bit int) - format version (always 1) +* `nonce` (12 bytes) - encryption nonce +* `ciphertext` (remaining bytes) - ciphertext from sealing operation + +Future versions may have a completely different format. ### Version diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index b48ed4491..18caeaadf 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -19,12 +19,12 @@ anyhow = "1.0" thiserror = "1.0" ureq = "^2.1.0" log = "^0.4.14" -tindercrypt = { version = "^0.2.2", default-features = false } rusqlite = { version = "0.25", features = ["bundled"] } strum = "0.21" strum_macros = "0.21" flate2 = "1" byteorder = "1.0" +ring = "0.16" [dev-dependencies] proptest = "^1.0.0" diff --git a/taskchampion/src/server/config.rs b/taskchampion/src/server/config.rs index efc444ed8..68ed00b68 100644 --- a/taskchampion/src/server/config.rs +++ b/taskchampion/src/server/config.rs @@ -33,7 +33,7 @@ impl ServerConfig { origin, client_key, encryption_secret, - } => Box::new(RemoteServer::new(origin, client_key, encryption_secret)), + } => Box::new(RemoteServer::new(origin, client_key, encryption_secret)?), }) } } diff --git a/taskchampion/src/server/remote/crypto.rs b/taskchampion/src/server/remote/crypto.rs index 65bdddbfa..45e2adac8 100644 --- a/taskchampion/src/server/remote/crypto.rs +++ b/taskchampion/src/server/remote/crypto.rs @@ -1,9 +1,107 @@ -use crate::server::HistorySegment; +/// This module implements the encryption specified in the sync-protocol +/// document. use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; +use ring::{aead, digest, pbkdf2, rand, rand::SecureRandom}; use std::io::{Cursor, Read}; -use tindercrypt::cryptors::RingCryptor; use uuid::Uuid; +const PBKDF2_ITERATIONS: u32 = 100000; +const ENVELOPE_VERSION: u32 = 1; + +/// An Cryptor stores a secret and allows sealing and unsealing. It derives a key from the secret, +/// which takes a nontrivial amount of time, so it should be created once and re-used for the given +/// client_key. +pub(super) struct Cryptor { + key: aead::LessSafeKey, + rng: rand::SystemRandom, +} + +impl Cryptor { + pub(super) fn new(client_key: Uuid, secret: &Secret) -> anyhow::Result { + Ok(Cryptor { + key: Self::derive_key(client_key, secret)?, + rng: rand::SystemRandom::new(), + }) + } + + /// Derive a key as specified for version 1. Note that this may take 10s of ms. + fn derive_key(client_key: Uuid, secret: &Secret) -> anyhow::Result { + let salt = digest::digest(&digest::SHA256, client_key.as_bytes()); + + let mut key_bytes = vec![0u8; ring::aead::AES_256_GCM.key_len()]; + pbkdf2::derive( + pbkdf2::PBKDF2_HMAC_SHA256, + std::num::NonZeroU32::new(PBKDF2_ITERATIONS).unwrap(), + salt.as_ref(), + secret.as_ref(), + &mut key_bytes, + ); + + let unbound_key = ring::aead::UnboundKey::new(&ring::aead::AES_256_GCM, &key_bytes) + .map_err(|_| anyhow::anyhow!("error while creating AEAD key"))?; + Ok(ring::aead::LessSafeKey::new(unbound_key)) + } + + /// Encrypt the given payload. + pub(super) fn seal(&self, payload: Unsealed) -> anyhow::Result { + let Unsealed { + version_id, + mut payload, + } = payload; + + let mut nonce_buf = [0u8; aead::NONCE_LEN]; + self.rng + .fill(&mut nonce_buf) + .map_err(|_| anyhow::anyhow!("error generating random nonce"))?; + let nonce = ring::aead::Nonce::assume_unique_for_key(nonce_buf); + + let aad = ring::aead::Aad::from(version_id.as_bytes()); + + let tag = self + .key + .seal_in_place_separate_tag(nonce, aad, &mut payload) + .map_err(|_| anyhow::anyhow!("error while sealing"))?; + payload.extend_from_slice(tag.as_ref()); + + let env = Envelope { + nonce: &nonce_buf, + payload: payload.as_ref(), + }; + + Ok(Sealed { + version_id, + payload: env.to_bytes(), + }) + } + + /// Decrypt the given payload, verifying it was created for the given version_id + pub(super) fn unseal(&self, payload: Sealed) -> anyhow::Result { + let Sealed { + version_id, + payload, + } = payload; + + let env = Envelope::from_bytes(&payload)?; + + let mut nonce = [0u8; aead::NONCE_LEN]; + nonce.copy_from_slice(env.nonce); + let nonce = ring::aead::Nonce::assume_unique_for_key(nonce); + let aad = ring::aead::Aad::from(version_id.as_bytes()); + + let mut payload = env.payload.to_vec(); + let plaintext = self + .key + .open_in_place(nonce, aad, payload.as_mut()) + .map_err(|_| anyhow::anyhow!("error while creating AEAD key"))?; + + Ok(Unsealed { + version_id, + payload: plaintext.to_vec(), + }) + } +} + +/// Secret represents a secret key as used for encryption and decryption. pub(super) struct Secret(pub(super) Vec); impl From> for Secret { @@ -18,24 +116,17 @@ impl AsRef<[u8]> for Secret { } } -const PBKDF2_SALT_SIZE: usize = 32; -const NONCE_SIZE: usize = 12; -const ENVELOPE_VERSION: u32 = 1; - /// Envelope for the data stored on the server, containing the information /// required to decrypt. #[derive(Debug, PartialEq, Eq)] -struct Envelope { - kdf_iterations: u32, - kdf_salt: [u8; PBKDF2_SALT_SIZE], - nonce: [u8; NONCE_SIZE], +struct Envelope<'a> { + nonce: &'a [u8], + payload: &'a [u8], } -impl Envelope { - const SIZE: usize = 4 + 4 + PBKDF2_SALT_SIZE + NONCE_SIZE; - - fn from_bytes(buf: &[u8]) -> anyhow::Result { - if buf.len() < 4 { +impl<'a> Envelope<'a> { + fn from_bytes(buf: &'a [u8]) -> anyhow::Result> { + if buf.len() <= 4 + aead::NONCE_LEN { anyhow::bail!("envelope is too small"); } @@ -44,84 +135,61 @@ impl Envelope { if version != 1 { anyhow::bail!("unrecognized envelope version {}", version); } - if buf.len() != Envelope::SIZE { - anyhow::bail!("envelope size {} is not {}", buf.len(), Envelope::SIZE); - } - let kdf_iterations = rdr.read_u32::().unwrap(); - - let mut env = Envelope { - kdf_iterations, - kdf_salt: [0; PBKDF2_SALT_SIZE], - nonce: [0; NONCE_SIZE], - }; - env.kdf_salt.clone_from_slice(&buf[8..8 + PBKDF2_SALT_SIZE]); - env.nonce - .clone_from_slice(&buf[8 + PBKDF2_SALT_SIZE..8 + PBKDF2_SALT_SIZE + NONCE_SIZE]); - Ok(env) // TODO: test + Ok(Envelope { + nonce: &buf[4..4 + aead::NONCE_LEN], + payload: &buf[4 + aead::NONCE_LEN..], + }) } fn to_bytes(&self) -> Vec { - let mut buf = Vec::with_capacity(Envelope::SIZE); + let mut buf = Vec::with_capacity(4 + self.nonce.len() + self.payload.len()); buf.write_u32::(ENVELOPE_VERSION).unwrap(); - buf.write_u32::(self.kdf_iterations).unwrap(); - buf.extend_from_slice(&self.kdf_salt); - buf.extend_from_slice(&self.nonce); - buf // TODO: test + buf.extend_from_slice(self.nonce); + buf.extend_from_slice(self.payload); + buf } } -/// A cleartext payload with an attached version_id. The version_id is used to -/// validate the context of the payload. -pub(super) struct Cleartext { +/// A unsealed payload with an attached version_id. The version_id is used to +/// validate the context of the payload on unsealing. +pub(super) struct Unsealed { pub(super) version_id: Uuid, - pub(super) payload: HistorySegment, + pub(super) payload: Vec, } -impl Cleartext { - /// Seal the payload into its ciphertext - pub(super) fn seal(self, secret: &Secret) -> anyhow::Result { - let cryptor = RingCryptor::new().with_aad(self.version_id.as_bytes()); - let ciphertext = cryptor.seal_with_passphrase(secret.as_ref(), &self.payload)?; - Ok(Ciphertext(ciphertext)) - } +/// An encrypted payload +pub(super) struct Sealed { + pub(super) version_id: Uuid, + pub(super) payload: Vec, } -/// An ecrypted payload -pub(super) struct Ciphertext(pub(super) Vec); - -impl Ciphertext { +impl Sealed { pub(super) fn from_resp( resp: ureq::Response, + version_id: Uuid, content_type: &str, - ) -> Result { + ) -> Result { if resp.header("Content-Type") == Some(content_type) { let mut reader = resp.into_reader(); - let mut bytes = vec![]; - reader.read_to_end(&mut bytes)?; - Ok(Self(bytes)) + let mut payload = vec![]; + reader.read_to_end(&mut payload)?; + Ok(Self { + version_id, + payload, + }) } else { Err(anyhow::anyhow!( "Response did not have expected content-type" )) } } - - pub(super) fn open(self, secret: &Secret, version_id: Uuid) -> anyhow::Result { - let cryptor = RingCryptor::new().with_aad(version_id.as_bytes()); - let plaintext = cryptor.open(secret.as_ref(), &self.0)?; - - Ok(Cleartext { - version_id, - payload: plaintext, - }) - } } -impl AsRef<[u8]> for Ciphertext { +impl AsRef<[u8]> for Sealed { fn as_ref(&self) -> &[u8] { - self.0.as_ref() + self.payload.as_ref() } } @@ -133,9 +201,8 @@ mod test { #[test] fn envelope_round_trip() { let env = Envelope { - kdf_iterations: 100, - kdf_salt: [1; 32], - nonce: [2; 12], + nonce: &[2; 12], + payload: b"HELLO", }; let bytes = env.to_bytes(); @@ -147,48 +214,76 @@ mod test { fn round_trip() { let version_id = Uuid::new_v4(); let payload = b"HISTORY REPEATS ITSELF".to_vec(); - let secret = Secret(b"SEKRIT".to_vec()); - let cleartext = Cleartext { + let secret = Secret(b"SEKRIT".to_vec()); + let cryptor = Cryptor::new(Uuid::new_v4(), &secret).unwrap(); + + let unsealed = Unsealed { version_id, payload: payload.clone(), }; - let ciphertext = cleartext.seal(&secret).unwrap(); - let cleartext = ciphertext.open(&secret, version_id).unwrap(); + let sealed = cryptor.seal(unsealed).unwrap(); + let unsealed = cryptor.unseal(sealed).unwrap(); - assert_eq!(cleartext.payload, payload); - assert_eq!(cleartext.version_id, version_id); + assert_eq!(unsealed.payload, payload); + assert_eq!(unsealed.version_id, version_id); } #[test] fn round_trip_bad_key() { let version_id = Uuid::new_v4(); let payload = b"HISTORY REPEATS ITSELF".to_vec(); - let secret = Secret(b"SEKRIT".to_vec()); + let client_key = Uuid::new_v4(); - let cleartext = Cleartext { + let secret = Secret(b"SEKRIT".to_vec()); + let cryptor = Cryptor::new(client_key, &secret).unwrap(); + + let unsealed = Unsealed { version_id, payload: payload.clone(), }; - let ciphertext = cleartext.seal(&secret).unwrap(); + let sealed = cryptor.seal(unsealed).unwrap(); - let secret = Secret(b"BADSEKRIT".to_vec()); - assert!(ciphertext.open(&secret, version_id).is_err()); + let secret = Secret(b"DIFFERENT_SECRET".to_vec()); + let cryptor = Cryptor::new(client_key, &secret).unwrap(); + assert!(cryptor.unseal(sealed).is_err()); } #[test] fn round_trip_bad_version() { let version_id = Uuid::new_v4(); let payload = b"HISTORY REPEATS ITSELF".to_vec(); - let secret = Secret(b"SEKRIT".to_vec()); + let client_key = Uuid::new_v4(); - let cleartext = Cleartext { + let secret = Secret(b"SEKRIT".to_vec()); + let cryptor = Cryptor::new(client_key, &secret).unwrap(); + + let unsealed = Unsealed { version_id, payload: payload.clone(), }; - let ciphertext = cleartext.seal(&secret).unwrap(); + let mut sealed = cryptor.seal(unsealed).unwrap(); + sealed.version_id = Uuid::new_v4(); // change the version_id + assert!(cryptor.unseal(sealed).is_err()); + } - let bad_version_id = Uuid::new_v4(); - assert!(ciphertext.open(&secret, bad_version_id).is_err()); + #[test] + fn round_trip_bad_client_key() { + let version_id = Uuid::new_v4(); + let payload = b"HISTORY REPEATS ITSELF".to_vec(); + let client_key = Uuid::new_v4(); + + let secret = Secret(b"SEKRIT".to_vec()); + let cryptor = Cryptor::new(client_key, &secret).unwrap(); + + let unsealed = Unsealed { + version_id, + payload: payload.clone(), + }; + let sealed = cryptor.seal(unsealed).unwrap(); + + let client_key = Uuid::new_v4(); + let cryptor = Cryptor::new(client_key, &secret).unwrap(); + assert!(cryptor.unseal(sealed).is_err()); } } diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index 139f5dc9f..e9d29b1ea 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -6,12 +6,12 @@ use std::time::Duration; use uuid::Uuid; mod crypto; -use crypto::{Ciphertext, Cleartext, Secret}; +use crypto::{Cryptor, Sealed, Secret, Unsealed}; pub struct RemoteServer { origin: String, client_key: Uuid, - encryption_secret: Secret, + cryptor: Cryptor, agent: ureq::Agent, } @@ -28,16 +28,20 @@ impl RemoteServer { /// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_key to /// identify this client to the server. Multiple replicas synchronizing the same task history /// should use the same client_key. - pub fn new(origin: String, client_key: Uuid, encryption_secret: Vec) -> RemoteServer { - RemoteServer { + pub fn new( + origin: String, + client_key: Uuid, + encryption_secret: Vec, + ) -> anyhow::Result { + Ok(RemoteServer { origin, client_key, - encryption_secret: encryption_secret.into(), + cryptor: Cryptor::new(client_key, &Secret(encryption_secret.to_vec()))?, agent: ureq::AgentBuilder::new() .timeout_connect(Duration::from_secs(10)) .timeout_read(Duration::from_secs(60)) .build(), - } + }) } } @@ -73,17 +77,17 @@ impl Server for RemoteServer { "{}/v1/client/add-version/{}", self.origin, parent_version_id ); - let cleartext = Cleartext { + let unsealed = Unsealed { version_id: parent_version_id, payload: history_segment, }; - let ciphertext = cleartext.seal(&self.encryption_secret)?; + let sealed = self.cryptor.seal(unsealed)?; match self .agent .post(&url) .set("Content-Type", HISTORY_SEGMENT_CONTENT_TYPE) .set("X-Client-Key", &self.client_key.to_string()) - .send_bytes(ciphertext.as_ref()) + .send_bytes(sealed.as_ref()) { Ok(resp) => { let version_id = get_uuid_header(&resp, "X-Version-Id")?; @@ -120,10 +124,9 @@ impl Server for RemoteServer { Ok(resp) => { let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; let version_id = get_uuid_header(&resp, "X-Version-Id")?; - let ciphertext = Ciphertext::from_resp(resp, HISTORY_SEGMENT_CONTENT_TYPE)?; - let history_segment = ciphertext - .open(&self.encryption_secret, parent_version_id)? - .payload; + let sealed = + Sealed::from_resp(resp, parent_version_id, HISTORY_SEGMENT_CONTENT_TYPE)?; + let history_segment = self.cryptor.unseal(sealed)?.payload; Ok(GetVersionResult::Version { version_id, parent_version_id, @@ -139,17 +142,17 @@ impl Server for RemoteServer { fn add_snapshot(&mut self, version_id: VersionId, snapshot: Snapshot) -> anyhow::Result<()> { let url = format!("{}/v1/client/add-snapshot/{}", self.origin, version_id); - let cleartext = Cleartext { + let unsealed = Unsealed { version_id, payload: snapshot, }; - let ciphertext = cleartext.seal(&self.encryption_secret)?; + let sealed = self.cryptor.seal(unsealed)?; Ok(self .agent .post(&url) .set("Content-Type", SNAPSHOT_CONTENT_TYPE) .set("X-Client-Key", &self.client_key.to_string()) - .send_bytes(ciphertext.as_ref()) + .send_bytes(sealed.as_ref()) .map(|_| ())?) } } From 97d1366b660334c09118e06611813193af8c6e50 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 16 Oct 2021 22:37:28 +0000 Subject: [PATCH 378/548] move taskchampion::server::remote::crypto to taskchampion::server::crypto --- taskchampion/src/server/{remote => }/crypto.rs | 0 taskchampion/src/server/mod.rs | 1 + taskchampion/src/server/remote/mod.rs | 3 +-- 3 files changed, 2 insertions(+), 2 deletions(-) rename taskchampion/src/server/{remote => }/crypto.rs (100%) diff --git a/taskchampion/src/server/remote/crypto.rs b/taskchampion/src/server/crypto.rs similarity index 100% rename from taskchampion/src/server/remote/crypto.rs rename to taskchampion/src/server/crypto.rs diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 68b5a713f..eb77b9bd3 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -12,6 +12,7 @@ However, users who wish to implement their own server interfaces can implement t pub(crate) mod test; mod config; +mod crypto; mod local; mod remote; mod types; diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index e9d29b1ea..8ddfa52ff 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -5,8 +5,7 @@ use crate::server::{ use std::time::Duration; use uuid::Uuid; -mod crypto; -use crypto::{Cryptor, Sealed, Secret, Unsealed}; +use super::crypto::{Cryptor, Sealed, Secret, Unsealed}; pub struct RemoteServer { origin: String, From 4300f7bddaa51b159730036e3d67e796209d2fc2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 17 Oct 2021 17:36:30 -0400 Subject: [PATCH 379/548] use CHACHA20_POLY1305 instead of AES_256_GCM --- docs/src/sync-protocol.md | 2 +- taskchampion/src/server/crypto.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md index 252b16bef..4d86f0af6 100644 --- a/docs/src/sync-protocol.md +++ b/docs/src/sync-protocol.md @@ -52,7 +52,7 @@ The salt is the SHA256 hash of the 16-byte form of the client key. #### Encryption -The client uses [AEAD](https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html), with algorithm AES_256_GCM. +The client uses [AEAD](https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html), with algorithm CHACHA20_POLY1305. Each encrypted payload has an associated version ID. The 16-byte form of this UUID is used as the associated data (AAD) with the AEAD algorithm. The client should generate a random nonce, noting that AEAD is _not secure_ if a nonce is used repeatedly for the same key. diff --git a/taskchampion/src/server/crypto.rs b/taskchampion/src/server/crypto.rs index 45e2adac8..fe47f65d5 100644 --- a/taskchampion/src/server/crypto.rs +++ b/taskchampion/src/server/crypto.rs @@ -28,7 +28,7 @@ impl Cryptor { fn derive_key(client_key: Uuid, secret: &Secret) -> anyhow::Result { let salt = digest::digest(&digest::SHA256, client_key.as_bytes()); - let mut key_bytes = vec![0u8; ring::aead::AES_256_GCM.key_len()]; + let mut key_bytes = vec![0u8; aead::CHACHA20_POLY1305.key_len()]; pbkdf2::derive( pbkdf2::PBKDF2_HMAC_SHA256, std::num::NonZeroU32::new(PBKDF2_ITERATIONS).unwrap(), @@ -37,9 +37,9 @@ impl Cryptor { &mut key_bytes, ); - let unbound_key = ring::aead::UnboundKey::new(&ring::aead::AES_256_GCM, &key_bytes) + let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key_bytes) .map_err(|_| anyhow::anyhow!("error while creating AEAD key"))?; - Ok(ring::aead::LessSafeKey::new(unbound_key)) + Ok(aead::LessSafeKey::new(unbound_key)) } /// Encrypt the given payload. @@ -53,9 +53,9 @@ impl Cryptor { self.rng .fill(&mut nonce_buf) .map_err(|_| anyhow::anyhow!("error generating random nonce"))?; - let nonce = ring::aead::Nonce::assume_unique_for_key(nonce_buf); + let nonce = aead::Nonce::assume_unique_for_key(nonce_buf); - let aad = ring::aead::Aad::from(version_id.as_bytes()); + let aad = aead::Aad::from(version_id.as_bytes()); let tag = self .key @@ -85,8 +85,8 @@ impl Cryptor { let mut nonce = [0u8; aead::NONCE_LEN]; nonce.copy_from_slice(env.nonce); - let nonce = ring::aead::Nonce::assume_unique_for_key(nonce); - let aad = ring::aead::Aad::from(version_id.as_bytes()); + let nonce = aead::Nonce::assume_unique_for_key(nonce); + let aad = aead::Aad::from(version_id.as_bytes()); let mut payload = env.payload.to_vec(); let plaintext = self From 17f5521ea4e0d77869d4e4be99b0cf307f7c45f7 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 17 Oct 2021 18:06:16 -0400 Subject: [PATCH 380/548] add an app_id to the encryption AAD --- docs/src/sync-protocol.md | 18 +++++++--- taskchampion/src/server/crypto.rs | 59 +++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md index 4d86f0af6..0bb588383 100644 --- a/docs/src/sync-protocol.md +++ b/docs/src/sync-protocol.md @@ -53,21 +53,29 @@ The salt is the SHA256 hash of the 16-byte form of the client key. #### Encryption The client uses [AEAD](https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html), with algorithm CHACHA20_POLY1305. -Each encrypted payload has an associated version ID. -The 16-byte form of this UUID is used as the associated data (AAD) with the AEAD algorithm. The client should generate a random nonce, noting that AEAD is _not secure_ if a nonce is used repeatedly for the same key. +AEAD supports additional authenticated data (AAD) which must be provided for both open and seal operations. +In this protocol, the AAD is always 17 bytes of the form: + * `app_id` (byte) - always 1 + * `version_id` (16 bytes) - 16-byte form of the version ID associated with this data + * for versions (AddVersion, GetChildVersion), the _parent_ version_id + * for snapshots (AddSnapshot, GetSnapshot), the snapshot version_id + +The `app_id` field is for future expansion to handle other, non-task data using this protocol. +Including it in the AAD ensures that such data cannot be confused with task data. + Although the AEAD specification distinguishes ciphertext and tags, for purposes of this specification they are considered concatenated into a single bytestring as in BoringSSL's `EVP_AEAD_CTX_seal`. #### Representation -The final byte-stream is comprised of the following structure, with integers represented in network-endian format. +The final byte-stream is comprised of the following structure: -* `version` (32-bit int) - format version (always 1) +* `version` (byte) - format version (always 1) * `nonce` (12 bytes) - encryption nonce * `ciphertext` (remaining bytes) - ciphertext from sealing operation -Future versions may have a completely different format. +The `version` field identifies this data format, and future formats will have a value other than 1 in this position. ### Version diff --git a/taskchampion/src/server/crypto.rs b/taskchampion/src/server/crypto.rs index fe47f65d5..b2728acad 100644 --- a/taskchampion/src/server/crypto.rs +++ b/taskchampion/src/server/crypto.rs @@ -1,12 +1,13 @@ /// This module implements the encryption specified in the sync-protocol /// document. -use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use ring::{aead, digest, pbkdf2, rand, rand::SecureRandom}; -use std::io::{Cursor, Read}; +use std::io::Read; use uuid::Uuid; const PBKDF2_ITERATIONS: u32 = 100000; -const ENVELOPE_VERSION: u32 = 1; +const ENVELOPE_VERSION: u8 = 1; +const AAD_LEN: usize = 17; +const TASK_APP_ID: u8 = 1; /// An Cryptor stores a secret and allows sealing and unsealing. It derives a key from the secret, /// which takes a nontrivial amount of time, so it should be created once and re-used for the given @@ -55,7 +56,7 @@ impl Cryptor { .map_err(|_| anyhow::anyhow!("error generating random nonce"))?; let nonce = aead::Nonce::assume_unique_for_key(nonce_buf); - let aad = aead::Aad::from(version_id.as_bytes()); + let aad = self.make_aad(version_id); let tag = self .key @@ -86,7 +87,7 @@ impl Cryptor { let mut nonce = [0u8; aead::NONCE_LEN]; nonce.copy_from_slice(env.nonce); let nonce = aead::Nonce::assume_unique_for_key(nonce); - let aad = aead::Aad::from(version_id.as_bytes()); + let aad = self.make_aad(version_id); let mut payload = env.payload.to_vec(); let plaintext = self @@ -99,6 +100,13 @@ impl Cryptor { payload: plaintext.to_vec(), }) } + + fn make_aad(&self, version_id: Uuid) -> aead::Aad<[u8; AAD_LEN]> { + let mut aad = [0u8; AAD_LEN]; + aad[0] = TASK_APP_ID; + aad[1..].copy_from_slice(version_id.as_bytes()); + aead::Aad::from(aad) + } } /// Secret represents a secret key as used for encryption and decryption. @@ -126,26 +134,25 @@ struct Envelope<'a> { impl<'a> Envelope<'a> { fn from_bytes(buf: &'a [u8]) -> anyhow::Result> { - if buf.len() <= 4 + aead::NONCE_LEN { + if buf.len() <= 1 + aead::NONCE_LEN { anyhow::bail!("envelope is too small"); } - let mut rdr = Cursor::new(buf); - let version = rdr.read_u32::().unwrap(); - if version != 1 { - anyhow::bail!("unrecognized envelope version {}", version); + let version = buf[0]; + if version != ENVELOPE_VERSION { + anyhow::bail!("unrecognized encryption envelope version {}", version); } Ok(Envelope { - nonce: &buf[4..4 + aead::NONCE_LEN], - payload: &buf[4 + aead::NONCE_LEN..], + nonce: &buf[1..1 + aead::NONCE_LEN], + payload: &buf[1 + aead::NONCE_LEN..], }) } fn to_bytes(&self) -> Vec { - let mut buf = Vec::with_capacity(4 + self.nonce.len() + self.payload.len()); + let mut buf = Vec::with_capacity(1 + self.nonce.len() + self.payload.len()); - buf.write_u32::(ENVELOPE_VERSION).unwrap(); + buf.push(ENVELOPE_VERSION); buf.extend_from_slice(self.nonce); buf.extend_from_slice(self.payload); buf @@ -210,6 +217,30 @@ mod test { assert_eq!(env, env2); } + #[test] + fn envelope_bad_version() { + let env = Envelope { + nonce: &[2; 12], + payload: b"HELLO", + }; + + let mut bytes = env.to_bytes(); + bytes[0] = 99; + assert!(Envelope::from_bytes(&bytes).is_err()); + } + + #[test] + fn envelope_too_short() { + let env = Envelope { + nonce: &[2; 12], + payload: b"HELLO", + }; + + let bytes = env.to_bytes(); + let bytes = &bytes[..10]; + assert!(Envelope::from_bytes(bytes).is_err()); + } + #[test] fn round_trip() { let version_id = Uuid::new_v4(); From 0af66fd6c8029390c53f0d4fa34b3e4fe168c16d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 19 Oct 2021 18:31:30 -0400 Subject: [PATCH 381/548] Validate encryption using externally-generated data --- taskchampion/src/server/crypto.rs | 92 +++++++++++++++++++ taskchampion/src/server/generate-test-data.py | 77 ++++++++++++++++ taskchampion/src/server/test-bad-app-id.data | 2 + .../src/server/test-bad-client-key.data | 1 + taskchampion/src/server/test-bad-secret.data | 1 + .../src/server/test-bad-version-id.data | 1 + taskchampion/src/server/test-bad-version.data | 1 + .../src/server/test-bad-version_id.data | 2 + taskchampion/src/server/test-good.data | 1 + 9 files changed, 178 insertions(+) create mode 100644 taskchampion/src/server/generate-test-data.py create mode 100644 taskchampion/src/server/test-bad-app-id.data create mode 100644 taskchampion/src/server/test-bad-client-key.data create mode 100644 taskchampion/src/server/test-bad-secret.data create mode 100644 taskchampion/src/server/test-bad-version-id.data create mode 100644 taskchampion/src/server/test-bad-version.data create mode 100644 taskchampion/src/server/test-bad-version_id.data create mode 100644 taskchampion/src/server/test-good.data diff --git a/taskchampion/src/server/crypto.rs b/taskchampion/src/server/crypto.rs index b2728acad..978a38fd8 100644 --- a/taskchampion/src/server/crypto.rs +++ b/taskchampion/src/server/crypto.rs @@ -317,4 +317,96 @@ mod test { let cryptor = Cryptor::new(client_key, &secret).unwrap(); assert!(cryptor.unseal(sealed).is_err()); } + + mod externally_valid { + // validate data generated by generate-test-data.py. The intent is to + // validate that this format matches the specification by implementing + // the specification in a second language + use super::*; + use pretty_assertions::assert_eq; + + /// The values in generate-test-data.py + fn defaults() -> (Uuid, Uuid, Vec) { + ( + Uuid::parse_str("b0517957-f912-4d49-8330-f612e73030c4").unwrap(), + Uuid::parse_str("0666d464-418a-4a08-ad53-6f15c78270cd").unwrap(), + b"b4a4e6b7b811eda1dc1a2693ded".to_vec(), + ) + } + + #[test] + fn good() { + let (version_id, client_key, encryption_secret) = defaults(); + let sealed = Sealed { + version_id, + payload: include_bytes!("test-good.data").to_vec(), + }; + + let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap(); + let unsealed = cryptor.unseal(sealed).unwrap(); + + assert_eq!(unsealed.payload, b"SUCCESS"); + assert_eq!(unsealed.version_id, version_id); + } + + #[test] + fn bad_version_id() { + let (version_id, client_key, encryption_secret) = defaults(); + let sealed = Sealed { + version_id, + payload: include_bytes!("test-bad-version-id.data").to_vec(), + }; + + let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap(); + assert!(cryptor.unseal(sealed).is_err()); + } + + #[test] + fn bad_client_key() { + let (version_id, client_key, encryption_secret) = defaults(); + let sealed = Sealed { + version_id, + payload: include_bytes!("test-bad-client-key.data").to_vec(), + }; + + let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap(); + assert!(cryptor.unseal(sealed).is_err()); + } + + #[test] + fn bad_secret() { + let (version_id, client_key, encryption_secret) = defaults(); + let sealed = Sealed { + version_id, + payload: include_bytes!("test-bad-secret.data").to_vec(), + }; + + let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap(); + assert!(cryptor.unseal(sealed).is_err()); + } + + #[test] + fn bad_version() { + let (version_id, client_key, encryption_secret) = defaults(); + let sealed = Sealed { + version_id, + payload: include_bytes!("test-bad-version.data").to_vec(), + }; + + let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap(); + assert!(cryptor.unseal(sealed).is_err()); + } + + #[test] + fn bad_app_id() { + let (version_id, client_key, encryption_secret) = defaults(); + let sealed = Sealed { + version_id, + payload: include_bytes!("test-bad-app-id.data").to_vec(), + }; + + let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap(); + assert!(cryptor.unseal(sealed).is_err()); + } + } } diff --git a/taskchampion/src/server/generate-test-data.py b/taskchampion/src/server/generate-test-data.py new file mode 100644 index 000000000..366597813 --- /dev/null +++ b/taskchampion/src/server/generate-test-data.py @@ -0,0 +1,77 @@ +# This file generates test-encrypted.data. To run it: +# - pip install cryptography pbkdf2 +# - python taskchampion/src/server/generate-test-data.py taskchampion/src/server/ + +import os +import hashlib +import pbkdf2 +import secrets +import sys +import uuid + +from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 + +# these values match values used in the rust tests +client_key = "0666d464-418a-4a08-ad53-6f15c78270cd" +encryption_secret = b"b4a4e6b7b811eda1dc1a2693ded" +version_id = "b0517957-f912-4d49-8330-f612e73030c4" + +def gen( + version_id=version_id, client_key=client_key, encryption_secret=encryption_secret, + app_id=1, version=1): + # first, generate the encryption key + salt = hashlib.sha256(uuid.UUID(client_key).bytes).digest() + key = pbkdf2.PBKDF2( + encryption_secret, + salt, + digestmodule=hashlib.sha256, + iterations=100000, + ).read(32) + + # create a nonce + nonce = secrets.token_bytes(12) + + assert len(b"\x01") == 1 + # create the AAD + aad = b''.join([ + bytes([app_id]), + uuid.UUID(version_id).bytes, + ]) + + # encrypt using AEAD + chacha = ChaCha20Poly1305(key) + ciphertext = chacha.encrypt(nonce, b"SUCCESS", aad) + + # create the envelope + envelope = b''.join([ + bytes([version]), + nonce, + ciphertext, + ]) + + return envelope + + +def main(): + dir = sys.argv[1] + + with open(os.path.join(dir, 'test-good.data'), "wb") as f: + f.write(gen()) + + with open(os.path.join(dir, 'test-bad-version-id.data'), "wb") as f: + f.write(gen(version_id=uuid.uuid4().hex)) + + with open(os.path.join(dir, 'test-bad-client-key.data'), "wb") as f: + f.write(gen(client_key=uuid.uuid4().hex)) + + with open(os.path.join(dir, 'test-bad-secret.data'), "wb") as f: + f.write(gen(encryption_secret=b"xxxxxxxxxxxxxxxxxxxxx")) + + with open(os.path.join(dir, 'test-bad-version.data'), "wb") as f: + f.write(gen(version=99)) + + with open(os.path.join(dir, 'test-bad-app-id.data'), "wb") as f: + f.write(gen(app_id=99)) + + +main() diff --git a/taskchampion/src/server/test-bad-app-id.data b/taskchampion/src/server/test-bad-app-id.data new file mode 100644 index 000000000..a1c6832a6 --- /dev/null +++ b/taskchampion/src/server/test-bad-app-id.data @@ -0,0 +1,2 @@ +#§$á­ +†—Õ^~B>n)j›i†¯1—î9™|µœÓ~ \ No newline at end of file diff --git a/taskchampion/src/server/test-bad-client-key.data b/taskchampion/src/server/test-bad-client-key.data new file mode 100644 index 000000000..bfdd9635e --- /dev/null +++ b/taskchampion/src/server/test-bad-client-key.data @@ -0,0 +1 @@ +ÍA4ö¯Ãè t;Äô õçp¦Ï¦x^Áýreü…œJÔ¤ \ No newline at end of file diff --git a/taskchampion/src/server/test-bad-secret.data b/taskchampion/src/server/test-bad-secret.data new file mode 100644 index 000000000..696da066f --- /dev/null +++ b/taskchampion/src/server/test-bad-secret.data @@ -0,0 +1 @@ +/}åd E°‡dIcÁXéè-‡!V°Û%è4îáòd]³ÃÇ} \ No newline at end of file diff --git a/taskchampion/src/server/test-bad-version-id.data b/taskchampion/src/server/test-bad-version-id.data new file mode 100644 index 000000000..2ccd8c638 --- /dev/null +++ b/taskchampion/src/server/test-bad-version-id.data @@ -0,0 +1 @@ +làæäa|‚ï@ÏS_‚¬…ãzÝV9£q¦Ñ…‘)+¦… \ No newline at end of file diff --git a/taskchampion/src/server/test-bad-version.data b/taskchampion/src/server/test-bad-version.data new file mode 100644 index 000000000..20fed792e --- /dev/null +++ b/taskchampion/src/server/test-bad-version.data @@ -0,0 +1 @@ +cª¶TH¨çp>¥«æº¦m4ï¹Ë~×1µ0PIö´W¢ \ No newline at end of file diff --git a/taskchampion/src/server/test-bad-version_id.data b/taskchampion/src/server/test-bad-version_id.data new file mode 100644 index 000000000..4a228ec4a --- /dev/null +++ b/taskchampion/src/server/test-bad-version_id.data @@ -0,0 +1,2 @@ +B• +áÔ-×3%¦j£,*ߺ7ê©–QØKúO¦œFPZÝ \ No newline at end of file diff --git a/taskchampion/src/server/test-good.data b/taskchampion/src/server/test-good.data new file mode 100644 index 000000000..9efec7577 --- /dev/null +++ b/taskchampion/src/server/test-good.data @@ -0,0 +1 @@ +pÑ¿µÒŸ½V²ûÝäToë"}cT·äY7Æ ˆÀ@ÙdLTý`Ò \ No newline at end of file From c72cae648df5dffcabf07e6916dcec14ffdc2cea Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 19 Oct 2021 22:01:37 -0400 Subject: [PATCH 382/548] Apply snapshots automatically on empty taskdbs --- taskchampion/src/server/local.rs | 4 +++ taskchampion/src/server/remote/mod.rs | 21 ++++++++++++++ taskchampion/src/server/test.rs | 15 ++++++++-- taskchampion/src/server/types.rs | 2 ++ taskchampion/src/storage/mod.rs | 10 +++++++ taskchampion/src/taskdb/snapshot.rs | 12 ++------ taskchampion/src/taskdb/sync.rs | 40 +++++++++++++++++++++++---- 7 files changed, 86 insertions(+), 18 deletions(-) diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index b8c0198a2..37cb06614 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -160,6 +160,10 @@ impl Server for LocalServer { // the local server never requests a snapshot, so it should never get one unreachable!() } + + fn get_snapshot(&mut self) -> anyhow::Result> { + Ok(None) + } } #[cfg(test)] diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index 139f5dc9f..c7d3362e5 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -152,4 +152,25 @@ impl Server for RemoteServer { .send_bytes(ciphertext.as_ref()) .map(|_| ())?) } + + fn get_snapshot(&mut self) -> anyhow::Result> { + let url = format!("{}/v1/client/snapshot", self.origin); + match self + .agent + .get(&url) + .set("X-Client-Key", &self.client_key.to_string()) + .call() + { + Ok(resp) => { + let version_id = get_uuid_header(&resp, "X-Version-Id")?; + let ciphertext = Ciphertext::from_resp(resp, SNAPSHOT_CONTENT_TYPE)?; + let snapshot = ciphertext + .open(&self.encryption_secret, version_id)? + .payload; + Ok(Some((version_id, snapshot))) + } + Err(ureq::Error::Status(status, _)) if status == 404 => Ok(None), + Err(err) => Err(err.into()), + } + } } diff --git a/taskchampion/src/server/test.rs b/taskchampion/src/server/test.rs index 3901e2fb6..1fff611cc 100644 --- a/taskchampion/src/server/test.rs +++ b/taskchampion/src/server/test.rs @@ -12,9 +12,8 @@ struct Version { history_segment: HistorySegment, } -#[derive(Clone)] - /// TestServer implements the Server trait with a test implementation. +#[derive(Clone)] pub(crate) struct TestServer(Arc>); pub(crate) struct Inner { @@ -35,6 +34,7 @@ impl TestServer { snapshot: None, }))) } + // feel free to add any test utility functions here /// Get a boxed Server implementation referring to this TestServer pub(crate) fn server(&self) -> Box { @@ -51,6 +51,12 @@ impl TestServer { let inner = self.0.lock().unwrap(); inner.snapshot.as_ref().cloned() } + + /// Delete a version from storage + pub(crate) fn delete_version(&mut self, parent_version_id: VersionId) { + let mut inner = self.0.lock().unwrap(); + inner.versions.remove(&parent_version_id); + } } impl Server for TestServer { @@ -119,4 +125,9 @@ impl Server for TestServer { inner.snapshot = Some((version_id, snapshot)); Ok(()) } + + fn get_snapshot(&mut self) -> anyhow::Result> { + let inner = self.0.lock().unwrap(); + Ok(inner.snapshot.clone()) + } } diff --git a/taskchampion/src/server/types.rs b/taskchampion/src/server/types.rs index 3a1178c41..fada6c04a 100644 --- a/taskchampion/src/server/types.rs +++ b/taskchampion/src/server/types.rs @@ -65,4 +65,6 @@ pub trait Server { /// Add a snapshot on the server fn add_snapshot(&mut self, version_id: VersionId, snapshot: Snapshot) -> anyhow::Result<()>; + + fn get_snapshot(&mut self) -> anyhow::Result>; } diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index 0b3e3da32..a16bb5a7e 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -105,6 +105,16 @@ pub trait StorageTxn { /// Note that this is the only way items are removed from the set. fn clear_working_set(&mut self) -> Result<()>; + /// Check whether this storage is entirely empty + fn is_empty(&mut self) -> Result { + let mut empty = true; + empty = empty && self.all_tasks()?.is_empty(); + empty = empty && self.get_working_set()? == vec![None]; + empty = empty && self.base_version()? == Uuid::nil(); + empty = empty && self.operations()?.is_empty(); + Ok(empty) + } + /// Commit any changes made in the transaction. It is an error to call this more than /// once. fn commit(&mut self) -> Result<()>; diff --git a/taskchampion/src/taskdb/snapshot.rs b/taskchampion/src/taskdb/snapshot.rs index e054612b3..33ab7e8df 100644 --- a/taskchampion/src/taskdb/snapshot.rs +++ b/taskchampion/src/taskdb/snapshot.rs @@ -70,7 +70,6 @@ impl SnapshotTasks { } } -#[allow(dead_code)] /// Generate a snapshot (compressed, unencrypted) for the current state of the taskdb in the given /// storage. pub(super) fn make_snapshot(txn: &mut dyn StorageTxn) -> anyhow::Result> { @@ -78,7 +77,6 @@ pub(super) fn make_snapshot(txn: &mut dyn StorageTxn) -> anyhow::Result> all_tasks.encode() } -#[allow(dead_code)] /// Apply the given snapshot (compressed, unencrypted) to the taskdb's storage. pub(super) fn apply_snapshot( txn: &mut dyn StorageTxn, @@ -87,14 +85,8 @@ pub(super) fn apply_snapshot( ) -> anyhow::Result<()> { let all_tasks = SnapshotTasks::decode(snapshot)?; - // first, verify that the taskdb truly is empty - let mut empty = true; - empty = empty && txn.all_tasks()?.is_empty(); - empty = empty && txn.get_working_set()? == vec![None]; - empty = empty && txn.base_version()? == Uuid::nil(); - empty = empty && txn.operations()?.is_empty(); - - if !empty { + // double-check emptiness + if !txn.is_empty()? { anyhow::bail!("Cannot apply snapshot to a non-empty task database"); } diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index 2cd8c2717..e77a5db66 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -16,6 +16,15 @@ pub(super) fn sync( txn: &mut dyn StorageTxn, avoid_snapshots: bool, ) -> anyhow::Result<()> { + // if this taskdb is entirely empty, then start by getting and applying a snapshot + if txn.is_empty()? { + trace!("storage is empty; attempting to apply a snapshot"); + if let Some((version, snap)) = server.get_snapshot()? { + snapshot::apply_snapshot(txn, version, snap.as_ref())?; + trace!("applied snapshot for version {}", version); + } + } + // retry synchronizing until the server accepts our version (this allows for races between // replicas trying to sync to the same server). If the server insists on the same base // version twice, then we have diverged. @@ -293,24 +302,23 @@ mod test { } #[test] - fn test_sync_adds_snapshot() -> anyhow::Result<()> { - let test_server = TestServer::new(); + fn test_sync_add_snapshot_start_with_snapshot() -> anyhow::Result<()> { + let mut test_server = TestServer::new(); let mut server: Box = test_server.server(); let mut db1 = newdb(); let uuid = Uuid::new_v4(); - db1.apply(Operation::Create { uuid }).unwrap(); + db1.apply(Operation::Create { uuid })?; db1.apply(Operation::Update { uuid, property: "title".into(), value: Some("my first task".into()), timestamp: Utc::now(), - }) - .unwrap(); + })?; test_server.set_snapshot_urgency(SnapshotUrgency::High); - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); + sync(&mut server, db1.storage.txn()?.as_mut(), false)?; // assert that a snapshot was added let base_version = db1.storage.txn()?.base_version()?; @@ -322,6 +330,26 @@ mod test { let tasks = SnapshotTasks::decode(&s)?.into_inner(); assert_eq!(tasks[0].0, uuid); + // update the taskdb and sync again + db1.apply(Operation::Update { + uuid, + property: "title".into(), + value: Some("my first task, updated".into()), + timestamp: Utc::now(), + })?; + sync(&mut server, db1.storage.txn()?.as_mut(), false)?; + + // delete the first version, so that db2 *must* initialize from + // the snapshot + test_server.delete_version(Uuid::nil()); + + // sync to a new DB and check that we got the expected results + let mut db2 = newdb(); + sync(&mut server, db2.storage.txn()?.as_mut(), false)?; + + let task = db2.get_task(uuid)?.unwrap(); + assert_eq!(task.get("title").unwrap(), "my first task, updated"); + Ok(()) } From ec35d4fa20710a97e228c8397df48899ab6b0813 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 20 Oct 2021 21:15:05 -0400 Subject: [PATCH 383/548] use a distinct error for out-of-sync replica --- cli/src/invocation/cmd/sync.rs | 31 +++++++++++++++++++++++++++---- taskchampion/src/errors.rs | 6 ++++++ taskchampion/src/replica.rs | 2 +- taskchampion/src/taskdb/sync.rs | 3 ++- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index a042a5bf4..7a3708421 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -1,5 +1,5 @@ use crate::settings::Settings; -use taskchampion::{server::Server, Replica}; +use taskchampion::{server::Server, Error as TCError, Replica}; use termcolor::WriteColor; pub(crate) fn execute( @@ -8,9 +8,32 @@ pub(crate) fn execute( settings: &Settings, server: &mut Box, ) -> Result<(), crate::Error> { - replica.sync(server, settings.avoid_snapshots)?; - writeln!(w, "sync complete.")?; - Ok(()) + match replica.sync(server, settings.avoid_snapshots) { + Ok(()) => { + writeln!(w, "sync complete.")?; + Ok(()) + } + Err(e) => match e.downcast() { + Ok(TCError::OutOfSync) => { + writeln!(w, "This replica cannot be synchronized with the server.")?; + writeln!( + w, + "It may be too old, or some other failure may have occurred." + )?; + writeln!( + w, + "To start fresh, remove the local task database and run `ta sync` again." + )?; + writeln!( + w, + "Note that doing so will lose any un-synchronized local changes." + )?; + Ok(()) + } + Ok(e) => Err(e.into()), + Err(e) => Err(e.into()), + }, + } } #[cfg(test)] diff --git a/taskchampion/src/errors.rs b/taskchampion/src/errors.rs index 44bad9881..3209b6ea9 100644 --- a/taskchampion/src/errors.rs +++ b/taskchampion/src/errors.rs @@ -4,6 +4,12 @@ use thiserror::Error; #[non_exhaustive] /// Errors returned from taskchampion operations pub enum Error { + /// A task-database-related error #[error("Task Database Error: {0}")] Database(String), + /// An error specifically indicating that the local replica cannot + /// be synchronized with the sever, due to being out of date or some + /// other irrecoverable error. + #[error("Local replica is out of sync with the server")] + OutOfSync, } diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index cc6338ceb..7afbc90a3 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -140,7 +140,7 @@ impl Replica { ) -> anyhow::Result<()> { self.taskdb .sync(server, avoid_snapshots) - .context("Failed to synchronize")?; + .context("Failed to synchronize with server")?; self.rebuild_working_set(false) .context("Failed to rebuild working set after sync")?; Ok(()) diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index e77a5db66..7ce1d76ce 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -1,6 +1,7 @@ use super::{ops, snapshot}; use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency}; use crate::storage::{Operation, StorageTxn}; +use crate::Error; use log::{info, trace, warn}; use serde::{Deserialize, Serialize}; use std::str; @@ -97,7 +98,7 @@ pub(super) fn sync( ); if let Some(requested) = requested_parent_version_id { if parent_version_id == requested { - anyhow::bail!("Server's task history has diverged from this replica"); + return Err(Error::OutOfSync.into()); } } requested_parent_version_id = Some(parent_version_id); From c63a21797a49b9450c47ef5da40bf2e1ea6b07ab Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 20 Oct 2021 21:25:28 -0400 Subject: [PATCH 384/548] remove dbg!(..) --- cli/src/invocation/cmd/config.rs | 1 - taskchampion/src/task/task.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/cli/src/invocation/cmd/config.rs b/cli/src/invocation/cmd/config.rs index a3cc9b223..ce4d0f27a 100644 --- a/cli/src/invocation/cmd/config.rs +++ b/cli/src/invocation/cmd/config.rs @@ -61,7 +61,6 @@ mod test { assert!(w.into_string().starts_with("Set configuration value ")); let updated_toml = fs::read_to_string(cfg_file.clone()).unwrap(); - dbg!(&updated_toml); assert_eq!( updated_toml, "# store data everywhere\ndata_dir = \"/somewhere\"\n" diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 61f1fcd94..d96539e10 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -380,7 +380,6 @@ mod test { .drain(..) .collect(), ); - dbg!(&task); assert!(!task.is_waiting()); assert_eq!(task.get_wait(), Some(ts)); From 642da4da3ee111ad69b976495deee825d4686f6f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 20 Oct 2021 22:29:16 -0400 Subject: [PATCH 385/548] do not run audit on PRs --- .github/workflows/audit.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 18e26f337..7454b8b58 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -7,7 +7,6 @@ on: paths: - '**/Cargo.toml' - '**/Cargo.lock' - pull_request: jobs: audit: From 6aa355b8359e8d13d4e4bee2eb6acc6d40ba7e68 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 21 Oct 2021 13:33:19 +1100 Subject: [PATCH 386/548] Add config for cargo-audit --- .cargo/audit.toml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .cargo/audit.toml diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 000000000..5ffb88d46 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,4 @@ +[advisories] +ignore = [ + "RUSTSEC-2020-0159", # segfault in localtime_r - low risk to TC +] From ae244055f79d0b352ff22ef848f01772738039a7 Mon Sep 17 00:00:00 2001 From: dbr Date: Thu, 21 Oct 2021 13:45:32 +1100 Subject: [PATCH 387/548] Also ignore time bug --- .cargo/audit.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/.cargo/audit.toml b/.cargo/audit.toml index 5ffb88d46..948236ca9 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -1,4 +1,5 @@ [advisories] ignore = [ "RUSTSEC-2020-0159", # segfault in localtime_r - low risk to TC + "RUSTSEC-2020-0071", # same localtime_r bug as above ] From 8df3e4f2f82e1e993a32ebb5ce524bb6690fd8a0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 24 Oct 2021 20:57:55 -0400 Subject: [PATCH 388/548] use TW's semantics for `start` --- docs/src/tasks.md | 2 +- taskchampion/src/task/task.rs | 125 +++++++--------------------------- 2 files changed, 27 insertions(+), 100 deletions(-) diff --git a/docs/src/tasks.md b/docs/src/tasks.md index ae354c62c..cfd6e1ef3 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -30,7 +30,7 @@ The following keys, and key formats, are defined: * `status` - one of `P` for a pending task (the default), `C` for completed or `D` for deleted * `description` - the one-line summary of the task * `modified` - the time of the last modification of this task -* `start.` - either an empty string (representing work on the task to the task that has not been stopped) or a timestamp (representing the time that work stopped) +* `start` - the most recent time at which this task was started (a task with no `start` key is not active) * `tag.` - indicates this task has tag `` (value is an empty string) * `wait` - indicates the time before which this task should be hidden, as it is not actionable diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index d96539e10..102d27aa8 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -100,9 +100,7 @@ impl Task { /// Determine whether this task is active -- that is, that it has been started /// and not stopped. pub fn is_active(&self) -> bool { - self.taskmap - .iter() - .any(|(k, v)| k.starts_with("start.") && v.is_empty()) + self.taskmap.contains_key("start") } /// Determine whether a given synthetic tag is present on this task. All other @@ -192,31 +190,18 @@ impl<'r> TaskMut<'r> { self.set_timestamp("modified", Some(modified)) } - /// Start the task by creating "start.", if the task is not already /// active. pub fn start(&mut self) -> anyhow::Result<()> { if self.is_active() { return Ok(()); } - let k = format!("start.{}", Utc::now().timestamp()); - self.set_string(k, Some(String::from(""))) + self.set_timestamp("start", Some(Utc::now())) } - /// Stop the task by adding the current timestamp to all un-resolved "start." keys. + /// Stop the task by removing the `start` key pub fn stop(&mut self) -> anyhow::Result<()> { - let keys = self - .taskmap - .iter() - .filter(|(k, v)| k.starts_with("start.") && v.is_empty()) - .map(|(k, _)| k) - .cloned() - .collect::>(); - let now = Utc::now(); - for key in keys { - println!("{}", key); - self.set_timestamp(&key, Some(now))?; - } - Ok(()) + self.set_timestamp("start", None) } /// Mark this task as complete @@ -340,10 +325,10 @@ mod test { } #[test] - fn test_is_active() { + fn test_is_active_active() { let task = Task::new( Uuid::new_v4(), - vec![(String::from("start.1234"), String::from(""))] + vec![(String::from("start"), String::from("1234"))] .drain(..) .collect(), ); @@ -352,14 +337,8 @@ mod test { } #[test] - fn test_is_active_stopped() { - let task = Task::new( - Uuid::new_v4(), - vec![(String::from("start.1234"), String::from("1235"))] - .drain(..) - .collect(), - ); - + fn test_is_active_inactive() { + let task = Task::new(Uuid::new_v4(), Default::default()); assert!(!task.is_active()); } @@ -405,7 +384,7 @@ mod test { Uuid::new_v4(), vec![ (String::from("tag.abc"), String::from("")), - (String::from("start.1234"), String::from("")), + (String::from("start"), String::from("1234")), ] .drain(..) .collect(), @@ -463,35 +442,21 @@ mod test { assert_eq!(tags, vec![utag("ok"), stag(SyntheticTag::Pending)]); } - fn count_taskmap(task: &TaskMut, f: fn(&(&String, &String)) -> bool) -> usize { - task.taskmap.iter().filter(f).count() - } - #[test] fn test_start() { with_mut_task(|mut task| { task.start().unwrap(); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), - 1 - ); + assert!(task.taskmap.contains_key("start")); + task.reload().unwrap(); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), - 1 - ); + assert!(task.taskmap.contains_key("start")); // second start doesn't change anything.. task.start().unwrap(); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), - 1 - ); + assert!(task.taskmap.contains_key("start")); + task.reload().unwrap(); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), - 1 - ); + assert!(task.taskmap.contains_key("start")); }); } @@ -500,23 +465,17 @@ mod test { with_mut_task(|mut task| { task.start().unwrap(); task.stop().unwrap(); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), - 0 - ); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && !v.is_empty()), - 1 - ); + assert!(!task.taskmap.contains_key("start")); + task.reload().unwrap(); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), - 0 - ); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && !v.is_empty()), - 1 - ); + assert!(!task.taskmap.contains_key("start")); + + // redundant call does nothing.. + task.stop().unwrap(); + assert!(!task.taskmap.contains_key("start")); + + task.reload().unwrap(); + assert!(!task.taskmap.contains_key("start")); }); } @@ -534,38 +493,6 @@ mod test { }); } - #[test] - fn test_stop_multiple() { - with_mut_task(|mut task| { - // simulate a task that has (through the synchronization process) been started twice - task.task - .taskmap - .insert(String::from("start.1234"), String::from("")); - task.task - .taskmap - .insert(String::from("start.5678"), String::from("")); - - task.stop().unwrap(); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), - 0 - ); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && !v.is_empty()), - 2 - ); - task.reload().unwrap(); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && v.is_empty()), - 0 - ); - assert_eq!( - count_taskmap(&task, |(k, v)| k.starts_with("start.") && !v.is_empty()), - 2 - ); - }); - } - #[test] fn test_add_tags() { with_mut_task(|mut task| { From 2e65d172cd83a9d1d2b359a887455869280c2991 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 24 Oct 2021 21:23:57 -0400 Subject: [PATCH 389/548] Add an integration test for snapshot syncing --- Cargo.lock | 2 + replica-server-tests/Cargo.toml | 2 + replica-server-tests/tests/cross-sync.rs | 5 ++ replica-server-tests/tests/snapshots.rs | 94 ++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 replica-server-tests/tests/snapshots.rs diff --git a/Cargo.lock b/Cargo.lock index 10f51edf9..d88bc7e6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2473,6 +2473,8 @@ dependencies = [ "actix-rt", "actix-web", "anyhow", + "env_logger 0.8.4", + "log", "pretty_assertions", "taskchampion", "taskchampion-sync-server", diff --git a/replica-server-tests/Cargo.toml b/replica-server-tests/Cargo.toml index 1dfb537ce..efe369b3c 100644 --- a/replica-server-tests/Cargo.toml +++ b/replica-server-tests/Cargo.toml @@ -17,3 +17,5 @@ actix-web = "^3.3.2" actix-rt = "^1.1.1" tempfile = "3" pretty_assertions = "1" +log = "^0.4.14" +env_logger = "^0.8.3" diff --git a/replica-server-tests/tests/cross-sync.rs b/replica-server-tests/tests/cross-sync.rs index 881a7481b..10b3317d2 100644 --- a/replica-server-tests/tests/cross-sync.rs +++ b/replica-server-tests/tests/cross-sync.rs @@ -5,6 +5,11 @@ use taskchampion_sync_server::{storage::InMemoryStorage, Server}; #[actix_rt::test] async fn cross_sync() -> anyhow::Result<()> { + let _ = env_logger::builder() + .is_test(true) + .filter_level(log::LevelFilter::Trace) + .try_init(); + let server = Server::new(Default::default(), Box::new(InMemoryStorage::new())); let httpserver = HttpServer::new(move || App::new().configure(|sc| server.config(sc))).bind("0.0.0.0:0")?; diff --git a/replica-server-tests/tests/snapshots.rs b/replica-server-tests/tests/snapshots.rs new file mode 100644 index 000000000..dbe889231 --- /dev/null +++ b/replica-server-tests/tests/snapshots.rs @@ -0,0 +1,94 @@ +use actix_web::{App, HttpServer}; +use pretty_assertions::assert_eq; +use taskchampion::{Replica, ServerConfig, Status, StorageConfig, Uuid}; +use taskchampion_sync_server::{ + storage::InMemoryStorage, Server, ServerConfig as SyncServerConfig, +}; + +const NUM_VERSIONS: u32 = 50; + +#[actix_rt::test] +async fn sync_with_snapshots() -> anyhow::Result<()> { + let _ = env_logger::builder() + .is_test(true) + .filter_level(log::LevelFilter::Trace) + .try_init(); + + let sync_server_config = SyncServerConfig { + snapshot_days: 100, + snapshot_versions: 3, + }; + let server = Server::new(sync_server_config, Box::new(InMemoryStorage::new())); + let httpserver = + HttpServer::new(move || App::new().configure(|sc| server.config(sc))).bind("0.0.0.0:0")?; + + // bind was to :0, so the kernel will have selected an unused port + let port = httpserver.addrs()[0].port(); + + httpserver.run(); + + let client_key = Uuid::new_v4(); + let encryption_secret = b"abc123".to_vec(); + let make_server = || { + ServerConfig::Remote { + origin: format!("http://127.0.0.1:{}", port), + client_key, + encryption_secret: encryption_secret.clone(), + } + .into_server() + }; + + // first we set up a single replica and sync it a lot of times, to establish a sync history. + let mut rep1 = Replica::new(StorageConfig::InMemory.into_storage()?); + let mut serv1 = make_server()?; + + let mut t1 = rep1.new_task(Status::Pending, "test 1".into())?; + log::info!("Applying modifications on replica 1"); + for i in 0..=NUM_VERSIONS { + let mut t1m = t1.into_mut(&mut rep1); + t1m.start()?; + t1m.stop()?; + t1m.set_description(format!("revision {}", i))?; + t1 = t1m.into_immut(); + + rep1.sync(&mut serv1, false)?; + } + + // now set up a second replica and sync it; it should catch up on that history, using a + // snapshot. Note that we can't verify that it used a snapshot, because the server currently + // keeps all versions (so rep2 could sync from the beginning of the version history). You can + // manually verify that it is applying a snapshot by adding `assert!(false)` below and skimming + // the logs. + + let mut rep2 = Replica::new(StorageConfig::InMemory.into_storage()?); + let mut serv2 = make_server()?; + + log::info!("Syncing replica 2"); + rep2.sync(&mut serv2, false)?; + + // those tasks should exist on rep2 now + let t12 = rep2 + .get_task(t1.get_uuid())? + .expect("expected task 1 on rep2"); + + assert_eq!(t12.get_description(), format!("revision {}", NUM_VERSIONS)); + assert_eq!(t12.is_active(), false); + + // sync that back to replica 1 + t12.into_mut(&mut rep2) + .set_description("sync-back".to_owned())?; + rep2.sync(&mut serv2, false)?; + rep1.sync(&mut serv1, false)?; + + let t11 = rep1 + .get_task(t1.get_uuid())? + .expect("expected task 1 on rep1"); + + assert_eq!(t11.get_description(), "sync-back"); + + // uncomment this to force a failure and see the logs + // assert!(false); + + // note that we just drop the server here.. + Ok(()) +} From 5bae2b6464bec85e1c23bc2ed65b796240f1fc34 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 24 Oct 2021 21:29:28 -0400 Subject: [PATCH 390/548] remove debug print --- sync-server/src/server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs index f7b40a16a..fea649e9d 100644 --- a/sync-server/src/server.rs +++ b/sync-server/src/server.rs @@ -188,7 +188,6 @@ pub(crate) fn add_version<'a>( } }; - println!("{:?}", client.snapshot); let version_urgency = match client.snapshot { None => SnapshotUrgency::High, Some(Snapshot { versions_since, .. }) => { From 5648d20bde91726d69928cc2e63198463eb54328 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 25 Oct 2021 09:20:44 -0400 Subject: [PATCH 391/548] add changelog --- .changelogs/2021-10-25-issue23-integration.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelogs/2021-10-25-issue23-integration.md diff --git a/.changelogs/2021-10-25-issue23-integration.md b/.changelogs/2021-10-25-issue23-integration.md new file mode 100644 index 000000000..d10a4d0ec --- /dev/null +++ b/.changelogs/2021-10-25-issue23-integration.md @@ -0,0 +1 @@ +- The details of how task start/stop is represented have changed. Any existing tasks will all be treated as inactive (stopped). From 7fe55530938582683b020abd2cdd722a349b6a34 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 26 Oct 2021 22:37:51 -0400 Subject: [PATCH 392/548] fix errors from merges --- taskchampion/src/server/remote/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index 36baf2325..9f3e92f95 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -165,10 +165,8 @@ impl Server for RemoteServer { { Ok(resp) => { let version_id = get_uuid_header(&resp, "X-Version-Id")?; - let ciphertext = Ciphertext::from_resp(resp, SNAPSHOT_CONTENT_TYPE)?; - let snapshot = ciphertext - .open(&self.encryption_secret, version_id)? - .payload; + let sealed = Sealed::from_resp(resp, version_id, SNAPSHOT_CONTENT_TYPE)?; + let snapshot = self.cryptor.unseal(sealed)?.payload; Ok(Some((version_id, snapshot))) } Err(ureq::Error::Status(status, _)) if status == 404 => Ok(None), From 4314b8bc2dc1d177fef872537d026d7bd0c17cf6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 26 Oct 2021 22:33:14 -0400 Subject: [PATCH 393/548] Add support for annotations This matches the taskwarrior task model for annotations. --- cli/src/argparse/modification.rs | 13 ++-- cli/src/argparse/subcommand.rs | 32 ++++++++ cli/src/invocation/cmd/info.rs | 5 ++ cli/src/invocation/modify.rs | 10 ++- docs/src/tasks.md | 4 +- taskchampion/src/lib.rs | 2 +- taskchampion/src/task/annotation.rs | 2 +- taskchampion/src/task/task.rs | 116 +++++++++++++++++++++++++++- 8 files changed, 173 insertions(+), 11 deletions(-) diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 2005bd2c3..08b04ffd2 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -48,6 +48,9 @@ pub struct Modification { /// Remove tags pub remove_tags: HashSet, + + /// Add annotation + pub annotate: Option, } /// A single argument that is part of a modification, used internally to this module @@ -128,12 +131,12 @@ impl Modification { pub(super) fn get_usage(u: &mut usage::Usage) { u.modifications.push(usage::Modification { syntax: "DESCRIPTION", - summary: "Set description", + summary: "Set description/annotation", description: " - Set the task description. Multiple arguments are combined into a single - space-separated description. To avoid surprises from shell quoting, prefer - to use a single quoted argument, for example `ta 19 modify \"return library - books\"`", + Set the task description (or the task annotation for `ta annotate`). Multiple + arguments are combined into a single space-separated description. To avoid + surprises from shell quoting, prefer to use a single quoted argument, for example + `ta 19 modify \"return library books\"`", }); u.modifications.push(usage::Modification { syntax: "+TAG", diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 2efc52ea4..d06244bec 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -207,6 +207,13 @@ impl Modify { "start" => modification.active = Some(true), "stop" => modification.active = Some(false), "done" => modification.status = Some(Status::Completed), + "annotate" => { + // what would be parsed as a description is, here, used as the annotation + if let DescriptionMod::Set(s) = modification.description { + modification.description = DescriptionMod::None; + modification.annotate = Some(s); + } + } _ => {} } @@ -225,6 +232,7 @@ impl Modify { arg_matching(literal("start")), arg_matching(literal("stop")), arg_matching(literal("done")), + arg_matching(literal("annotate")), )), Modification::parse, )), @@ -278,6 +286,13 @@ impl Modify { Mark all tasks matching the required filter as completed, additionally applying any given modifications.", }); + u.subcommands.push(usage::Subcommand { + name: "annotate", + syntax: " annotate [modification]", + summary: "Mark tasks as completed", + description: " + Add an annotation to all tasks matching the required filter.", + }); } } @@ -652,6 +667,23 @@ mod test { ); } + #[test] + fn test_annotate() { + let subcommand = Subcommand::Modify { + filter: Filter { + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], + }, + modification: Modification { + annotate: Some("sent invoice".into()), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "annotate", "sent", "invoice"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + #[test] fn test_report() { let subcommand = Subcommand::Report { diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index 58bd46fba..bd7e6e269 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -39,6 +39,11 @@ pub(crate) fn execute( if let Some(wait) = task.get_wait() { t.add_row(row![b->"Wait", wait]); } + let mut annotations: Vec<_> = task.get_annotations().collect(); + annotations.sort(); + for ann in annotations { + t.add_row(row![b->"Annotation", format!("{}: {}", ann.entry, ann.description)]); + } } t.print(w)?; } diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index 2e27fba74..e3731e5e1 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,5 +1,6 @@ use crate::argparse::{DescriptionMod, Modification}; -use taskchampion::TaskMut; +use chrono::Utc; +use taskchampion::{Annotation, TaskMut}; /// Apply the given modification pub(super) fn apply_modification( @@ -41,5 +42,12 @@ pub(super) fn apply_modification( task.set_wait(wait)?; } + if let Some(ref ann) = modification.annotate { + task.add_annotation(Annotation { + entry: Utc::now(), + description: ann.into(), + })?; + } + Ok(()) } diff --git a/docs/src/tasks.md b/docs/src/tasks.md index cfd6e1ef3..e19136d88 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -33,8 +33,8 @@ The following keys, and key formats, are defined: * `start` - the most recent time at which this task was started (a task with no `start` key is not active) * `tag.` - indicates this task has tag `` (value is an empty string) * `wait` - indicates the time before which this task should be hidden, as it is not actionable +* `annotation_` - value is an annotation created at the given time The following are not yet implemented: -* `dep.` - indicates this task depends on `` (value is an empty string) -* `annotation.` - value is an annotation created at the given time +* `dep_` - indicates this task depends on `` (value is an empty string) diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index df72bdb22..dea259d4c 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -58,7 +58,7 @@ pub use errors::Error; pub use replica::Replica; pub use server::{Server, ServerConfig}; pub use storage::StorageConfig; -pub use task::{Priority, Status, Tag, Task, TaskMut}; +pub use task::{Annotation, Priority, Status, Tag, Task, TaskMut}; pub use workingset::WorkingSet; /// Re-exported type from the `uuid` crate, for ease of compatibility for consumers of this crate. diff --git a/taskchampion/src/task/annotation.rs b/taskchampion/src/task/annotation.rs index dadb72b0f..951dc3f11 100644 --- a/taskchampion/src/task/annotation.rs +++ b/taskchampion/src/task/annotation.rs @@ -1,7 +1,7 @@ use super::Timestamp; /// An annotation for a task -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Annotation { /// Time the annotation was made pub entry: Timestamp, diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 102d27aa8..6b8f2b3c3 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -1,5 +1,5 @@ use super::tag::{SyntheticTag, TagInner}; -use super::{Status, Tag}; +use super::{Annotation, Status, Tag, Timestamp}; use crate::replica::Replica; use crate::storage::TaskMap; use chrono::prelude::*; @@ -145,6 +145,22 @@ impl Task { ) } + /// Iterate over the task's annotations, in arbitrary order. + pub fn get_annotations(&self) -> impl Iterator + '_ { + self.taskmap.iter().filter_map(|(k, v)| { + if let Some(ts) = k.strip_prefix("annotation_") { + if let Ok(ts) = ts.parse::() { + return Some(Annotation { + entry: Utc.timestamp(ts, 0), + description: v.to_owned(), + }); + } + // note that invalid "annotation_*" are ignored + } + None + }) + } + pub fn get_modified(&self) -> Option> { self.get_timestamp("modified") } @@ -225,6 +241,20 @@ impl<'r> TaskMut<'r> { self.set_string(format!("tag.{}", tag), None) } + /// Add a new annotation. Note that annotations with the same entry time + /// will overwrite one another. + pub fn add_annotation(&mut self, ann: Annotation) -> anyhow::Result<()> { + self.set_string( + format!("annotation_{}", ann.entry.timestamp()), + Some(ann.description), + ) + } + + /// Remove an annotation, based on its entry time. + pub fn remove_annotation(&mut self, entry: Timestamp) -> anyhow::Result<()> { + self.set_string(format!("annotation_{}", entry.timestamp()), None) + } + // -- utility functions fn lastmod(&mut self) -> anyhow::Result<()> { @@ -442,6 +472,90 @@ mod test { assert_eq!(tags, vec![utag("ok"), stag(SyntheticTag::Pending)]); } + #[test] + fn test_get_annotations() { + let task = Task::new( + Uuid::new_v4(), + vec![ + ( + String::from("annotation_1635301873"), + String::from("left message"), + ), + ( + String::from("annotation_1635301883"), + String::from("left another message"), + ), + (String::from("annotation_"), String::from("invalid")), + (String::from("annotation_abcde"), String::from("invalid")), + ] + .drain(..) + .collect(), + ); + + let mut anns: Vec<_> = task.get_annotations().collect(); + anns.sort(); + assert_eq!( + anns, + vec![ + Annotation { + entry: Utc.timestamp(1635301873, 0), + description: "left message".into() + }, + Annotation { + entry: Utc.timestamp(1635301883, 0), + description: "left another message".into() + } + ] + ); + } + + #[test] + fn test_add_annotation() { + with_mut_task(|mut task| { + task.add_annotation(Annotation { + entry: Utc.timestamp(1635301900, 0), + description: "right message".into(), + }) + .unwrap(); + let k = "annotation_1635301900"; + assert_eq!(task.taskmap[k], "right message".to_owned()); + task.reload().unwrap(); + assert_eq!(task.taskmap[k], "right message".to_owned()); + // adding with same time overwrites.. + task.add_annotation(Annotation { + entry: Utc.timestamp(1635301900, 0), + description: "right message 2".into(), + }) + .unwrap(); + assert_eq!(task.taskmap[k], "right message 2".to_owned()); + }); + } + + #[test] + fn test_remove_annotation() { + with_mut_task(|mut task| { + task.set_string("annotation_1635301873", Some("left message".into())) + .unwrap(); + task.set_string("annotation_1635301883", Some("left another message".into())) + .unwrap(); + + task.remove_annotation(Utc.timestamp(1635301873, 0)) + .unwrap(); + + task.reload().unwrap(); + + let mut anns: Vec<_> = task.get_annotations().collect(); + anns.sort(); + assert_eq!( + anns, + vec![Annotation { + entry: Utc.timestamp(1635301883, 0), + description: "left another message".into() + }] + ); + }); + } + #[test] fn test_start() { with_mut_task(|mut task| { From e9b3611fd9eeb2c8a24bcd8cc1c819f87d745286 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 29 Oct 2021 20:41:37 -0400 Subject: [PATCH 394/548] Use `tag_` instead of `tag.` This aligns with the Taskwarrior data model. --- docs/src/tasks.md | 2 +- taskchampion/src/task/task.rs | 36 +++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/src/tasks.md b/docs/src/tasks.md index cfd6e1ef3..e892d5886 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -31,7 +31,7 @@ The following keys, and key formats, are defined: * `description` - the one-line summary of the task * `modified` - the time of the last modification of this task * `start` - the most recent time at which this task was started (a task with no `start` key is not active) -* `tag.` - indicates this task has tag `` (value is an empty string) +* `tag_` - indicates this task has tag `` (value is an empty string) * `wait` - indicates the time before which this task should be hidden, as it is not actionable The following are not yet implemented: diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 102d27aa8..ae1e60b3a 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -118,7 +118,7 @@ impl Task { /// Check if this task has the given tag pub fn has_tag(&self, tag: &Tag) -> bool { match tag.inner() { - TagInner::User(s) => self.taskmap.contains_key(&format!("tag.{}", s)), + TagInner::User(s) => self.taskmap.contains_key(&format!("tag_{}", s)), TagInner::Synthetic(st) => self.has_synthetic_tag(st), } } @@ -130,11 +130,11 @@ impl Task { self.taskmap .iter() .filter_map(|(k, _)| { - if let Some(tag) = k.strip_prefix("tag.") { + if let Some(tag) = k.strip_prefix("tag_") { if let Ok(tag) = tag.try_into() { return Some(tag); } - // note that invalid "tag.*" are ignored + // note that invalid "tag_*" are ignored } None }) @@ -214,7 +214,7 @@ impl<'r> TaskMut<'r> { if tag.is_synthetic() { anyhow::bail!("Synthetic tags cannot be modified"); } - self.set_string(format!("tag.{}", tag), Some("".to_owned())) + self.set_string(format!("tag_{}", tag), Some("".to_owned())) } /// Remove a tag from this task. Does nothing if the tag is not present. @@ -222,7 +222,7 @@ impl<'r> TaskMut<'r> { if tag.is_synthetic() { anyhow::bail!("Synthetic tags cannot be modified"); } - self.set_string(format!("tag.{}", tag), None) + self.set_string(format!("tag_{}", tag), None) } // -- utility functions @@ -383,7 +383,7 @@ mod test { let task = Task::new( Uuid::new_v4(), vec![ - (String::from("tag.abc"), String::from("")), + (String::from("tag_abc"), String::from("")), (String::from("start"), String::from("1234")), ] .drain(..) @@ -402,8 +402,8 @@ mod test { let task = Task::new( Uuid::new_v4(), vec![ - (String::from("tag.abc"), String::from("")), - (String::from("tag.def"), String::from("")), + (String::from("tag_abc"), String::from("")), + (String::from("tag_def"), String::from("")), // set `wait` so the synthetic tag WAITING is present (String::from("wait"), String::from("33158909732")), ] @@ -428,10 +428,10 @@ mod test { let task = Task::new( Uuid::new_v4(), vec![ - (String::from("tag.ok"), String::from("")), - (String::from("tag."), String::from("")), - (String::from("tag.123"), String::from("")), - (String::from("tag.a!!"), String::from("")), + (String::from("tag_ok"), String::from("")), + (String::from("tag_"), String::from("")), + (String::from("tag_123"), String::from("")), + (String::from("tag_a!!"), String::from("")), ] .drain(..) .collect(), @@ -497,12 +497,12 @@ mod test { fn test_add_tags() { with_mut_task(|mut task| { task.add_tag(&utag("abc")).unwrap(); - assert!(task.taskmap.contains_key("tag.abc")); + assert!(task.taskmap.contains_key("tag_abc")); task.reload().unwrap(); - assert!(task.taskmap.contains_key("tag.abc")); + assert!(task.taskmap.contains_key("tag_abc")); // redundant add has no effect.. task.add_tag(&utag("abc")).unwrap(); - assert!(task.taskmap.contains_key("tag.abc")); + assert!(task.taskmap.contains_key("tag_abc")); }); } @@ -511,13 +511,13 @@ mod test { with_mut_task(|mut task| { task.add_tag(&utag("abc")).unwrap(); task.reload().unwrap(); - assert!(task.taskmap.contains_key("tag.abc")); + assert!(task.taskmap.contains_key("tag_abc")); task.remove_tag(&utag("abc")).unwrap(); - assert!(!task.taskmap.contains_key("tag.abc")); + assert!(!task.taskmap.contains_key("tag_abc")); // redundant remove has no effect.. task.remove_tag(&utag("abc")).unwrap(); - assert!(!task.taskmap.contains_key("tag.abc")); + assert!(!task.taskmap.contains_key("tag_abc")); }); } } From ef12e1a2f80403ea82eb17ef54cc88a48c127b4d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 18 Dec 2021 23:39:56 +0000 Subject: [PATCH 395/548] Define UDAs --- docs/src/tasks.md | 11 ++ taskchampion/src/task/task.rs | 188 +++++++++++++++++++++++++++++++--- 2 files changed, 186 insertions(+), 13 deletions(-) diff --git a/docs/src/tasks.md b/docs/src/tasks.md index f21dd475d..f8debd20c 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -38,3 +38,14 @@ The following keys, and key formats, are defined: The following are not yet implemented: * `dep_` - indicates this task depends on `` (value is an empty string) + +### UDAs + +Any unrecognized keys are treated as "user-defined attributes" (UDAs). +These attributes can be used to store additional data associated with a task. +For example, applications that synchronize tasks with other systems such as calendars or team planning services might store unique identifiers for those systems as UDAs. +The application defining a UDA defines the format of the value. + +UDAs _should_ have a namespaced structure of the form `.`, where `` identifies the application defining the UDA. +For example, a service named "DevSync" synchronizing tasks from GitHub might use UDAs like `devsync.github.issue-id`. +Note that many existing UDAs for Taskwarrior integrations do not follow this pattern. diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index d38555e19..5d01293d1 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -6,6 +6,7 @@ use chrono::prelude::*; use log::trace; use std::convert::AsRef; use std::convert::TryInto; +use std::str::FromStr; use uuid::Uuid; /* The Task and TaskMut classes wrap the underlying [`TaskMap`], which is a simple key/value map. @@ -46,6 +47,18 @@ pub struct TaskMut<'r> { updated_modified: bool, } +/// An enum containing all of the key names defined in the data model, with the exception +/// of the properties containing data (`tag_..`, etc.) +#[derive(strum_macros::AsRefStr, strum_macros::EnumString)] +#[strum(serialize_all = "kebab-case")] +enum Prop { + Description, + Modified, + Start, + Status, + Wait, +} + impl Task { pub(crate) fn new(uuid: Uuid, taskmap: TaskMap) -> Task { Task { uuid, taskmap } @@ -71,14 +84,14 @@ impl Task { pub fn get_status(&self) -> Status { self.taskmap - .get("status") + .get(Prop::Status.as_ref()) .map(|s| Status::from_taskmap(s)) .unwrap_or(Status::Pending) } pub fn get_description(&self) -> &str { self.taskmap - .get("description") + .get(Prop::Description.as_ref()) .map(|s| s.as_ref()) .unwrap_or("") } @@ -86,7 +99,7 @@ impl Task { /// Get the wait time. If this value is set, it will be returned, even /// if it is in the past. pub fn get_wait(&self) -> Option> { - self.get_timestamp("wait") + self.get_timestamp(Prop::Wait.as_ref()) } /// Determine whether this task is waiting now. @@ -100,7 +113,7 @@ impl Task { /// Determine whether this task is active -- that is, that it has been started /// and not stopped. pub fn is_active(&self) -> bool { - self.taskmap.contains_key("start") + self.taskmap.contains_key(Prop::Start.as_ref()) } /// Determine whether a given synthetic tag is present on this task. All other @@ -161,12 +174,37 @@ impl Task { }) } + /// Get the named user defined attributes (UDA). This will return None + /// for any key defined in the Task data model, regardless of whether + /// it is set or not. + pub fn get_uda(&self, key: &str) -> Option<&str> { + if Task::is_known_key(key) { + return None; + } + self.taskmap.get(key).map(|s| s.as_ref()) + } + + /// Get the user defined attributes (UDAs) of this task, in arbitrary order. + pub fn get_udas(&self) -> impl Iterator + '_ { + self.taskmap + .iter() + .filter(|(p, _)| !Task::is_known_key(p)) + .map(|(p, v)| (p.as_ref(), v.as_ref())) + } + pub fn get_modified(&self) -> Option> { - self.get_timestamp("modified") + self.get_timestamp(Prop::Modified.as_ref()) } // -- utility functions + fn is_known_key(key: &str) -> bool { + Prop::from_str(key).is_ok() + || key.starts_with("tag_") + || key.starts_with("annotation_") + || key.starts_with("dep_") + } + fn get_timestamp(&self, property: &str) -> Option> { if let Some(ts) = self.taskmap.get(property) { if let Ok(ts) = ts.parse() { @@ -191,19 +229,22 @@ impl<'r> TaskMut<'r> { let uuid = self.uuid; self.replica.add_to_working_set(uuid)?; } - self.set_string("status", Some(String::from(status.to_taskmap()))) + self.set_string( + Prop::Status.as_ref(), + Some(String::from(status.to_taskmap())), + ) } pub fn set_description(&mut self, description: String) -> anyhow::Result<()> { - self.set_string("description", Some(description)) + self.set_string(Prop::Description.as_ref(), Some(description)) } pub fn set_wait(&mut self, wait: Option>) -> anyhow::Result<()> { - self.set_timestamp("wait", wait) + self.set_timestamp(Prop::Wait.as_ref(), wait) } pub fn set_modified(&mut self, modified: DateTime) -> anyhow::Result<()> { - self.set_timestamp("modified", Some(modified)) + self.set_timestamp(Prop::Modified.as_ref(), Some(modified)) } /// Start the task by creating "start": "", if the task is not already @@ -212,12 +253,12 @@ impl<'r> TaskMut<'r> { if self.is_active() { return Ok(()); } - self.set_timestamp("start", Some(Utc::now())) + self.set_timestamp(Prop::Start.as_ref(), Some(Utc::now())) } /// Stop the task by removing the `start` key pub fn stop(&mut self) -> anyhow::Result<()> { - self.set_timestamp("start", None) + self.set_timestamp(Prop::Start.as_ref(), None) } /// Mark this task as complete @@ -255,15 +296,50 @@ impl<'r> TaskMut<'r> { self.set_string(format!("annotation_{}", entry.timestamp()), None) } + /// Set a user-defined attribute (UDA). This will fail if the key is defined by the data + /// model. + pub fn set_uda(&mut self, key: S1, value: S2) -> anyhow::Result<()> + where + S1: Into, + S2: Into, + { + let key = key.into(); + if Task::is_known_key(&key) { + anyhow::bail!( + "Property name {} as special meaning in a task and cannot be used as a UDA", + key + ); + } + self.set_string(key, Some(value.into())) + } + + /// Remove a user-defined attribute (UDA). This will fail if the key is defined by the data + /// model. + pub fn remove_uda(&mut self, key: S) -> anyhow::Result<()> + where + S: Into, + { + let key = key.into(); + if Task::is_known_key(&key) { + anyhow::bail!( + "Property name {} as special meaning in a task and cannot be used as a UDA", + key + ); + } + self.set_string(key, None) + } + // -- utility functions fn lastmod(&mut self) -> anyhow::Result<()> { if !self.updated_modified { let now = format!("{}", Utc::now().timestamp()); self.replica - .update_task(self.task.uuid, "modified", Some(now.clone()))?; + .update_task(self.task.uuid, Prop::Modified.as_ref(), Some(now.clone()))?; trace!("task {}: set property modified={:?}", self.task.uuid, now); - self.task.taskmap.insert(String::from("modified"), now); + self.task + .taskmap + .insert(String::from(Prop::Modified.as_ref()), now); self.updated_modified = true; } Ok(()) @@ -634,4 +710,90 @@ mod test { assert!(!task.taskmap.contains_key("tag_abc")); }); } + + #[test] + fn test_get_udas() { + let task = Task::new( + Uuid::new_v4(), + vec![ + ("description".into(), "not a uda".into()), + ("modified".into(), "not a uda".into()), + ("start".into(), "not a uda".into()), + ("status".into(), "not a uda".into()), + ("wait".into(), "not a uda".into()), + ("start".into(), "not a uda".into()), + ("tag_abc".into(), "not a uda".into()), + ("dep_1234".into(), "not a uda".into()), + ("annotation_1234".into(), "not a uda".into()), + ("githubid".into(), "123".into()), + ] + .drain(..) + .collect(), + ); + + let udas: Vec<_> = task.get_udas().collect(); + assert_eq!(udas, vec![("githubid", "123")]); + } + + #[test] + fn test_get_uda() { + let task = Task::new( + Uuid::new_v4(), + vec![ + ("description".into(), "not a uda".into()), + ("dep_1234".into(), "not a uda".into()), + ("githubid".into(), "123".into()), + ] + .drain(..) + .collect(), + ); + + assert_eq!(task.get_uda("description"), None); // invalid UDA + assert_eq!(task.get_uda("dep_1234"), None); // invalid UDA + assert_eq!(task.get_uda("githubid"), Some("123")); + assert_eq!(task.get_uda("jiraid"), None); + } + + #[test] + fn test_set_uda() { + with_mut_task(|mut task| { + task.set_uda("githubid", "123").unwrap(); + + let udas: Vec<_> = task.get_udas().collect(); + assert_eq!(udas, vec![("githubid", "123")]); + + task.set_uda("jiraid", "TW-1234").unwrap(); + + let mut udas: Vec<_> = task.get_udas().collect(); + udas.sort_unstable(); + assert_eq!(udas, vec![("githubid", "123"), ("jiraid", "TW-1234")]); + }) + } + + #[test] + fn test_set_uda_invalid() { + with_mut_task(|mut task| { + assert!(task.set_uda("modified", "123").is_err()); + assert!(task.set_uda("tag_abc", "123").is_err()); + }) + } + + #[test] + fn test_rmmove_uda() { + with_mut_task(|mut task| { + task.set_string("githubid", Some("123".into())).unwrap(); + task.remove_uda("githubid").unwrap(); + + let udas: Vec<_> = task.get_udas().collect(); + assert_eq!(udas, vec![]); + }) + } + + #[test] + fn test_remove_uda_invalid() { + with_mut_task(|mut task| { + assert!(task.remove_uda("modified").is_err()); + assert!(task.remove_uda("tag_abc").is_err()); + }) + } } From ff9ad8185b3f2fb6c53f8a64fca529ade86be568 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 12 Dec 2021 10:31:27 -0500 Subject: [PATCH 396/548] undo docs --- docs/src/storage.md | 52 ++++++++++++++++++++++++++++++++++++++---- docs/src/sync-model.md | 10 ++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/docs/src/storage.md b/docs/src/storage.md index c1c34f7e1..f733f4f98 100644 --- a/docs/src/storage.md +++ b/docs/src/storage.md @@ -21,21 +21,63 @@ See [Tasks](./tasks.md) for details on the content of that map. Every change to the task database is captured as an operation. In other words, operations act as deltas between database states. -Operations are crucial to synchronization of replicas, using a technique known as Operational Transforms. +Operations are crucial to synchronization of replicas, described in [Synchronization Model](./sync-model.md). + +Operations are entirely managed by the replica, and some combinations of operations are described as "invalid" here. +A replica must not create invalid operations, but should be resilient to receiving invalid operations during a synchronization operation. Each operation has one of the forms * `Create(uuid)` - * `Delete(uuid)` - * `Update(uuid, property, value, timestamp)` + * `Delete(uuid, oldTask)` + * `Update(uuid, property, oldValue, newValue, timestamp)` + * `UndoPoint()` The Create form creates a new task. It is invalid to create a task that already exists. Similarly, the Delete form deletes an existing task. It is invalid to delete a task that does not exist. +The `oldTask` property contains the task data from before it was deleted. -The Update form updates the given property of the given task, where property and value are both strings. -Value can also be `None` to indicate deletion of a property. +The Update form updates the given property of the given task, where the property and values are strings. +The `oldValue` gives the old value of the property (or None to create a new property), while `newValue` gives the new value (or None to delete a property). It is invalid to update a task that does not exist. The timestamp on updates serves as additional metadata and is used to resolve conflicts. + +### Application + +Each operation can be "applied" to a task database in a natural way: + + * Applying `Create` creates a new, empty task in the task database. + * Applying `Delete` deletes a task, including all of its properties, from the task database. + * Applying `Update` modifies the properties of a task. + * Applying `UndoPoint` does nothing. + +### Undo + +Each operation also contains enough information to reverse its application: + + * Undoing `Create` deletes a task. + * Undoing `Delete` creates a task, including all of the properties in `oldTask`. + * Undoing `Update` modifies the properties of a task, reverting to `oldValue`. + * Undoing `UndoPoint` does nothing. + +The `UndoPoint` operation serves as a marker of points in the operation sequence to which the user might wish to undo. +For example, creation of a new task with several properities involves several operations, but is a single step from the user's perspective. +An "undo" command reverses operations, removing them from the operations sequence, until it reaches an `UndoPoint` operation. + +### Synchronizing Operations + +After operations are synchronized to the server, they can no longer be undone. +As such, the [synchronization model](./sync-model.md) uses simpler operations. +Replica operations are converted to sync operations as follows: + + * `Create(uuid)` -> `Create(uuid)` (no change) + * `Delete(uuid, oldTask)` -> `Delete(uuid)` + * `Update(uuid, property, oldValue, newValue, timestamp)` -> `Update(uuid, property, newValue, timestamp)` + * `UndoPoint()` -> Ø (dropped from operation sequence) + +Once a sequence of operations has been synchronized, there is no need to store those operations on the replica. +The current implementation deletes operations at that time. +An alternative approach is to keep operations for existing tasks, and provide access to those operations as a "history" of modifications to the task. diff --git a/docs/src/sync-model.md b/docs/src/sync-model.md index e75bab670..11d6e0855 100644 --- a/docs/src/sync-model.md +++ b/docs/src/sync-model.md @@ -34,6 +34,16 @@ Since the replicas are not connected, each may have additional operations that h The synchronization process uses operational transformation to "linearize" those operations. This process is analogous (vaguely) to rebasing a sequence of Git commits. +### Sync Operations + +The [Replica Storage](./storage.md) model contains additional information in its operations that is not included in operations synchronized to other replicas. +In this document, we will be discussing "sync operations" of the form + + * `Create(uuid)` + * `Delete(uuid)` + * `Update(uuid, property, value, timestamp)` + + ### Versions Occasionally, database states are given a name (that takes the form of a UUID). From 6f7794c7de9dd20c49248676243e1545d2c3389f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 19 Dec 2021 19:50:26 +0000 Subject: [PATCH 397/548] introduce a new taskchampion::server::SyncOp type --- taskchampion/src/server/mod.rs | 3 + taskchampion/src/server/op.rs | 420 +++++++++++++++++++++++++++++++++ taskchampion/src/taskdb/mod.rs | 28 ++- 3 files changed, 449 insertions(+), 2 deletions(-) create mode 100644 taskchampion/src/server/op.rs diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index eb77b9bd3..f97b9181b 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -14,6 +14,7 @@ pub(crate) mod test; mod config; mod crypto; mod local; +mod op; mod remote; mod types; @@ -21,3 +22,5 @@ pub use config::ServerConfig; pub use local::LocalServer; pub use remote::RemoteServer; pub use types::*; + +pub(crate) use op::SyncOp; diff --git a/taskchampion/src/server/op.rs b/taskchampion/src/server/op.rs new file mode 100644 index 000000000..6f6561ceb --- /dev/null +++ b/taskchampion/src/server/op.rs @@ -0,0 +1,420 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// A SyncOp defines a single change to the task database, that can be synchronized +/// via a server. +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum SyncOp { + /// Create a new task. + /// + /// On application, if the task already exists, the operation does nothing. + Create { uuid: Uuid }, + + /// Delete an existing task. + /// + /// On application, if the task does not exist, the operation does nothing. + Delete { uuid: Uuid }, + + /// Update an existing task, setting the given property to the given value. If the value is + /// None, then the corresponding property is deleted. + /// + /// If the given task does not exist, the operation does nothing. + Update { + uuid: Uuid, + property: String, + value: Option, + timestamp: DateTime, + }, +} + +use SyncOp::*; + +impl SyncOp { + // Transform takes two operations A and B that happened concurrently and produces two + // operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This + // function is used to serialize operations in a process similar to a Git "rebase". + // + // * + // / \ + // op1 / \ op2 + // / \ + // * * + // + // this function "completes the diamond: + // + // * * + // \ / + // op2' \ / op1' + // \ / + // * + // + // such that applying op2' after op1 has the same effect as applying op1' after op2. This + // allows two different systems which have already applied op1 and op2, respectively, and thus + // reached different states, to return to the same state by applying op2' and op1', + // respectively. + pub fn transform(operation1: SyncOp, operation2: SyncOp) -> (Option, Option) { + match (&operation1, &operation2) { + // Two creations or deletions of the same uuid reach the same state, so there's no need + // for any further operations to bring the state together. + (&Create { uuid: uuid1 }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => (None, None), + (&Delete { uuid: uuid1 }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => (None, None), + + // Given a create and a delete of the same task, one of the operations is invalid: the + // create implies the task does not exist, but the delete implies it exists. Somewhat + // arbitrarily, we prefer the Create + (&Create { uuid: uuid1 }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => { + (Some(operation1), None) + } + (&Delete { uuid: uuid1 }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => { + (None, Some(operation2)) + } + + // And again from an Update and a Create, prefer the Update + (&Update { uuid: uuid1, .. }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => { + (Some(operation1), None) + } + (&Create { uuid: uuid1 }, &Update { uuid: uuid2, .. }) if uuid1 == uuid2 => { + (None, Some(operation2)) + } + + // Given a delete and an update, prefer the delete + (&Update { uuid: uuid1, .. }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => { + (None, Some(operation2)) + } + (&Delete { uuid: uuid1 }, &Update { uuid: uuid2, .. }) if uuid1 == uuid2 => { + (Some(operation1), None) + } + + // Two updates to the same property of the same task might conflict. + ( + &Update { + uuid: ref uuid1, + property: ref property1, + value: ref value1, + timestamp: ref timestamp1, + }, + &Update { + uuid: ref uuid2, + property: ref property2, + value: ref value2, + timestamp: ref timestamp2, + }, + ) if uuid1 == uuid2 && property1 == property2 => { + // if the value is the same, there's no conflict + if value1 == value2 { + (None, None) + } else if timestamp1 < timestamp2 { + // prefer the later modification + (None, Some(operation2)) + } else { + // prefer the later modification or, if the modifications are the same, + // just choose one of them + (Some(operation1), None) + } + } + + // anything else is not a conflict of any sort, so return the operations unchanged + (_, _) => (Some(operation1), Some(operation2)), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::storage::InMemoryStorage; + use crate::taskdb::TaskDb; + use chrono::{Duration, Utc}; + use pretty_assertions::assert_eq; + use proptest::prelude::*; + + #[test] + fn test_json_create() -> anyhow::Result<()> { + let uuid = Uuid::new_v4(); + let op = Create { uuid }; + let json = serde_json::to_string(&op)?; + assert_eq!(json, format!(r#"{{"Create":{{"uuid":"{}"}}}}"#, uuid)); + let deser: SyncOp = serde_json::from_str(&json)?; + assert_eq!(deser, op); + Ok(()) + } + + #[test] + fn test_json_delete() -> anyhow::Result<()> { + let uuid = Uuid::new_v4(); + let op = Delete { uuid }; + let json = serde_json::to_string(&op)?; + assert_eq!(json, format!(r#"{{"Delete":{{"uuid":"{}"}}}}"#, uuid)); + let deser: SyncOp = serde_json::from_str(&json)?; + assert_eq!(deser, op); + Ok(()) + } + + #[test] + fn test_json_update() -> anyhow::Result<()> { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + let op = Update { + uuid, + property: "abc".into(), + value: Some("false".into()), + timestamp, + }; + + let json = serde_json::to_string(&op)?; + assert_eq!( + json, + format!( + r#"{{"Update":{{"uuid":"{}","property":"abc","value":"false","timestamp":"{:?}"}}}}"#, + uuid, timestamp, + ) + ); + let deser: SyncOp = serde_json::from_str(&json)?; + assert_eq!(deser, op); + Ok(()) + } + + #[test] + fn test_json_update_none() -> anyhow::Result<()> { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + let op = Update { + uuid, + property: "abc".into(), + value: None, + timestamp, + }; + + let json = serde_json::to_string(&op)?; + assert_eq!( + json, + format!( + r#"{{"Update":{{"uuid":"{}","property":"abc","value":null,"timestamp":"{:?}"}}}}"#, + uuid, timestamp, + ) + ); + let deser: SyncOp = serde_json::from_str(&json)?; + assert_eq!(deser, op); + Ok(()) + } + + fn test_transform( + setup: Option, + o1: SyncOp, + o2: SyncOp, + exp1p: Option, + exp2p: Option, + ) { + let (o1p, o2p) = SyncOp::transform(o1.clone(), o2.clone()); + assert_eq!((&o1p, &o2p), (&exp1p, &exp2p)); + + // check that the two operation sequences have the same effect, enforcing the invariant of + // the transform function. + let mut db1 = TaskDb::new_inmemory(); + if let Some(ref o) = setup { + db1.apply_sync_tmp(o.clone()).unwrap(); + } + db1.apply_sync_tmp(o1).unwrap(); + if let Some(o) = o2p { + db1.apply_sync_tmp(o).unwrap(); + } + + let mut db2 = TaskDb::new_inmemory(); + if let Some(ref o) = setup { + db2.apply_sync_tmp(o.clone()).unwrap(); + } + db2.apply_sync_tmp(o2).unwrap(); + if let Some(o) = o1p { + db2.apply_sync_tmp(o).unwrap(); + } + + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + } + + #[test] + fn test_unrelated_create() { + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + test_transform( + None, + Create { uuid: uuid1 }, + Create { uuid: uuid2 }, + Some(Create { uuid: uuid1 }), + Some(Create { uuid: uuid2 }), + ); + } + + #[test] + fn test_related_updates_different_props() { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + test_transform( + Some(Create { uuid }), + Update { + uuid, + property: "abc".into(), + value: Some("true".into()), + timestamp, + }, + Update { + uuid, + property: "def".into(), + value: Some("false".into()), + timestamp, + }, + Some(Update { + uuid, + property: "abc".into(), + value: Some("true".into()), + timestamp, + }), + Some(Update { + uuid, + property: "def".into(), + value: Some("false".into()), + timestamp, + }), + ); + } + + #[test] + fn test_related_updates_same_prop() { + let uuid = Uuid::new_v4(); + let timestamp1 = Utc::now(); + let timestamp2 = timestamp1 + Duration::seconds(10); + + test_transform( + Some(Create { uuid }), + Update { + uuid, + property: "abc".into(), + value: Some("true".into()), + timestamp: timestamp1, + }, + Update { + uuid, + property: "abc".into(), + value: Some("false".into()), + timestamp: timestamp2, + }, + None, + Some(Update { + uuid, + property: "abc".into(), + value: Some("false".into()), + timestamp: timestamp2, + }), + ); + } + + #[test] + fn test_related_updates_same_prop_same_time() { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + test_transform( + Some(Create { uuid }), + Update { + uuid, + property: "abc".into(), + value: Some("true".into()), + timestamp, + }, + Update { + uuid, + property: "abc".into(), + value: Some("false".into()), + timestamp, + }, + Some(Update { + uuid, + property: "abc".into(), + value: Some("true".into()), + timestamp, + }), + None, + ); + } + + fn uuid_strategy() -> impl Strategy { + prop_oneof![ + Just(Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap()), + Just(Uuid::parse_str("56e0be07-c61f-494c-a54c-bdcfdd52d2a7").unwrap()), + Just(Uuid::parse_str("4b7ed904-f7b0-4293-8a10-ad452422c7b3").unwrap()), + Just(Uuid::parse_str("9bdd0546-07c8-4e1f-a9bc-9d6299f4773b").unwrap()), + ] + } + + fn operation_strategy() -> impl Strategy { + prop_oneof![ + uuid_strategy().prop_map(|uuid| Create { uuid }), + uuid_strategy().prop_map(|uuid| Delete { uuid }), + (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { + Update { + uuid, + property, + value: Some("true".into()), + timestamp: Utc::now(), + } + }), + ] + } + + proptest! { + #![proptest_config(ProptestConfig { + cases: 1024, .. ProptestConfig::default() + })] + #[test] + // check that the two operation sequences have the same effect, enforcing the invariant of + // the transform function. + fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { + let (o1p, o2p) = SyncOp::transform(o1.clone(), o2.clone()); + + let mut db1 = TaskDb::new(Box::new(InMemoryStorage::new())); + let mut db2 = TaskDb::new(Box::new(InMemoryStorage::new())); + + // Ensure that any expected tasks already exist + if let Update{ uuid, .. } = o1 { + let _ = db1.apply_sync_tmp(Create{uuid}); + let _ = db2.apply_sync_tmp(Create{uuid}); + } + + if let Update{ uuid, .. } = o2 { + let _ = db1.apply_sync_tmp(Create{uuid}); + let _ = db2.apply_sync_tmp(Create{uuid}); + } + + if let Delete{ uuid } = o1 { + let _ = db1.apply_sync_tmp(Create{uuid}); + let _ = db2.apply_sync_tmp(Create{uuid}); + } + + if let Delete{ uuid } = o2 { + let _ = db1.apply_sync_tmp(Create{uuid}); + let _ = db2.apply_sync_tmp(Create{uuid}); + } + + // if applying the initial operations fail, that indicates the operation was invalid + // in the base state, so consider the case successful. + if db1.apply_sync_tmp(o1).is_err() { + return Ok(()); + } + if db2.apply_sync_tmp(o2).is_err() { + return Ok(()); + } + + if let Some(o) = o2p { + db1.apply_sync_tmp(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?; + } + if let Some(o) = o1p { + db2.apply_sync_tmp(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?; + } + assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); + } + } +} diff --git a/taskchampion/src/taskdb/mod.rs b/taskchampion/src/taskdb/mod.rs index 65546c462..026e82698 100644 --- a/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -1,4 +1,4 @@ -use crate::server::Server; +use crate::server::{Server, SyncOp}; use crate::storage::{Operation, Storage, TaskMap}; use uuid::Uuid; @@ -22,7 +22,10 @@ impl TaskDb { #[cfg(test)] pub fn new_inmemory() -> TaskDb { - TaskDb::new(Box::new(crate::storage::InMemoryStorage::new())) + #[cfg(test)] + use crate::storage::InMemoryStorage; + + TaskDb::new(Box::new(InMemoryStorage::new())) } /// Apply an operation to the TaskDb. Aside from synchronization operations, this is the only way @@ -39,6 +42,27 @@ impl TaskDb { Ok(()) } + // temporary + pub fn apply_sync_tmp(&mut self, op: SyncOp) -> anyhow::Result<()> { + // create an op from SyncOp + let op = match op { + SyncOp::Create { uuid } => Operation::Create { uuid }, + SyncOp::Delete { uuid } => Operation::Delete { uuid }, + SyncOp::Update { + uuid, + property, + value, + timestamp, + } => Operation::Update { + uuid, + property, + value, + timestamp, + }, + }; + self.apply(op) + } + /// Get all tasks. pub fn all_tasks(&mut self) -> anyhow::Result> { let mut txn = self.storage.txn()?; From 0b29efab31950f7b93794a15620e828faa7b3e26 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 19 Dec 2021 20:03:01 +0000 Subject: [PATCH 398/548] rename Operation to ReplicaOp for clarity --- taskchampion/src/replica.rs | 8 +-- taskchampion/src/storage/inmemory.rs | 10 +-- taskchampion/src/storage/mod.rs | 10 +-- .../src/storage/{operation.rs => op.rs} | 61 ++++++++++--------- taskchampion/src/storage/sqlite.rs | 44 ++++++------- taskchampion/src/taskdb/mod.rs | 22 +++---- taskchampion/src/taskdb/ops.rs | 34 +++++------ taskchampion/src/taskdb/sync.rs | 44 ++++++------- taskchampion/src/taskdb/working_set.rs | 6 +- 9 files changed, 120 insertions(+), 119 deletions(-) rename taskchampion/src/storage/{operation.rs => op.rs} (88%) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 7afbc90a3..6bdb756e8 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -1,6 +1,6 @@ use crate::errors::Error; use crate::server::Server; -use crate::storage::{Operation, Storage, TaskMap}; +use crate::storage::{ReplicaOp, Storage, TaskMap}; use crate::task::{Status, Task}; use crate::taskdb::TaskDb; use crate::workingset::WorkingSet; @@ -56,7 +56,7 @@ impl Replica { S1: Into, S2: Into, { - self.taskdb.apply(Operation::Update { + self.taskdb.apply(ReplicaOp::Update { uuid, property: property.into(), value: value.map(|v| v.into()), @@ -100,7 +100,7 @@ impl Replica { /// Create a new task. The task must not already exist. pub fn new_task(&mut self, status: Status, description: String) -> anyhow::Result { let uuid = Uuid::new_v4(); - self.taskdb.apply(Operation::Create { uuid })?; + self.taskdb.apply(ReplicaOp::Create { uuid })?; trace!("task {} created", uuid); let mut task = Task::new(uuid, TaskMap::new()).into_mut(self); task.set_description(description)?; @@ -118,7 +118,7 @@ impl Replica { if self.taskdb.get_task(uuid)?.is_none() { return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); } - self.taskdb.apply(Operation::Delete { uuid })?; + self.taskdb.apply(ReplicaOp::Delete { uuid })?; trace!("task {} deleted", uuid); Ok(()) } diff --git a/taskchampion/src/storage/inmemory.rs b/taskchampion/src/storage/inmemory.rs index 448d9ae7f..4a4463c19 100644 --- a/taskchampion/src/storage/inmemory.rs +++ b/taskchampion/src/storage/inmemory.rs @@ -1,6 +1,6 @@ #![allow(clippy::new_without_default)] -use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; +use crate::storage::{ReplicaOp, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; @@ -9,7 +9,7 @@ use uuid::Uuid; struct Data { tasks: HashMap, base_version: VersionId, - operations: Vec, + operations: Vec, working_set: Vec>, } @@ -87,16 +87,16 @@ impl<'t> StorageTxn for Txn<'t> { Ok(()) } - fn operations(&mut self) -> anyhow::Result> { + fn operations(&mut self) -> anyhow::Result> { Ok(self.data_ref().operations.clone()) } - fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { + fn add_operation(&mut self, op: ReplicaOp) -> anyhow::Result<()> { self.mut_data_ref().operations.push(op); Ok(()) } - fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { + fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { self.mut_data_ref().operations = ops; Ok(()) } diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index a16bb5a7e..d3d494a1b 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -11,14 +11,14 @@ use uuid::Uuid; mod config; mod inmemory; -mod operation; +mod op; pub(crate) mod sqlite; pub use config::StorageConfig; pub use inmemory::InMemoryStorage; pub use sqlite::SqliteStorage; -pub use operation::Operation; +pub use op::ReplicaOp; /// An in-memory representation of a task as a simple hashmap pub type TaskMap = HashMap; @@ -80,14 +80,14 @@ pub trait StorageTxn { /// Get the current set of outstanding operations (operations that have not been sync'd to the /// server yet) - fn operations(&mut self) -> Result>; + fn operations(&mut self) -> Result>; /// Add an operation to the end of the list of operations in the storage. Note that this /// merely *stores* the operation; it is up to the TaskDb to apply it. - fn add_operation(&mut self, op: Operation) -> Result<()>; + fn add_operation(&mut self, op: ReplicaOp) -> Result<()>; /// Replace the current list of operations with a new list. - fn set_operations(&mut self, ops: Vec) -> Result<()>; + fn set_operations(&mut self, ops: Vec) -> Result<()>; /// Get the entire working set, with each task UUID at its appropriate (1-based) index. /// Element 0 is always None. diff --git a/taskchampion/src/storage/operation.rs b/taskchampion/src/storage/op.rs similarity index 88% rename from taskchampion/src/storage/operation.rs rename to taskchampion/src/storage/op.rs index ed9a5182d..0ab9e1104 100644 --- a/taskchampion/src/storage/operation.rs +++ b/taskchampion/src/storage/op.rs @@ -2,9 +2,10 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -/// An Operation defines a single change to the task database +/// A ReplicaOp defines a single change to the task database, as stored locally in the replica. +/// This contains additional information not included in SyncOp. #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum Operation { +pub enum ReplicaOp { /// Create a new task. /// /// On application, if the task already exists, the operation does nothing. @@ -27,9 +28,9 @@ pub enum Operation { }, } -use Operation::*; +use ReplicaOp::*; -impl Operation { +impl ReplicaOp { // Transform takes two operations A and B that happened concurrently and produces two // operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This // function is used to serialize operations in a process similar to a Git "rebase". @@ -53,9 +54,9 @@ impl Operation { // reached different states, to return to the same state by applying op2' and op1', // respectively. pub fn transform( - operation1: Operation, - operation2: Operation, - ) -> (Option, Option) { + operation1: ReplicaOp, + operation2: ReplicaOp, + ) -> (Option, Option) { match (&operation1, &operation2) { // Two creations or deletions of the same uuid reach the same state, so there's no need // for any further operations to bring the state together. @@ -135,13 +136,13 @@ mod test { // thoroughly, so this testing is light. fn test_transform( - setup: Option, - o1: Operation, - o2: Operation, - exp1p: Option, - exp2p: Option, + setup: Option, + o1: ReplicaOp, + o2: ReplicaOp, + exp1p: Option, + exp2p: Option, ) { - let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); + let (o1p, o2p) = ReplicaOp::transform(o1.clone(), o2.clone()); assert_eq!((&o1p, &o2p), (&exp1p, &exp2p)); // check that the two operation sequences have the same effect, enforcing the invariant of @@ -349,12 +350,12 @@ mod test { ] } - fn operation_strategy() -> impl Strategy { + fn operation_strategy() -> impl Strategy { prop_oneof![ - uuid_strategy().prop_map(|uuid| Operation::Create { uuid }), - uuid_strategy().prop_map(|uuid| Operation::Delete { uuid }), + uuid_strategy().prop_map(|uuid| ReplicaOp::Create { uuid }), + uuid_strategy().prop_map(|uuid| ReplicaOp::Delete { uuid }), (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { - Operation::Update { + ReplicaOp::Update { uuid, property, value: Some("true".into()), @@ -372,30 +373,30 @@ mod test { // check that the two operation sequences have the same effect, enforcing the invariant of // the transform function. fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { - let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); + let (o1p, o2p) = ReplicaOp::transform(o1.clone(), o2.clone()); let mut db1 = TaskDb::new(Box::new(InMemoryStorage::new())); let mut db2 = TaskDb::new(Box::new(InMemoryStorage::new())); // Ensure that any expected tasks already exist - if let Operation::Update{ ref uuid, .. } = o1 { - let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); - let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); + if let ReplicaOp::Update{ ref uuid, .. } = o1 { + let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()}); + let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()}); } - if let Operation::Update{ ref uuid, .. } = o2 { - let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); - let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); + if let ReplicaOp::Update{ ref uuid, .. } = o2 { + let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()}); + let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()}); } - if let Operation::Delete{ ref uuid } = o1 { - let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); - let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); + if let ReplicaOp::Delete{ ref uuid } = o1 { + let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()}); + let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()}); } - if let Operation::Delete{ ref uuid } = o2 { - let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); - let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); + if let ReplicaOp::Delete{ ref uuid } = o2 { + let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()}); + let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()}); } // if applying the initial operations fail, that indicates the operation was invalid diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 86525d0ca..bf5620af5 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -1,4 +1,4 @@ -use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; +use crate::storage::{ReplicaOp, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use anyhow::Context; use rusqlite::types::{FromSql, ToSql}; use rusqlite::{params, Connection, OptionalExtension}; @@ -52,17 +52,17 @@ impl ToSql for StoredTaskMap { } } -/// Stores [`Operation`] in SQLite -impl FromSql for Operation { +/// Stores [`ReplicaOp`] in SQLite +impl FromSql for ReplicaOp { fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { - let o: Operation = serde_json::from_str(value.as_str()?) + let o: ReplicaOp = serde_json::from_str(value.as_str()?) .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; Ok(o) } } -/// Parsers Operation stored as JSON in string column -impl ToSql for Operation { +/// Parses ReplicaOp stored as JSON in string column +impl ToSql for ReplicaOp { fn to_sql(&self) -> rusqlite::Result> { let s = serde_json::to_string(&self) .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; @@ -241,12 +241,12 @@ impl<'t> StorageTxn for Txn<'t> { Ok(()) } - fn operations(&mut self) -> anyhow::Result> { + fn operations(&mut self) -> anyhow::Result> { let t = self.get_txn()?; let mut q = t.prepare("SELECT data FROM operations ORDER BY id ASC")?; let rows = q.query_map([], |r| { - let data: Operation = r.get("data")?; + let data: ReplicaOp = r.get("data")?; Ok(data) })?; @@ -257,7 +257,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(ret) } - fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { + fn add_operation(&mut self, op: ReplicaOp) -> anyhow::Result<()> { let t = self.get_txn()?; t.execute("INSERT INTO operations (data) VALUES (?)", params![&op]) @@ -265,7 +265,7 @@ impl<'t> StorageTxn for Txn<'t> { Ok(()) } - fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { + fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { let t = self.get_txn()?; t.execute("DELETE FROM operations", []) .context("Clear all existing operations")?; @@ -611,8 +611,8 @@ mod test { // create some operations { let mut txn = storage.txn()?; - txn.add_operation(Operation::Create { uuid: uuid1 })?; - txn.add_operation(Operation::Create { uuid: uuid2 })?; + txn.add_operation(ReplicaOp::Create { uuid: uuid1 })?; + txn.add_operation(ReplicaOp::Create { uuid: uuid2 })?; txn.commit()?; } @@ -623,8 +623,8 @@ mod test { assert_eq!( ops, vec![ - Operation::Create { uuid: uuid1 }, - Operation::Create { uuid: uuid2 }, + ReplicaOp::Create { uuid: uuid1 }, + ReplicaOp::Create { uuid: uuid2 }, ] ); } @@ -633,8 +633,8 @@ mod test { { let mut txn = storage.txn()?; txn.set_operations(vec![ - Operation::Delete { uuid: uuid2 }, - Operation::Delete { uuid: uuid1 }, + ReplicaOp::Delete { uuid: uuid2 }, + ReplicaOp::Delete { uuid: uuid1 }, ])?; txn.commit()?; } @@ -642,8 +642,8 @@ mod test { // create some more operations (to test adding operations after clearing) { let mut txn = storage.txn()?; - txn.add_operation(Operation::Create { uuid: uuid3 })?; - txn.add_operation(Operation::Delete { uuid: uuid3 })?; + txn.add_operation(ReplicaOp::Create { uuid: uuid3 })?; + txn.add_operation(ReplicaOp::Delete { uuid: uuid3 })?; txn.commit()?; } @@ -654,10 +654,10 @@ mod test { assert_eq!( ops, vec![ - Operation::Delete { uuid: uuid2 }, - Operation::Delete { uuid: uuid1 }, - Operation::Create { uuid: uuid3 }, - Operation::Delete { uuid: uuid3 }, + ReplicaOp::Delete { uuid: uuid2 }, + ReplicaOp::Delete { uuid: uuid1 }, + ReplicaOp::Create { uuid: uuid3 }, + ReplicaOp::Delete { uuid: uuid3 }, ] ); } diff --git a/taskchampion/src/taskdb/mod.rs b/taskchampion/src/taskdb/mod.rs index 026e82698..bd886c29b 100644 --- a/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -1,5 +1,5 @@ use crate::server::{Server, SyncOp}; -use crate::storage::{Operation, Storage, TaskMap}; +use crate::storage::{ReplicaOp, Storage, TaskMap}; use uuid::Uuid; mod ops; @@ -31,7 +31,7 @@ impl TaskDb { /// Apply an operation to the TaskDb. Aside from synchronization operations, this is the only way /// to modify the TaskDb. In cases where an operation does not make sense, this function will do /// nothing and return an error (but leave the TaskDb in a consistent state). - pub fn apply(&mut self, op: Operation) -> anyhow::Result<()> { + pub fn apply(&mut self, op: ReplicaOp) -> anyhow::Result<()> { // TODO: differentiate error types here? let mut txn = self.storage.txn()?; if let err @ Err(_) = ops::apply_op(txn.as_mut(), &op) { @@ -46,14 +46,14 @@ impl TaskDb { pub fn apply_sync_tmp(&mut self, op: SyncOp) -> anyhow::Result<()> { // create an op from SyncOp let op = match op { - SyncOp::Create { uuid } => Operation::Create { uuid }, - SyncOp::Delete { uuid } => Operation::Delete { uuid }, + SyncOp::Create { uuid } => ReplicaOp::Create { uuid }, + SyncOp::Delete { uuid } => ReplicaOp::Delete { uuid }, SyncOp::Update { uuid, property, value, timestamp, - } => Operation::Update { + } => ReplicaOp::Update { uuid, property, value, @@ -158,7 +158,7 @@ impl TaskDb { } #[cfg(test)] - pub(crate) fn operations(&mut self) -> Vec { + pub(crate) fn operations(&mut self) -> Vec { let mut txn = self.storage.txn().unwrap(); txn.operations() .unwrap() @@ -184,7 +184,7 @@ mod tests { // operations; more detailed tests are in the `ops` module. let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op = Operation::Create { uuid }; + let op = ReplicaOp::Create { uuid }; db.apply(op.clone()).unwrap(); assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]); @@ -197,7 +197,7 @@ mod tests { #[derive(Debug)] enum Action { - Op(Operation), + Op(ReplicaOp), Sync, } @@ -209,14 +209,14 @@ mod tests { .chunks(2) .map(|action_on| { let action = match action_on[0] { - b'C' => Action::Op(Operation::Create { uuid }), - b'U' => Action::Op(Operation::Update { + b'C' => Action::Op(ReplicaOp::Create { uuid }), + b'U' => Action::Op(ReplicaOp::Update { uuid, property: "title".into(), value: Some("foo".into()), timestamp: Utc::now(), }), - b'D' => Action::Op(Operation::Delete { uuid }), + b'D' => Action::Op(ReplicaOp::Delete { uuid }), b'S' => Action::Sync, _ => unreachable!(), }; diff --git a/taskchampion/src/taskdb/ops.rs b/taskchampion/src/taskdb/ops.rs index 7e23d04ce..45c66cb0c 100644 --- a/taskchampion/src/taskdb/ops.rs +++ b/taskchampion/src/taskdb/ops.rs @@ -1,20 +1,20 @@ use crate::errors::Error; -use crate::storage::{Operation, StorageTxn}; +use crate::storage::{ReplicaOp, StorageTxn}; -pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &Operation) -> anyhow::Result<()> { +pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &ReplicaOp) -> anyhow::Result<()> { match op { - Operation::Create { uuid } => { + ReplicaOp::Create { uuid } => { // insert if the task does not already exist if !txn.create_task(*uuid)? { return Err(Error::Database(format!("Task {} already exists", uuid)).into()); } } - Operation::Delete { ref uuid } => { + ReplicaOp::Delete { ref uuid } => { if !txn.delete_task(*uuid)? { return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); } } - Operation::Update { + ReplicaOp::Update { ref uuid, ref property, ref value, @@ -49,7 +49,7 @@ mod tests { fn test_apply_create() -> anyhow::Result<()> { let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op = Operation::Create { uuid }; + let op = ReplicaOp::Create { uuid }; { let mut txn = db.storage.txn()?; @@ -65,7 +65,7 @@ mod tests { fn test_apply_create_exists() -> anyhow::Result<()> { let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op = Operation::Create { uuid }; + let op = ReplicaOp::Create { uuid }; { let mut txn = db.storage.txn()?; apply_op(txn.as_mut(), &op)?; @@ -86,7 +86,7 @@ mod tests { fn test_apply_create_update() -> anyhow::Result<()> { let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op1 = Operation::Create { uuid }; + let op1 = ReplicaOp::Create { uuid }; { let mut txn = db.storage.txn()?; @@ -94,7 +94,7 @@ mod tests { txn.commit()?; } - let op2 = Operation::Update { + let op2 = ReplicaOp::Update { uuid, property: String::from("title"), value: Some("my task".into()), @@ -118,14 +118,14 @@ mod tests { fn test_apply_create_update_delete_prop() -> anyhow::Result<()> { let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op1 = Operation::Create { uuid }; + let op1 = ReplicaOp::Create { uuid }; { let mut txn = db.storage.txn()?; apply_op(txn.as_mut(), &op1)?; txn.commit()?; } - let op2 = Operation::Update { + let op2 = ReplicaOp::Update { uuid, property: String::from("title"), value: Some("my task".into()), @@ -137,7 +137,7 @@ mod tests { txn.commit()?; } - let op3 = Operation::Update { + let op3 = ReplicaOp::Update { uuid, property: String::from("priority"), value: Some("H".into()), @@ -149,7 +149,7 @@ mod tests { txn.commit()?; } - let op4 = Operation::Update { + let op4 = ReplicaOp::Update { uuid, property: String::from("title"), value: None, @@ -177,7 +177,7 @@ mod tests { fn test_apply_update_does_not_exist() -> anyhow::Result<()> { let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op = Operation::Update { + let op = ReplicaOp::Update { uuid, property: String::from("title"), value: Some("my task".into()), @@ -199,8 +199,8 @@ mod tests { fn test_apply_create_delete() -> anyhow::Result<()> { let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op1 = Operation::Create { uuid }; - let op2 = Operation::Delete { uuid }; + let op1 = ReplicaOp::Create { uuid }; + let op2 = ReplicaOp::Delete { uuid }; { let mut txn = db.storage.txn()?; @@ -218,7 +218,7 @@ mod tests { fn test_apply_delete_not_present() -> anyhow::Result<()> { let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op = Operation::Delete { uuid }; + let op = ReplicaOp::Delete { uuid }; { let mut txn = db.storage.txn()?; assert_eq!( diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index 7ce1d76ce..f5806656a 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -1,6 +1,6 @@ use super::{ops, snapshot}; use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency}; -use crate::storage::{Operation, StorageTxn}; +use crate::storage::{ReplicaOp, StorageTxn}; use crate::Error; use log::{info, trace, warn}; use serde::{Deserialize, Serialize}; @@ -8,7 +8,7 @@ use std::str; #[derive(Serialize, Deserialize, Debug)] struct Version { - operations: Vec, + operations: Vec, } /// Sync to the given server, pulling remote changes and pushing local changes. @@ -58,7 +58,7 @@ pub(super) fn sync( } } - let operations: Vec = txn.operations()?.to_vec(); + let operations: Vec = txn.operations()?.to_vec(); if operations.is_empty() { info!("no changes to push to server"); // nothing to sync back to the server.. @@ -136,7 +136,7 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu // This is slightly complicated by the fact that the transform function can return None, // indicating no operation is required. If this happens for a local op, we can just omit // it. If it happens for server op, then we must copy the remaining local ops. - let mut local_operations: Vec = txn.operations()?; + let mut local_operations: Vec = txn.operations()?; for server_op in version.operations.drain(..) { trace!( "rebasing local operations onto server operation {:?}", @@ -146,7 +146,7 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu let mut svr_op = Some(server_op); for local_op in local_operations.drain(..) { if let Some(o) = svr_op { - let (new_server_op, new_local_op) = Operation::transform(o, local_op.clone()); + let (new_server_op, new_local_op) = ReplicaOp::transform(o, local_op.clone()); trace!("local operation {:?} -> {:?}", local_op, new_local_op); svr_op = new_server_op; if let Some(o) = new_local_op { @@ -175,7 +175,7 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu mod test { use super::*; use crate::server::test::TestServer; - use crate::storage::{InMemoryStorage, Operation}; + use crate::storage::{InMemoryStorage, ReplicaOp}; use crate::taskdb::{snapshot::SnapshotTasks, TaskDb}; use chrono::Utc; use pretty_assertions::assert_eq; @@ -197,8 +197,8 @@ mod test { // make some changes in parallel to db1 and db2.. let uuid1 = Uuid::new_v4(); - db1.apply(Operation::Create { uuid: uuid1 }).unwrap(); - db1.apply(Operation::Update { + db1.apply(ReplicaOp::Create { uuid: uuid1 }).unwrap(); + db1.apply(ReplicaOp::Update { uuid: uuid1, property: "title".into(), value: Some("my first task".into()), @@ -207,8 +207,8 @@ mod test { .unwrap(); let uuid2 = Uuid::new_v4(); - db2.apply(Operation::Create { uuid: uuid2 }).unwrap(); - db2.apply(Operation::Update { + db2.apply(ReplicaOp::Create { uuid: uuid2 }).unwrap(); + db2.apply(ReplicaOp::Update { uuid: uuid2, property: "title".into(), value: Some("my second task".into()), @@ -223,14 +223,14 @@ mod test { assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // now make updates to the same task on both sides - db1.apply(Operation::Update { + db1.apply(ReplicaOp::Update { uuid: uuid2, property: "priority".into(), value: Some("H".into()), timestamp: Utc::now(), }) .unwrap(); - db2.apply(Operation::Update { + db2.apply(ReplicaOp::Update { uuid: uuid2, property: "project".into(), value: Some("personal".into()), @@ -259,8 +259,8 @@ mod test { // create and update a task.. let uuid = Uuid::new_v4(); - db1.apply(Operation::Create { uuid }).unwrap(); - db1.apply(Operation::Update { + db1.apply(ReplicaOp::Create { uuid }).unwrap(); + db1.apply(ReplicaOp::Update { uuid, property: "title".into(), value: Some("my first task".into()), @@ -275,9 +275,9 @@ mod test { assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // delete and re-create the task on db1 - db1.apply(Operation::Delete { uuid }).unwrap(); - db1.apply(Operation::Create { uuid }).unwrap(); - db1.apply(Operation::Update { + db1.apply(ReplicaOp::Delete { uuid }).unwrap(); + db1.apply(ReplicaOp::Create { uuid }).unwrap(); + db1.apply(ReplicaOp::Update { uuid, property: "title".into(), value: Some("my second task".into()), @@ -286,7 +286,7 @@ mod test { .unwrap(); // and on db2, update a property of the task - db2.apply(Operation::Update { + db2.apply(ReplicaOp::Update { uuid, property: "project".into(), value: Some("personal".into()), @@ -310,8 +310,8 @@ mod test { let mut db1 = newdb(); let uuid = Uuid::new_v4(); - db1.apply(Operation::Create { uuid })?; - db1.apply(Operation::Update { + db1.apply(ReplicaOp::Create { uuid })?; + db1.apply(ReplicaOp::Update { uuid, property: "title".into(), value: Some("my first task".into()), @@ -332,7 +332,7 @@ mod test { assert_eq!(tasks[0].0, uuid); // update the taskdb and sync again - db1.apply(Operation::Update { + db1.apply(ReplicaOp::Update { uuid, property: "title".into(), value: Some("my first task, updated".into()), @@ -362,7 +362,7 @@ mod test { let mut db1 = newdb(); let uuid = Uuid::new_v4(); - db1.apply(Operation::Create { uuid }).unwrap(); + db1.apply(ReplicaOp::Create { uuid }).unwrap(); test_server.set_snapshot_urgency(SnapshotUrgency::Low); sync(&mut server, db1.storage.txn()?.as_mut(), true).unwrap(); diff --git a/taskchampion/src/taskdb/working_set.rs b/taskchampion/src/taskdb/working_set.rs index d5e0774b0..dc0680ceb 100644 --- a/taskchampion/src/taskdb/working_set.rs +++ b/taskchampion/src/taskdb/working_set.rs @@ -63,7 +63,7 @@ where #[cfg(test)] mod test { use super::*; - use crate::storage::Operation; + use crate::storage::ReplicaOp; use crate::taskdb::TaskDb; use chrono::Utc; use uuid::Uuid; @@ -94,10 +94,10 @@ mod test { // add everything to the TaskDb for uuid in &uuids { - db.apply(Operation::Create { uuid: *uuid })?; + db.apply(ReplicaOp::Create { uuid: *uuid })?; } for i in &[0usize, 1, 4] { - db.apply(Operation::Update { + db.apply(ReplicaOp::Update { uuid: uuids[*i].clone(), property: String::from("status"), value: Some("pending".into()), From fee25fa74284ebe327fdf1cdee8a919f3f5cf7d2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 19 Dec 2021 20:54:48 +0000 Subject: [PATCH 399/548] Apply SyncOps, but keep a list of ReplicaOps This changes a lot of function signatures, but basically: * TaskDB::apply now takes a SyncOp, not a ReplicaOp * Replica::update_task returns a TaskMap --- taskchampion/src/replica.rs | 20 +- taskchampion/src/server/op.rs | 36 +-- taskchampion/src/storage/op.rs | 255 ++------------------ taskchampion/src/taskdb/apply.rs | 313 +++++++++++++++++++++++++ taskchampion/src/taskdb/mod.rs | 64 ++--- taskchampion/src/taskdb/ops.rs | 233 ------------------ taskchampion/src/taskdb/sync.rs | 78 ++++-- taskchampion/src/taskdb/working_set.rs | 6 +- 8 files changed, 440 insertions(+), 565 deletions(-) create mode 100644 taskchampion/src/taskdb/apply.rs delete mode 100644 taskchampion/src/taskdb/ops.rs diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 6bdb756e8..ac39091c8 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -1,6 +1,5 @@ -use crate::errors::Error; -use crate::server::Server; -use crate::storage::{ReplicaOp, Storage, TaskMap}; +use crate::server::{Server, SyncOp}; +use crate::storage::{Storage, TaskMap}; use crate::task::{Status, Task}; use crate::taskdb::TaskDb; use crate::workingset::WorkingSet; @@ -51,12 +50,12 @@ impl Replica { uuid: Uuid, property: S1, value: Option, - ) -> anyhow::Result<()> + ) -> anyhow::Result where S1: Into, S2: Into, { - self.taskdb.apply(ReplicaOp::Update { + self.taskdb.apply(SyncOp::Update { uuid, property: property.into(), value: value.map(|v| v.into()), @@ -100,9 +99,9 @@ impl Replica { /// Create a new task. The task must not already exist. pub fn new_task(&mut self, status: Status, description: String) -> anyhow::Result { let uuid = Uuid::new_v4(); - self.taskdb.apply(ReplicaOp::Create { uuid })?; + let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; trace!("task {} created", uuid); - let mut task = Task::new(uuid, TaskMap::new()).into_mut(self); + let mut task = Task::new(uuid, taskmap).into_mut(self); task.set_description(description)?; task.set_status(status)?; Ok(task.into_immut()) @@ -113,12 +112,7 @@ impl Replica { /// should only occur through expiration. #[allow(dead_code)] fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result<()> { - // check that it already exists; this is a convenience check, as the task may already exist - // when this Create operation is finally sync'd with operations from other replicas - if self.taskdb.get_task(uuid)?.is_none() { - return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); - } - self.taskdb.apply(ReplicaOp::Delete { uuid })?; + self.taskdb.apply(SyncOp::Delete { uuid })?; trace!("task {} deleted", uuid); Ok(()) } diff --git a/taskchampion/src/server/op.rs b/taskchampion/src/server/op.rs index 6f6561ceb..c2906700f 100644 --- a/taskchampion/src/server/op.rs +++ b/taskchampion/src/server/op.rs @@ -215,20 +215,20 @@ mod test { // the transform function. let mut db1 = TaskDb::new_inmemory(); if let Some(ref o) = setup { - db1.apply_sync_tmp(o.clone()).unwrap(); + db1.apply(o.clone()).unwrap(); } - db1.apply_sync_tmp(o1).unwrap(); + db1.apply(o1).unwrap(); if let Some(o) = o2p { - db1.apply_sync_tmp(o).unwrap(); + db1.apply(o).unwrap(); } let mut db2 = TaskDb::new_inmemory(); if let Some(ref o) = setup { - db2.apply_sync_tmp(o.clone()).unwrap(); + db2.apply(o.clone()).unwrap(); } - db2.apply_sync_tmp(o2).unwrap(); + db2.apply(o2).unwrap(); if let Some(o) = o1p { - db2.apply_sync_tmp(o).unwrap(); + db2.apply(o).unwrap(); } assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); @@ -380,39 +380,39 @@ mod test { // Ensure that any expected tasks already exist if let Update{ uuid, .. } = o1 { - let _ = db1.apply_sync_tmp(Create{uuid}); - let _ = db2.apply_sync_tmp(Create{uuid}); + let _ = db1.apply(Create{uuid}); + let _ = db2.apply(Create{uuid}); } if let Update{ uuid, .. } = o2 { - let _ = db1.apply_sync_tmp(Create{uuid}); - let _ = db2.apply_sync_tmp(Create{uuid}); + let _ = db1.apply(Create{uuid}); + let _ = db2.apply(Create{uuid}); } if let Delete{ uuid } = o1 { - let _ = db1.apply_sync_tmp(Create{uuid}); - let _ = db2.apply_sync_tmp(Create{uuid}); + let _ = db1.apply(Create{uuid}); + let _ = db2.apply(Create{uuid}); } if let Delete{ uuid } = o2 { - let _ = db1.apply_sync_tmp(Create{uuid}); - let _ = db2.apply_sync_tmp(Create{uuid}); + let _ = db1.apply(Create{uuid}); + let _ = db2.apply(Create{uuid}); } // if applying the initial operations fail, that indicates the operation was invalid // in the base state, so consider the case successful. - if db1.apply_sync_tmp(o1).is_err() { + if db1.apply(o1).is_err() { return Ok(()); } - if db2.apply_sync_tmp(o2).is_err() { + if db2.apply(o2).is_err() { return Ok(()); } if let Some(o) = o2p { - db1.apply_sync_tmp(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?; + db1.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?; } if let Some(o) = o1p { - db2.apply_sync_tmp(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?; + db2.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?; } assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); } diff --git a/taskchampion/src/storage/op.rs b/taskchampion/src/storage/op.rs index 0ab9e1104..2e0f45b26 100644 --- a/taskchampion/src/storage/op.rs +++ b/taskchampion/src/storage/op.rs @@ -126,163 +126,17 @@ impl ReplicaOp { #[cfg(test)] mod test { use super::*; - use crate::storage::InMemoryStorage; - use crate::taskdb::TaskDb; - use chrono::{Duration, Utc}; + use chrono::Utc; use pretty_assertions::assert_eq; - use proptest::prelude::*; - - // note that `tests/operation_transform_invariant.rs` tests the transform function quite - // thoroughly, so this testing is light. - - fn test_transform( - setup: Option, - o1: ReplicaOp, - o2: ReplicaOp, - exp1p: Option, - exp2p: Option, - ) { - let (o1p, o2p) = ReplicaOp::transform(o1.clone(), o2.clone()); - assert_eq!((&o1p, &o2p), (&exp1p, &exp2p)); - - // check that the two operation sequences have the same effect, enforcing the invariant of - // the transform function. - let mut db1 = TaskDb::new_inmemory(); - if let Some(ref o) = setup { - db1.apply(o.clone()).unwrap(); - } - db1.apply(o1).unwrap(); - if let Some(o) = o2p { - db1.apply(o).unwrap(); - } - - let mut db2 = TaskDb::new_inmemory(); - if let Some(ref o) = setup { - db2.apply(o.clone()).unwrap(); - } - db2.apply(o2).unwrap(); - if let Some(o) = o1p { - db2.apply(o).unwrap(); - } - - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - } - - #[test] - fn test_unrelated_create() { - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - test_transform( - None, - Create { uuid: uuid1 }, - Create { uuid: uuid2 }, - Some(Create { uuid: uuid1 }), - Some(Create { uuid: uuid2 }), - ); - } - - #[test] - fn test_related_updates_different_props() { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - - test_transform( - Some(Create { uuid }), - Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp, - }, - Update { - uuid, - property: "def".into(), - value: Some("false".into()), - timestamp, - }, - Some(Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp, - }), - Some(Update { - uuid, - property: "def".into(), - value: Some("false".into()), - timestamp, - }), - ); - } - - #[test] - fn test_related_updates_same_prop() { - let uuid = Uuid::new_v4(); - let timestamp1 = Utc::now(); - let timestamp2 = timestamp1 + Duration::seconds(10); - - test_transform( - Some(Create { uuid }), - Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp: timestamp1, - }, - Update { - uuid, - property: "abc".into(), - value: Some("false".into()), - timestamp: timestamp2, - }, - None, - Some(Update { - uuid, - property: "abc".into(), - value: Some("false".into()), - timestamp: timestamp2, - }), - ); - } - - #[test] - fn test_related_updates_same_prop_same_time() { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - - test_transform( - Some(Create { uuid }), - Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp, - }, - Update { - uuid, - property: "abc".into(), - value: Some("false".into()), - timestamp, - }, - Some(Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp, - }), - None, - ); - } #[test] fn test_json_create() -> anyhow::Result<()> { let uuid = Uuid::new_v4(); let op = Create { uuid }; - assert_eq!( - serde_json::to_string(&op)?, - format!(r#"{{"Create":{{"uuid":"{}"}}}}"#, uuid), - ); + let json = serde_json::to_string(&op)?; + assert_eq!(json, format!(r#"{{"Create":{{"uuid":"{}"}}}}"#, uuid)); + let deser: ReplicaOp = serde_json::from_str(&json)?; + assert_eq!(deser, op); Ok(()) } @@ -290,10 +144,10 @@ mod test { fn test_json_delete() -> anyhow::Result<()> { let uuid = Uuid::new_v4(); let op = Delete { uuid }; - assert_eq!( - serde_json::to_string(&op)?, - format!(r#"{{"Delete":{{"uuid":"{}"}}}}"#, uuid), - ); + let json = serde_json::to_string(&op)?; + assert_eq!(json, format!(r#"{{"Delete":{{"uuid":"{}"}}}}"#, uuid)); + let deser: ReplicaOp = serde_json::from_str(&json)?; + assert_eq!(deser, op); Ok(()) } @@ -309,13 +163,16 @@ mod test { timestamp, }; + let json = serde_json::to_string(&op)?; assert_eq!( - serde_json::to_string(&op)?, + json, format!( r#"{{"Update":{{"uuid":"{}","property":"abc","value":"false","timestamp":"{:?}"}}}}"#, uuid, timestamp, - ), + ) ); + let deser: ReplicaOp = serde_json::from_str(&json)?; + assert_eq!(deser, op); Ok(()) } @@ -331,90 +188,16 @@ mod test { timestamp, }; + let json = serde_json::to_string(&op)?; assert_eq!( - serde_json::to_string(&op)?, + json, format!( r#"{{"Update":{{"uuid":"{}","property":"abc","value":null,"timestamp":"{:?}"}}}}"#, uuid, timestamp, - ), + ) ); + let deser: ReplicaOp = serde_json::from_str(&json)?; + assert_eq!(deser, op); Ok(()) } - - fn uuid_strategy() -> impl Strategy { - prop_oneof![ - Just(Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap()), - Just(Uuid::parse_str("56e0be07-c61f-494c-a54c-bdcfdd52d2a7").unwrap()), - Just(Uuid::parse_str("4b7ed904-f7b0-4293-8a10-ad452422c7b3").unwrap()), - Just(Uuid::parse_str("9bdd0546-07c8-4e1f-a9bc-9d6299f4773b").unwrap()), - ] - } - - fn operation_strategy() -> impl Strategy { - prop_oneof![ - uuid_strategy().prop_map(|uuid| ReplicaOp::Create { uuid }), - uuid_strategy().prop_map(|uuid| ReplicaOp::Delete { uuid }), - (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { - ReplicaOp::Update { - uuid, - property, - value: Some("true".into()), - timestamp: Utc::now(), - } - }), - ] - } - - proptest! { - #![proptest_config(ProptestConfig { - cases: 1024, .. ProptestConfig::default() - })] - #[test] - // check that the two operation sequences have the same effect, enforcing the invariant of - // the transform function. - fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { - let (o1p, o2p) = ReplicaOp::transform(o1.clone(), o2.clone()); - - let mut db1 = TaskDb::new(Box::new(InMemoryStorage::new())); - let mut db2 = TaskDb::new(Box::new(InMemoryStorage::new())); - - // Ensure that any expected tasks already exist - if let ReplicaOp::Update{ ref uuid, .. } = o1 { - let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()}); - let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()}); - } - - if let ReplicaOp::Update{ ref uuid, .. } = o2 { - let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()}); - let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()}); - } - - if let ReplicaOp::Delete{ ref uuid } = o1 { - let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()}); - let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()}); - } - - if let ReplicaOp::Delete{ ref uuid } = o2 { - let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()}); - let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()}); - } - - // if applying the initial operations fail, that indicates the operation was invalid - // in the base state, so consider the case successful. - if let Err(_) = db1.apply(o1) { - return Ok(()); - } - if let Err(_) = db2.apply(o2) { - return Ok(()); - } - - if let Some(o) = o2p { - db1.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?; - } - if let Some(o) = o1p { - db2.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?; - } - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - } - } } diff --git a/taskchampion/src/taskdb/apply.rs b/taskchampion/src/taskdb/apply.rs new file mode 100644 index 000000000..2824586c4 --- /dev/null +++ b/taskchampion/src/taskdb/apply.rs @@ -0,0 +1,313 @@ +use crate::errors::Error; +use crate::server::SyncOp; +use crate::storage::{ReplicaOp, StorageTxn, TaskMap}; + +/// Apply the given SyncOp to the replica, updating both the task data and adding a +/// ReplicaOp to the list of operations. Returns the TaskMap of the task after the +/// operation has been applied (or an empty TaskMap for Delete). +pub(super) fn apply(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow::Result { + match op { + SyncOp::Create { uuid } => { + let created = txn.create_task(uuid)?; + if created { + txn.add_operation(ReplicaOp::Create { uuid })?; + txn.commit()?; + Ok(TaskMap::new()) + } else { + // TODO: differentiate error types here? + Err(Error::Database(format!("Task {} already exists", uuid)).into()) + } + } + SyncOp::Delete { uuid } => { + let task = txn.get_task(uuid)?; + // (we'll need _task in the next commit) + if let Some(_task) = task { + txn.delete_task(uuid)?; + txn.add_operation(ReplicaOp::Delete { uuid })?; + txn.commit()?; + Ok(TaskMap::new()) + } else { + Err(Error::Database(format!("Task {} does not exist", uuid)).into()) + } + } + SyncOp::Update { + uuid, + property, + value, + timestamp, + } => { + let task = txn.get_task(uuid)?; + if let Some(mut task) = task { + if let Some(ref v) = value { + task.insert(property.clone(), v.clone()); + } else { + task.remove(&property); + } + txn.set_task(uuid, task.clone())?; + txn.add_operation(ReplicaOp::Update { + uuid, + property, + value, + timestamp, + })?; + txn.commit()?; + Ok(task) + } else { + Err(Error::Database(format!("Task {} does not exist", uuid)).into()) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::taskdb::TaskDb; + use chrono::Utc; + use pretty_assertions::assert_eq; + use std::collections::HashMap; + use uuid::Uuid; + + #[test] + fn test_apply_create() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op = SyncOp::Create { uuid }; + + { + let mut txn = db.storage.txn()?; + let taskmap = apply(txn.as_mut(), op)?; + assert_eq!(taskmap.len(), 0); + txn.commit()?; + } + + assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]); + assert_eq!(db.operations(), vec![ReplicaOp::Create { uuid }]); + Ok(()) + } + + #[test] + fn test_apply_create_exists() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op = SyncOp::Create { uuid }; + { + let mut txn = db.storage.txn()?; + let taskmap = apply(txn.as_mut(), op.clone())?; + assert_eq!(taskmap.len(), 0); + assert_eq!( + apply(txn.as_mut(), op).err().unwrap().to_string(), + format!("Task Database Error: Task {} already exists", uuid) + ); + txn.commit()?; + } + + // first op was applied + assert_eq!(db.sorted_tasks(), vec![(uuid, vec![])]); + assert_eq!(db.operations(), vec![ReplicaOp::Create { uuid }]); + Ok(()) + } + + #[test] + fn test_apply_create_update() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let now = Utc::now(); + let op1 = SyncOp::Create { uuid }; + + { + let mut txn = db.storage.txn()?; + let taskmap = apply(txn.as_mut(), op1)?; + assert_eq!(taskmap.len(), 0); + txn.commit()?; + } + + let op2 = SyncOp::Update { + uuid, + property: String::from("title"), + value: Some("my task".into()), + timestamp: now, + }; + { + let mut txn = db.storage.txn()?; + let mut taskmap = apply(txn.as_mut(), op2)?; + assert_eq!( + taskmap.drain().collect::>(), + vec![("title".into(), "my task".into())] + ); + txn.commit()?; + } + + assert_eq!( + db.sorted_tasks(), + vec![(uuid, vec![("title".into(), "my task".into())])] + ); + assert_eq!( + db.operations(), + vec![ + ReplicaOp::Create { uuid }, + ReplicaOp::Update { + uuid, + property: "title".into(), + value: Some("my task".into()), + timestamp: now + } + ] + ); + + Ok(()) + } + + #[test] + fn test_apply_create_update_delete_prop() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let now = Utc::now(); + let op1 = SyncOp::Create { uuid }; + { + let mut txn = db.storage.txn()?; + let taskmap = apply(txn.as_mut(), op1)?; + assert_eq!(taskmap.len(), 0); + txn.commit()?; + } + + let op2 = SyncOp::Update { + uuid, + property: String::from("title"), + value: Some("my task".into()), + timestamp: now, + }; + { + let mut txn = db.storage.txn()?; + let taskmap = apply(txn.as_mut(), op2)?; + assert_eq!(taskmap.get("title"), Some(&"my task".to_owned())); + txn.commit()?; + } + + let op3 = SyncOp::Update { + uuid, + property: String::from("priority"), + value: Some("H".into()), + timestamp: now, + }; + { + let mut txn = db.storage.txn()?; + let taskmap = apply(txn.as_mut(), op3)?; + assert_eq!(taskmap.get("priority"), Some(&"H".to_owned())); + txn.commit()?; + } + + let op4 = SyncOp::Update { + uuid, + property: String::from("title"), + value: None, + timestamp: now, + }; + { + let mut txn = db.storage.txn()?; + let taskmap = apply(txn.as_mut(), op4)?; + assert_eq!(taskmap.get("title"), None); + assert_eq!(taskmap.get("priority"), Some(&"H".to_owned())); + txn.commit()?; + } + + let mut exp = HashMap::new(); + let mut task = HashMap::new(); + task.insert(String::from("priority"), String::from("H")); + exp.insert(uuid, task); + assert_eq!( + db.sorted_tasks(), + vec![(uuid, vec![("priority".into(), "H".into())])] + ); + assert_eq!( + db.operations(), + vec![ + ReplicaOp::Create { uuid }, + ReplicaOp::Update { + uuid, + property: "title".into(), + value: Some("my task".into()), + timestamp: now, + }, + ReplicaOp::Update { + uuid, + property: "priority".into(), + value: Some("H".into()), + timestamp: now, + }, + ReplicaOp::Update { + uuid, + property: "title".into(), + value: None, + timestamp: now, + } + ] + ); + + Ok(()) + } + + #[test] + fn test_apply_update_does_not_exist() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op = SyncOp::Update { + uuid, + property: String::from("title"), + value: Some("my task".into()), + timestamp: Utc::now(), + }; + { + let mut txn = db.storage.txn()?; + assert_eq!( + apply(txn.as_mut(), op).err().unwrap().to_string(), + format!("Task Database Error: Task {} does not exist", uuid) + ); + txn.commit()?; + } + + Ok(()) + } + + #[test] + fn test_apply_create_delete() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op1 = SyncOp::Create { uuid }; + let op2 = SyncOp::Delete { uuid }; + + { + let mut txn = db.storage.txn()?; + let taskmap = apply(txn.as_mut(), op1)?; + assert_eq!(taskmap.len(), 0); + let taskmap = apply(txn.as_mut(), op2)?; + assert_eq!(taskmap.len(), 0); + txn.commit()?; + } + + assert_eq!(db.sorted_tasks(), vec![]); + assert_eq!( + db.operations(), + vec![ReplicaOp::Create { uuid }, ReplicaOp::Delete { uuid },] + ); + + Ok(()) + } + + #[test] + fn test_apply_delete_not_present() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid = Uuid::new_v4(); + let op = SyncOp::Delete { uuid }; + { + let mut txn = db.storage.txn()?; + assert_eq!( + apply(txn.as_mut(), op).err().unwrap().to_string(), + format!("Task Database Error: Task {} does not exist", uuid) + ); + txn.commit()?; + } + + Ok(()) + } +} diff --git a/taskchampion/src/taskdb/mod.rs b/taskchampion/src/taskdb/mod.rs index bd886c29b..69c0a91e3 100644 --- a/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -1,8 +1,11 @@ use crate::server::{Server, SyncOp}; -use crate::storage::{ReplicaOp, Storage, TaskMap}; +use crate::storage::{Storage, TaskMap}; use uuid::Uuid; -mod ops; +#[cfg(test)] +use crate::storage::ReplicaOp; + +mod apply; mod snapshot; mod sync; mod working_set; @@ -28,39 +31,16 @@ impl TaskDb { TaskDb::new(Box::new(InMemoryStorage::new())) } - /// Apply an operation to the TaskDb. Aside from synchronization operations, this is the only way - /// to modify the TaskDb. In cases where an operation does not make sense, this function will do - /// nothing and return an error (but leave the TaskDb in a consistent state). - pub fn apply(&mut self, op: ReplicaOp) -> anyhow::Result<()> { - // TODO: differentiate error types here? + /// Apply an operation to the TaskDb. This will update the set of tasks and add a ReplicaOp to + /// the set of operations in the TaskDb, and return the TaskMap containing the resulting task's + /// properties (or an empty TaskMap for deletion). + /// + /// Aside from synchronization operations, this is the only way to modify the TaskDb. In cases + /// where an operation does not make sense, this function will do nothing and return an error + /// (but leave the TaskDb in a consistent state). + pub fn apply(&mut self, op: SyncOp) -> anyhow::Result { let mut txn = self.storage.txn()?; - if let err @ Err(_) = ops::apply_op(txn.as_mut(), &op) { - return err; - } - txn.add_operation(op)?; - txn.commit()?; - Ok(()) - } - - // temporary - pub fn apply_sync_tmp(&mut self, op: SyncOp) -> anyhow::Result<()> { - // create an op from SyncOp - let op = match op { - SyncOp::Create { uuid } => ReplicaOp::Create { uuid }, - SyncOp::Delete { uuid } => ReplicaOp::Delete { uuid }, - SyncOp::Update { - uuid, - property, - value, - timestamp, - } => ReplicaOp::Update { - uuid, - property, - value, - timestamp, - }, - }; - self.apply(op) + apply::apply(txn.as_mut(), op) } /// Get all tasks. @@ -172,7 +152,7 @@ impl TaskDb { mod tests { use super::*; use crate::server::test::TestServer; - use crate::storage::InMemoryStorage; + use crate::storage::{InMemoryStorage, ReplicaOp}; use chrono::Utc; use pretty_assertions::assert_eq; use proptest::prelude::*; @@ -181,14 +161,14 @@ mod tests { #[test] fn test_apply() { // this verifies that the operation is both applied and included in the list of - // operations; more detailed tests are in the `ops` module. + // operations; more detailed tests are in the `apply` module. let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op = ReplicaOp::Create { uuid }; + let op = SyncOp::Create { uuid }; db.apply(op.clone()).unwrap(); assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]); - assert_eq!(db.operations(), vec![op]); + assert_eq!(db.operations(), vec![ReplicaOp::Create { uuid }]); } fn newdb() -> TaskDb { @@ -197,7 +177,7 @@ mod tests { #[derive(Debug)] enum Action { - Op(ReplicaOp), + Op(SyncOp), Sync, } @@ -209,14 +189,14 @@ mod tests { .chunks(2) .map(|action_on| { let action = match action_on[0] { - b'C' => Action::Op(ReplicaOp::Create { uuid }), - b'U' => Action::Op(ReplicaOp::Update { + b'C' => Action::Op(SyncOp::Create { uuid }), + b'U' => Action::Op(SyncOp::Update { uuid, property: "title".into(), value: Some("foo".into()), timestamp: Utc::now(), }), - b'D' => Action::Op(ReplicaOp::Delete { uuid }), + b'D' => Action::Op(SyncOp::Delete { uuid }), b'S' => Action::Sync, _ => unreachable!(), }; diff --git a/taskchampion/src/taskdb/ops.rs b/taskchampion/src/taskdb/ops.rs deleted file mode 100644 index 45c66cb0c..000000000 --- a/taskchampion/src/taskdb/ops.rs +++ /dev/null @@ -1,233 +0,0 @@ -use crate::errors::Error; -use crate::storage::{ReplicaOp, StorageTxn}; - -pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &ReplicaOp) -> anyhow::Result<()> { - match op { - ReplicaOp::Create { uuid } => { - // insert if the task does not already exist - if !txn.create_task(*uuid)? { - return Err(Error::Database(format!("Task {} already exists", uuid)).into()); - } - } - ReplicaOp::Delete { ref uuid } => { - if !txn.delete_task(*uuid)? { - return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); - } - } - ReplicaOp::Update { - ref uuid, - ref property, - ref value, - timestamp: _, - } => { - // update if this task exists, otherwise ignore - if let Some(mut task) = txn.get_task(*uuid)? { - match value { - Some(ref val) => task.insert(property.to_string(), val.clone()), - None => task.remove(property), - }; - txn.set_task(*uuid, task)?; - } else { - return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); - } - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::taskdb::TaskDb; - use chrono::Utc; - use pretty_assertions::assert_eq; - use std::collections::HashMap; - use uuid::Uuid; - - #[test] - fn test_apply_create() -> anyhow::Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = ReplicaOp::Create { uuid }; - - { - let mut txn = db.storage.txn()?; - apply_op(txn.as_mut(), &op)?; - txn.commit()?; - } - - assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]); - Ok(()) - } - - #[test] - fn test_apply_create_exists() -> anyhow::Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = ReplicaOp::Create { uuid }; - { - let mut txn = db.storage.txn()?; - apply_op(txn.as_mut(), &op)?; - assert_eq!( - apply_op(txn.as_mut(), &op).err().unwrap().to_string(), - format!("Task Database Error: Task {} already exists", uuid) - ); - txn.commit()?; - } - - // first op was applied - assert_eq!(db.sorted_tasks(), vec![(uuid, vec![])]); - - Ok(()) - } - - #[test] - fn test_apply_create_update() -> anyhow::Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op1 = ReplicaOp::Create { uuid }; - - { - let mut txn = db.storage.txn()?; - apply_op(txn.as_mut(), &op1)?; - txn.commit()?; - } - - let op2 = ReplicaOp::Update { - uuid, - property: String::from("title"), - value: Some("my task".into()), - timestamp: Utc::now(), - }; - { - let mut txn = db.storage.txn()?; - apply_op(txn.as_mut(), &op2)?; - txn.commit()?; - } - - assert_eq!( - db.sorted_tasks(), - vec![(uuid, vec![("title".into(), "my task".into())])] - ); - - Ok(()) - } - - #[test] - fn test_apply_create_update_delete_prop() -> anyhow::Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op1 = ReplicaOp::Create { uuid }; - { - let mut txn = db.storage.txn()?; - apply_op(txn.as_mut(), &op1)?; - txn.commit()?; - } - - let op2 = ReplicaOp::Update { - uuid, - property: String::from("title"), - value: Some("my task".into()), - timestamp: Utc::now(), - }; - { - let mut txn = db.storage.txn()?; - apply_op(txn.as_mut(), &op2)?; - txn.commit()?; - } - - let op3 = ReplicaOp::Update { - uuid, - property: String::from("priority"), - value: Some("H".into()), - timestamp: Utc::now(), - }; - { - let mut txn = db.storage.txn()?; - apply_op(txn.as_mut(), &op3)?; - txn.commit()?; - } - - let op4 = ReplicaOp::Update { - uuid, - property: String::from("title"), - value: None, - timestamp: Utc::now(), - }; - { - let mut txn = db.storage.txn()?; - apply_op(txn.as_mut(), &op4)?; - txn.commit()?; - } - - let mut exp = HashMap::new(); - let mut task = HashMap::new(); - task.insert(String::from("priority"), String::from("H")); - exp.insert(uuid, task); - assert_eq!( - db.sorted_tasks(), - vec![(uuid, vec![("priority".into(), "H".into())])] - ); - - Ok(()) - } - - #[test] - fn test_apply_update_does_not_exist() -> anyhow::Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = ReplicaOp::Update { - uuid, - property: String::from("title"), - value: Some("my task".into()), - timestamp: Utc::now(), - }; - { - let mut txn = db.storage.txn()?; - assert_eq!( - apply_op(txn.as_mut(), &op).err().unwrap().to_string(), - format!("Task Database Error: Task {} does not exist", uuid) - ); - txn.commit()?; - } - - Ok(()) - } - - #[test] - fn test_apply_create_delete() -> anyhow::Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op1 = ReplicaOp::Create { uuid }; - let op2 = ReplicaOp::Delete { uuid }; - - { - let mut txn = db.storage.txn()?; - apply_op(txn.as_mut(), &op1)?; - apply_op(txn.as_mut(), &op2)?; - txn.commit()?; - } - - assert_eq!(db.sorted_tasks(), vec![]); - - Ok(()) - } - - #[test] - fn test_apply_delete_not_present() -> anyhow::Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = ReplicaOp::Delete { uuid }; - { - let mut txn = db.storage.txn()?; - assert_eq!( - apply_op(txn.as_mut(), &op).err().unwrap().to_string(), - format!("Task Database Error: Task {} does not exist", uuid) - ); - txn.commit()?; - } - - Ok(()) - } -} diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index f5806656a..944deda57 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -1,4 +1,4 @@ -use super::{ops, snapshot}; +use super::snapshot; use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency}; use crate::storage::{ReplicaOp, StorageTxn}; use crate::Error; @@ -11,6 +11,44 @@ struct Version { operations: Vec, } +/// Apply an op to the TaskDb's set of tasks (without recording it in the list of operations) +pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &ReplicaOp) -> anyhow::Result<()> { + // TODO: it'd be nice if this was integrated into apply() somehow, but that clones TaskMaps + // unnecessariliy + match op { + ReplicaOp::Create { uuid } => { + // insert if the task does not already exist + if !txn.create_task(*uuid)? { + return Err(Error::Database(format!("Task {} already exists", uuid)).into()); + } + } + ReplicaOp::Delete { ref uuid } => { + if !txn.delete_task(*uuid)? { + return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); + } + } + ReplicaOp::Update { + ref uuid, + ref property, + ref value, + timestamp: _, + } => { + // update if this task exists, otherwise ignore + if let Some(mut task) = txn.get_task(*uuid)? { + match value { + Some(ref val) => task.insert(property.to_string(), val.clone()), + None => task.remove(property), + }; + txn.set_task(*uuid, task)?; + } else { + return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); + } + } + } + + Ok(()) +} + /// Sync to the given server, pulling remote changes and pushing local changes. pub(super) fn sync( server: &mut Box, @@ -161,7 +199,7 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu } } if let Some(o) = svr_op { - if let Err(e) = ops::apply_op(txn, &o) { + if let Err(e) = apply_op(txn, &o) { warn!("Invalid operation when syncing: {} (ignored)", e); } } @@ -174,8 +212,8 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu #[cfg(test)] mod test { use super::*; - use crate::server::test::TestServer; - use crate::storage::{InMemoryStorage, ReplicaOp}; + use crate::server::{test::TestServer, SyncOp}; + use crate::storage::InMemoryStorage; use crate::taskdb::{snapshot::SnapshotTasks, TaskDb}; use chrono::Utc; use pretty_assertions::assert_eq; @@ -197,8 +235,8 @@ mod test { // make some changes in parallel to db1 and db2.. let uuid1 = Uuid::new_v4(); - db1.apply(ReplicaOp::Create { uuid: uuid1 }).unwrap(); - db1.apply(ReplicaOp::Update { + db1.apply(SyncOp::Create { uuid: uuid1 }).unwrap(); + db1.apply(SyncOp::Update { uuid: uuid1, property: "title".into(), value: Some("my first task".into()), @@ -207,8 +245,8 @@ mod test { .unwrap(); let uuid2 = Uuid::new_v4(); - db2.apply(ReplicaOp::Create { uuid: uuid2 }).unwrap(); - db2.apply(ReplicaOp::Update { + db2.apply(SyncOp::Create { uuid: uuid2 }).unwrap(); + db2.apply(SyncOp::Update { uuid: uuid2, property: "title".into(), value: Some("my second task".into()), @@ -223,14 +261,14 @@ mod test { assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // now make updates to the same task on both sides - db1.apply(ReplicaOp::Update { + db1.apply(SyncOp::Update { uuid: uuid2, property: "priority".into(), value: Some("H".into()), timestamp: Utc::now(), }) .unwrap(); - db2.apply(ReplicaOp::Update { + db2.apply(SyncOp::Update { uuid: uuid2, property: "project".into(), value: Some("personal".into()), @@ -259,8 +297,8 @@ mod test { // create and update a task.. let uuid = Uuid::new_v4(); - db1.apply(ReplicaOp::Create { uuid }).unwrap(); - db1.apply(ReplicaOp::Update { + db1.apply(SyncOp::Create { uuid }).unwrap(); + db1.apply(SyncOp::Update { uuid, property: "title".into(), value: Some("my first task".into()), @@ -275,9 +313,9 @@ mod test { assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); // delete and re-create the task on db1 - db1.apply(ReplicaOp::Delete { uuid }).unwrap(); - db1.apply(ReplicaOp::Create { uuid }).unwrap(); - db1.apply(ReplicaOp::Update { + db1.apply(SyncOp::Delete { uuid }).unwrap(); + db1.apply(SyncOp::Create { uuid }).unwrap(); + db1.apply(SyncOp::Update { uuid, property: "title".into(), value: Some("my second task".into()), @@ -286,7 +324,7 @@ mod test { .unwrap(); // and on db2, update a property of the task - db2.apply(ReplicaOp::Update { + db2.apply(SyncOp::Update { uuid, property: "project".into(), value: Some("personal".into()), @@ -310,8 +348,8 @@ mod test { let mut db1 = newdb(); let uuid = Uuid::new_v4(); - db1.apply(ReplicaOp::Create { uuid })?; - db1.apply(ReplicaOp::Update { + db1.apply(SyncOp::Create { uuid })?; + db1.apply(SyncOp::Update { uuid, property: "title".into(), value: Some("my first task".into()), @@ -332,7 +370,7 @@ mod test { assert_eq!(tasks[0].0, uuid); // update the taskdb and sync again - db1.apply(ReplicaOp::Update { + db1.apply(SyncOp::Update { uuid, property: "title".into(), value: Some("my first task, updated".into()), @@ -362,7 +400,7 @@ mod test { let mut db1 = newdb(); let uuid = Uuid::new_v4(); - db1.apply(ReplicaOp::Create { uuid }).unwrap(); + db1.apply(SyncOp::Create { uuid }).unwrap(); test_server.set_snapshot_urgency(SnapshotUrgency::Low); sync(&mut server, db1.storage.txn()?.as_mut(), true).unwrap(); diff --git a/taskchampion/src/taskdb/working_set.rs b/taskchampion/src/taskdb/working_set.rs index dc0680ceb..dd9e57f97 100644 --- a/taskchampion/src/taskdb/working_set.rs +++ b/taskchampion/src/taskdb/working_set.rs @@ -63,7 +63,7 @@ where #[cfg(test)] mod test { use super::*; - use crate::storage::ReplicaOp; + use crate::server::SyncOp; use crate::taskdb::TaskDb; use chrono::Utc; use uuid::Uuid; @@ -94,10 +94,10 @@ mod test { // add everything to the TaskDb for uuid in &uuids { - db.apply(ReplicaOp::Create { uuid: *uuid })?; + db.apply(SyncOp::Create { uuid: *uuid })?; } for i in &[0usize, 1, 4] { - db.apply(ReplicaOp::Update { + db.apply(SyncOp::Update { uuid: uuids[*i].clone(), property: String::from("status"), value: Some("pending".into()), From cefdd83d9403ad8b182136c4cd26e993e3407c49 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 19 Dec 2021 21:04:00 +0000 Subject: [PATCH 400/548] Use the latest taskmap when modifying a task The previous logic duplicated the action of applying an operation to the TaskDb with a "manual" application to the Task's local TaskMap. This now uses the updated TaskMap fetched from the DB, which will help to incorporate any other concurrent DB updates. --- taskchampion/src/task/task.rs | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index d38555e19..6cc3c5126 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -260,10 +260,10 @@ impl<'r> TaskMut<'r> { fn lastmod(&mut self) -> anyhow::Result<()> { if !self.updated_modified { let now = format!("{}", Utc::now().timestamp()); - self.replica - .update_task(self.task.uuid, "modified", Some(now.clone()))?; trace!("task {}: set property modified={:?}", self.task.uuid, now); - self.task.taskmap.insert(String::from("modified"), now); + self.task.taskmap = + self.replica + .update_task(self.task.uuid, "modified", Some(now.clone()))?; self.updated_modified = true; } Ok(()) @@ -276,16 +276,17 @@ impl<'r> TaskMut<'r> { ) -> anyhow::Result<()> { let property = property.into(); self.lastmod()?; - self.replica - .update_task(self.task.uuid, &property, value.as_ref())?; - if let Some(v) = value { + if let Some(ref v) = value { trace!("task {}: set property {}={:?}", self.task.uuid, property, v); - self.task.taskmap.insert(property, v); } else { trace!("task {}: remove property {}", self.task.uuid, property); - self.task.taskmap.remove(&property); } + + self.task.taskmap = self + .replica + .update_task(self.task.uuid, &property, value.as_ref())?; + Ok(()) } @@ -294,18 +295,7 @@ impl<'r> TaskMut<'r> { property: &str, value: Option>, ) -> anyhow::Result<()> { - self.lastmod()?; - if let Some(value) = value { - let ts = format!("{}", value.timestamp()); - self.replica - .update_task(self.task.uuid, property, Some(ts.clone()))?; - self.task.taskmap.insert(property.to_string(), ts); - } else { - self.replica - .update_task::<_, &str>(self.task.uuid, property, None)?; - self.task.taskmap.remove(property); - } - Ok(()) + self.set_string(property, value.map(|v| v.timestamp().to_string())) } /// Used by tests to ensure that updates are properly written From 1789344cd0f6254c0e2c3f78d3f19a5e2fc1accc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 19 Dec 2021 21:13:55 +0000 Subject: [PATCH 401/548] refactor sync to use SyncOps --- taskchampion/src/storage/op.rs | 140 ++++++++++++-------------------- taskchampion/src/taskdb/sync.rs | 49 ++++++----- 2 files changed, 79 insertions(+), 110 deletions(-) diff --git a/taskchampion/src/storage/op.rs b/taskchampion/src/storage/op.rs index 2e0f45b26..25225a848 100644 --- a/taskchampion/src/storage/op.rs +++ b/taskchampion/src/storage/op.rs @@ -1,3 +1,4 @@ +use crate::server::SyncOp; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -28,97 +29,23 @@ pub enum ReplicaOp { }, } -use ReplicaOp::*; - impl ReplicaOp { - // Transform takes two operations A and B that happened concurrently and produces two - // operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This - // function is used to serialize operations in a process similar to a Git "rebase". - // - // * - // / \ - // op1 / \ op2 - // / \ - // * * - // - // this function "completes the diamond: - // - // * * - // \ / - // op2' \ / op1' - // \ / - // * - // - // such that applying op2' after op1 has the same effect as applying op1' after op2. This - // allows two different systems which have already applied op1 and op2, respectively, and thus - // reached different states, to return to the same state by applying op2' and op1', - // respectively. - pub fn transform( - operation1: ReplicaOp, - operation2: ReplicaOp, - ) -> (Option, Option) { - match (&operation1, &operation2) { - // Two creations or deletions of the same uuid reach the same state, so there's no need - // for any further operations to bring the state together. - (&Create { uuid: uuid1 }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => (None, None), - (&Delete { uuid: uuid1 }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => (None, None), - - // Given a create and a delete of the same task, one of the operations is invalid: the - // create implies the task does not exist, but the delete implies it exists. Somewhat - // arbitrarily, we prefer the Create - (&Create { uuid: uuid1 }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => { - (Some(operation1), None) - } - (&Delete { uuid: uuid1 }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => { - (None, Some(operation2)) - } - - // And again from an Update and a Create, prefer the Update - (&Update { uuid: uuid1, .. }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => { - (Some(operation1), None) - } - (&Create { uuid: uuid1 }, &Update { uuid: uuid2, .. }) if uuid1 == uuid2 => { - (None, Some(operation2)) - } - - // Given a delete and an update, prefer the delete - (&Update { uuid: uuid1, .. }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => { - (None, Some(operation2)) - } - (&Delete { uuid: uuid1 }, &Update { uuid: uuid2, .. }) if uuid1 == uuid2 => { - (Some(operation1), None) - } - - // Two updates to the same property of the same task might conflict. - ( - &Update { - uuid: ref uuid1, - property: ref property1, - value: ref value1, - timestamp: ref timestamp1, - }, - &Update { - uuid: ref uuid2, - property: ref property2, - value: ref value2, - timestamp: ref timestamp2, - }, - ) if uuid1 == uuid2 && property1 == property2 => { - // if the value is the same, there's no conflict - if value1 == value2 { - (None, None) - } else if timestamp1 < timestamp2 { - // prefer the later modification - (None, Some(operation2)) - } else { - // prefer the later modification or, if the modifications are the same, - // just choose one of them - (Some(operation1), None) - } - } - - // anything else is not a conflict of any sort, so return the operations unchanged - (_, _) => (Some(operation1), Some(operation2)), + /// Convert this operation into a [`SyncOp`]. + pub fn into_sync(self) -> SyncOp { + match self { + Self::Create { uuid } => SyncOp::Create { uuid }, + Self::Delete { uuid } => SyncOp::Delete { uuid }, + Self::Update { + uuid, + property, + value, + timestamp, + } => SyncOp::Update { + uuid, + property, + value, + timestamp, + }, } } } @@ -200,4 +127,37 @@ mod test { assert_eq!(deser, op); Ok(()) } + + #[test] + fn test_into_sync_create() { + let uuid = Uuid::new_v4(); + assert_eq!(Create { uuid }.into_sync(), SyncOp::Create { uuid }); + } + + #[test] + fn test_into_sync_delete() { + let uuid = Uuid::new_v4(); + assert_eq!(Delete { uuid }.into_sync(), SyncOp::Delete { uuid }); + } + + #[test] + fn test_into_sync_update() { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + assert_eq!( + Update { + uuid, + property: "prop".into(), + value: Some("v".into()), + timestamp, + } + .into_sync(), + SyncOp::Update { + uuid, + property: "prop".into(), + value: Some("v".into()), + timestamp, + } + ); + } } diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index 944deda57..b6c92f093 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -1,6 +1,6 @@ use super::snapshot; -use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency}; -use crate::storage::{ReplicaOp, StorageTxn}; +use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency, SyncOp}; +use crate::storage::StorageTxn; use crate::Error; use log::{info, trace, warn}; use serde::{Deserialize, Serialize}; @@ -8,26 +8,26 @@ use std::str; #[derive(Serialize, Deserialize, Debug)] struct Version { - operations: Vec, + operations: Vec, } /// Apply an op to the TaskDb's set of tasks (without recording it in the list of operations) -pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &ReplicaOp) -> anyhow::Result<()> { +pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &SyncOp) -> anyhow::Result<()> { // TODO: it'd be nice if this was integrated into apply() somehow, but that clones TaskMaps // unnecessariliy match op { - ReplicaOp::Create { uuid } => { + SyncOp::Create { uuid } => { // insert if the task does not already exist if !txn.create_task(*uuid)? { return Err(Error::Database(format!("Task {} already exists", uuid)).into()); } } - ReplicaOp::Delete { ref uuid } => { + SyncOp::Delete { ref uuid } => { if !txn.delete_task(*uuid)? { return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); } } - ReplicaOp::Update { + SyncOp::Update { ref uuid, ref property, ref value, @@ -72,6 +72,12 @@ pub(super) fn sync( trace!("beginning sync outer loop"); let mut base_version_id = txn.base_version()?; + let mut local_ops: Vec = txn + .operations()? + .drain(..) + .map(|op| op.into_sync()) + .collect(); + // first pull changes and "rebase" on top of them loop { trace!("beginning sync inner loop"); @@ -86,7 +92,7 @@ pub(super) fn sync( // apply this verison and update base_version in storage info!("applying version {:?} from server", version_id); - apply_version(txn, version)?; + apply_version(txn, &mut local_ops, version)?; txn.set_base_version(version_id)?; base_version_id = version_id; } else { @@ -96,17 +102,18 @@ pub(super) fn sync( } } - let operations: Vec = txn.operations()?.to_vec(); - if operations.is_empty() { + if local_ops.is_empty() { info!("no changes to push to server"); // nothing to sync back to the server.. break; } - trace!("sending {} operations to the server", operations.len()); + trace!("sending {} operations to the server", local_ops.len()); // now make a version of our local changes and push those - let new_version = Version { operations }; + let new_version = Version { + operations: local_ops, + }; let history_segment = serde_json::to_string(&new_version).unwrap().into(); info!("sending new version to server"); let (res, snapshot_urgency) = server.add_version(base_version_id, history_segment)?; @@ -114,7 +121,6 @@ pub(super) fn sync( AddVersionResult::Ok(new_version_id) => { info!("version {:?} received by server", new_version_id); txn.set_base_version(new_version_id)?; - txn.set_operations(vec![])?; // make a snapshot if the server indicates it is urgent enough let base_urgency = if avoid_snapshots { @@ -144,11 +150,16 @@ pub(super) fn sync( } } + txn.set_operations(vec![])?; txn.commit()?; Ok(()) } -fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Result<()> { +fn apply_version( + txn: &mut dyn StorageTxn, + local_ops: &mut Vec, + mut version: Version, +) -> anyhow::Result<()> { // The situation here is that the server has already applied all server operations, and we // have already applied all local operations, so states have diverged by several // operations. We need to figure out what operations to apply locally and on the server in @@ -174,17 +185,16 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu // This is slightly complicated by the fact that the transform function can return None, // indicating no operation is required. If this happens for a local op, we can just omit // it. If it happens for server op, then we must copy the remaining local ops. - let mut local_operations: Vec = txn.operations()?; for server_op in version.operations.drain(..) { trace!( "rebasing local operations onto server operation {:?}", server_op ); - let mut new_local_ops = Vec::with_capacity(local_operations.len()); + let mut new_local_ops = Vec::with_capacity(local_ops.len()); let mut svr_op = Some(server_op); - for local_op in local_operations.drain(..) { + for local_op in local_ops.drain(..) { if let Some(o) = svr_op { - let (new_server_op, new_local_op) = ReplicaOp::transform(o, local_op.clone()); + let (new_server_op, new_local_op) = SyncOp::transform(o, local_op.clone()); trace!("local operation {:?} -> {:?}", local_op, new_local_op); svr_op = new_server_op; if let Some(o) = new_local_op { @@ -203,9 +213,8 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu warn!("Invalid operation when syncing: {} (ignored)", e); } } - local_operations = new_local_ops; + *local_ops = new_local_ops; } - txn.set_operations(local_operations)?; Ok(()) } From 103bbcdf8f2acef459ac97923cde59cc49a96cee Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 19 Dec 2021 21:25:13 +0000 Subject: [PATCH 402/548] Store data necessary to undo ReplicaOps --- taskchampion/src/storage/op.rs | 42 +++++++++++++++++++------ taskchampion/src/storage/sqlite.rs | 30 ++++++++++++++---- taskchampion/src/taskdb/apply.rs | 50 ++++++++++++++++++++++++++---- 3 files changed, 100 insertions(+), 22 deletions(-) diff --git a/taskchampion/src/storage/op.rs b/taskchampion/src/storage/op.rs index 25225a848..a31366ddc 100644 --- a/taskchampion/src/storage/op.rs +++ b/taskchampion/src/storage/op.rs @@ -1,4 +1,5 @@ use crate::server::SyncOp; +use crate::storage::TaskMap; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -9,21 +10,22 @@ use uuid::Uuid; pub enum ReplicaOp { /// Create a new task. /// - /// On application, if the task already exists, the operation does nothing. + /// On undo, the task is deleted. Create { uuid: Uuid }, /// Delete an existing task. /// - /// On application, if the task does not exist, the operation does nothing. - Delete { uuid: Uuid }, + /// On undo, the task's data is restored from old_task. + Delete { uuid: Uuid, old_task: TaskMap }, /// Update an existing task, setting the given property to the given value. If the value is /// None, then the corresponding property is deleted. /// - /// If the given task does not exist, the operation does nothing. + /// On undo, the property is set back to its previous value. Update { uuid: Uuid, property: String, + old_value: Option, value: Option, timestamp: DateTime, }, @@ -34,12 +36,13 @@ impl ReplicaOp { pub fn into_sync(self) -> SyncOp { match self { Self::Create { uuid } => SyncOp::Create { uuid }, - Self::Delete { uuid } => SyncOp::Delete { uuid }, + Self::Delete { uuid, .. } => SyncOp::Delete { uuid }, Self::Update { uuid, property, value, timestamp, + .. } => SyncOp::Update { uuid, property, @@ -56,6 +59,8 @@ mod test { use chrono::Utc; use pretty_assertions::assert_eq; + use ReplicaOp::*; + #[test] fn test_json_create() -> anyhow::Result<()> { let uuid = Uuid::new_v4(); @@ -70,9 +75,16 @@ mod test { #[test] fn test_json_delete() -> anyhow::Result<()> { let uuid = Uuid::new_v4(); - let op = Delete { uuid }; + let old_task = vec![("foo".into(), "bar".into())].drain(..).collect(); + let op = Delete { uuid, old_task }; let json = serde_json::to_string(&op)?; - assert_eq!(json, format!(r#"{{"Delete":{{"uuid":"{}"}}}}"#, uuid)); + assert_eq!( + json, + format!( + r#"{{"Delete":{{"uuid":"{}","old_task":{{"foo":"bar"}}}}}}"#, + uuid + ) + ); let deser: ReplicaOp = serde_json::from_str(&json)?; assert_eq!(deser, op); Ok(()) @@ -86,6 +98,7 @@ mod test { let op = Update { uuid, property: "abc".into(), + old_value: Some("true".into()), value: Some("false".into()), timestamp, }; @@ -94,7 +107,7 @@ mod test { assert_eq!( json, format!( - r#"{{"Update":{{"uuid":"{}","property":"abc","value":"false","timestamp":"{:?}"}}}}"#, + r#"{{"Update":{{"uuid":"{}","property":"abc","old_value":"true","value":"false","timestamp":"{:?}"}}}}"#, uuid, timestamp, ) ); @@ -111,6 +124,7 @@ mod test { let op = Update { uuid, property: "abc".into(), + old_value: None, value: None, timestamp, }; @@ -119,7 +133,7 @@ mod test { assert_eq!( json, format!( - r#"{{"Update":{{"uuid":"{}","property":"abc","value":null,"timestamp":"{:?}"}}}}"#, + r#"{{"Update":{{"uuid":"{}","property":"abc","old_value":null,"value":null,"timestamp":"{:?}"}}}}"#, uuid, timestamp, ) ); @@ -137,7 +151,14 @@ mod test { #[test] fn test_into_sync_delete() { let uuid = Uuid::new_v4(); - assert_eq!(Delete { uuid }.into_sync(), SyncOp::Delete { uuid }); + assert_eq!( + Delete { + uuid, + old_task: TaskMap::new() + } + .into_sync(), + SyncOp::Delete { uuid } + ); } #[test] @@ -148,6 +169,7 @@ mod test { Update { uuid, property: "prop".into(), + old_value: Some("foo".into()), value: Some("v".into()), timestamp, } diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index bf5620af5..1f1fe239a 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -633,8 +633,14 @@ mod test { { let mut txn = storage.txn()?; txn.set_operations(vec![ - ReplicaOp::Delete { uuid: uuid2 }, - ReplicaOp::Delete { uuid: uuid1 }, + ReplicaOp::Delete { + uuid: uuid2, + old_task: TaskMap::new(), + }, + ReplicaOp::Delete { + uuid: uuid1, + old_task: TaskMap::new(), + }, ])?; txn.commit()?; } @@ -643,7 +649,10 @@ mod test { { let mut txn = storage.txn()?; txn.add_operation(ReplicaOp::Create { uuid: uuid3 })?; - txn.add_operation(ReplicaOp::Delete { uuid: uuid3 })?; + txn.add_operation(ReplicaOp::Delete { + uuid: uuid3, + old_task: TaskMap::new(), + })?; txn.commit()?; } @@ -654,10 +663,19 @@ mod test { assert_eq!( ops, vec![ - ReplicaOp::Delete { uuid: uuid2 }, - ReplicaOp::Delete { uuid: uuid1 }, + ReplicaOp::Delete { + uuid: uuid2, + old_task: TaskMap::new() + }, + ReplicaOp::Delete { + uuid: uuid1, + old_task: TaskMap::new() + }, ReplicaOp::Create { uuid: uuid3 }, - ReplicaOp::Delete { uuid: uuid3 }, + ReplicaOp::Delete { + uuid: uuid3, + old_task: TaskMap::new() + }, ] ); } diff --git a/taskchampion/src/taskdb/apply.rs b/taskchampion/src/taskdb/apply.rs index 2824586c4..738a0550b 100644 --- a/taskchampion/src/taskdb/apply.rs +++ b/taskchampion/src/taskdb/apply.rs @@ -20,10 +20,12 @@ pub(super) fn apply(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow::Result { let task = txn.get_task(uuid)?; - // (we'll need _task in the next commit) - if let Some(_task) = task { + if let Some(task) = task { txn.delete_task(uuid)?; - txn.add_operation(ReplicaOp::Delete { uuid })?; + txn.add_operation(ReplicaOp::Delete { + uuid, + old_task: task, + })?; txn.commit()?; Ok(TaskMap::new()) } else { @@ -38,6 +40,7 @@ pub(super) fn apply(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow::Result { let task = txn.get_task(uuid)?; if let Some(mut task) = task { + let old_value = task.get(&property).cloned(); if let Some(ref v) = value { task.insert(property.clone(), v.clone()); } else { @@ -47,6 +50,7 @@ pub(super) fn apply(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow::Result anyhow::Result<()> { let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); - let op1 = SyncOp::Create { uuid }; - let op2 = SyncOp::Delete { uuid }; + let now = Utc::now(); + let op1 = SyncOp::Create { uuid }; { let mut txn = db.storage.txn()?; let taskmap = apply(txn.as_mut(), op1)?; assert_eq!(taskmap.len(), 0); + } + + let op2 = SyncOp::Update { + uuid, + property: String::from("priority"), + value: Some("H".into()), + timestamp: now, + }; + { + let mut txn = db.storage.txn()?; let taskmap = apply(txn.as_mut(), op2)?; + assert_eq!(taskmap.get("priority"), Some(&"H".to_owned())); + txn.commit()?; + } + + let op3 = SyncOp::Delete { uuid }; + { + let mut txn = db.storage.txn()?; + let taskmap = apply(txn.as_mut(), op3)?; assert_eq!(taskmap.len(), 0); txn.commit()?; } assert_eq!(db.sorted_tasks(), vec![]); + let mut old_task = TaskMap::new(); + old_task.insert("priority".into(), "H".into()); assert_eq!( db.operations(), - vec![ReplicaOp::Create { uuid }, ReplicaOp::Delete { uuid },] + vec![ + ReplicaOp::Create { uuid }, + ReplicaOp::Update { + uuid, + property: "priority".into(), + old_value: None, + value: Some("H".into()), + timestamp: now, + }, + ReplicaOp::Delete { uuid, old_task }, + ] ); Ok(()) From 1647ba9144e66ed29f3b1116965cb5e20b3c04f3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 19 Dec 2021 21:37:37 +0000 Subject: [PATCH 403/548] insert UndoPoint appropriately into the replica operations --- taskchampion/src/replica.rs | 105 +++++++++++++++++++++++++++++++- taskchampion/src/storage/op.rs | 30 ++++++--- taskchampion/src/taskdb/mod.rs | 19 ++++-- taskchampion/src/taskdb/sync.rs | 2 +- 4 files changed, 141 insertions(+), 15 deletions(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index ac39091c8..57b1cfa56 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -28,12 +28,14 @@ use uuid::Uuid; /// during the garbage-collection process. pub struct Replica { taskdb: TaskDb, + added_undo_point: bool, } impl Replica { pub fn new(storage: Box) -> Replica { Replica { taskdb: TaskDb::new(storage), + added_undo_point: false, } } @@ -55,6 +57,7 @@ impl Replica { S1: Into, S2: Into, { + self.add_undo_point(false)?; self.taskdb.apply(SyncOp::Update { uuid, property: property.into(), @@ -98,6 +101,7 @@ impl Replica { /// Create a new task. The task must not already exist. pub fn new_task(&mut self, status: Status, description: String) -> anyhow::Result { + self.add_undo_point(false)?; let uuid = Uuid::new_v4(); let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; trace!("task {} created", uuid); @@ -112,6 +116,7 @@ impl Replica { /// should only occur through expiration. #[allow(dead_code)] fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result<()> { + self.add_undo_point(false)?; self.taskdb.apply(SyncOp::Delete { uuid })?; trace!("task {} deleted", uuid); Ok(()) @@ -150,11 +155,24 @@ impl Replica { .rebuild_working_set(|t| t.get("status") == Some(&pending), renumber)?; Ok(()) } + + /// 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 laready 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. + pub fn add_undo_point(&mut self, force: bool) -> anyhow::Result<()> { + if force || !self.added_undo_point { + self.taskdb.add_undo_point()?; + self.added_undo_point = true; + } + Ok(()) + } } #[cfg(test)] mod tests { use super::*; + use crate::storage::ReplicaOp; use crate::task::Status; use pretty_assertions::assert_eq; use uuid::Uuid; @@ -187,10 +205,95 @@ mod tests { assert_eq!(t.get_description(), "past tense"); assert_eq!(t.get_status(), Status::Completed); - // check tha values have changed in storage, too + // check that values have changed in storage, too let t = rep.get_task(t.get_uuid()).unwrap().unwrap(); assert_eq!(t.get_description(), "past tense"); assert_eq!(t.get_status(), Status::Completed); + + // and check for the corresponding operations, cleaning out the timestamps + // and modified properties as these are based on the current time + let now = Utc::now(); + let clean_op = |op: ReplicaOp| { + if let ReplicaOp::Update { + uuid, + property, + mut old_value, + mut value, + .. + } = op + { + if property == "modified" { + if value.is_some() { + value = Some("just-now".into()); + } + if old_value.is_some() { + old_value = Some("just-now".into()); + } + } + ReplicaOp::Update { + uuid, + property, + old_value, + value, + timestamp: now, + } + } else { + op + } + }; + assert_eq!( + rep.taskdb + .operations() + .drain(..) + .map(clean_op) + .collect::>(), + vec![ + ReplicaOp::UndoPoint, + ReplicaOp::Create { uuid: t.get_uuid() }, + ReplicaOp::Update { + uuid: t.get_uuid(), + property: "modified".into(), + old_value: None, + value: Some("just-now".into()), + timestamp: now, + }, + ReplicaOp::Update { + uuid: t.get_uuid(), + property: "description".into(), + old_value: None, + value: Some("a task".into()), + timestamp: now, + }, + ReplicaOp::Update { + uuid: t.get_uuid(), + property: "status".into(), + old_value: None, + value: Some("P".into()), + timestamp: now, + }, + ReplicaOp::Update { + uuid: t.get_uuid(), + property: "modified".into(), + old_value: Some("just-now".into()), + value: Some("just-now".into()), + timestamp: now, + }, + ReplicaOp::Update { + uuid: t.get_uuid(), + property: "description".into(), + old_value: Some("a task".into()), + value: Some("past tense".into()), + timestamp: now, + }, + ReplicaOp::Update { + uuid: t.get_uuid(), + property: "status".into(), + old_value: Some("P".into()), + value: Some("C".into()), + timestamp: now, + }, + ] + ); } #[test] diff --git a/taskchampion/src/storage/op.rs b/taskchampion/src/storage/op.rs index a31366ddc..283c876d9 100644 --- a/taskchampion/src/storage/op.rs +++ b/taskchampion/src/storage/op.rs @@ -29,26 +29,33 @@ pub enum ReplicaOp { value: Option, timestamp: DateTime, }, + + /// Mark a point in the operations history to which the user might like to undo. Users + /// typically want to undo more than one operation at a time (for example, most changes update + /// both the `modified` property and some other task property -- the user would like to "undo" + /// both updates at the same time). Applying an UndoPoint does nothing. + UndoPoint, } impl ReplicaOp { /// Convert this operation into a [`SyncOp`]. - pub fn into_sync(self) -> SyncOp { + pub fn into_sync(self) -> Option { match self { - Self::Create { uuid } => SyncOp::Create { uuid }, - Self::Delete { uuid, .. } => SyncOp::Delete { uuid }, + Self::Create { uuid } => Some(SyncOp::Create { uuid }), + Self::Delete { uuid, .. } => Some(SyncOp::Delete { uuid }), Self::Update { uuid, property, value, timestamp, .. - } => SyncOp::Update { + } => Some(SyncOp::Update { uuid, property, value, timestamp, - }, + }), + Self::UndoPoint => None, } } } @@ -145,7 +152,7 @@ mod test { #[test] fn test_into_sync_create() { let uuid = Uuid::new_v4(); - assert_eq!(Create { uuid }.into_sync(), SyncOp::Create { uuid }); + assert_eq!(Create { uuid }.into_sync(), Some(SyncOp::Create { uuid })); } #[test] @@ -157,7 +164,7 @@ mod test { old_task: TaskMap::new() } .into_sync(), - SyncOp::Delete { uuid } + Some(SyncOp::Delete { uuid }) ); } @@ -174,12 +181,17 @@ mod test { timestamp, } .into_sync(), - SyncOp::Update { + Some(SyncOp::Update { uuid, property: "prop".into(), value: Some("v".into()), timestamp, - } + }) ); } + + #[test] + fn test_into_sync_undo_point() { + assert_eq!(UndoPoint.into_sync(), None); + } } diff --git a/taskchampion/src/taskdb/mod.rs b/taskchampion/src/taskdb/mod.rs index 69c0a91e3..4861746e5 100644 --- a/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -1,10 +1,7 @@ use crate::server::{Server, SyncOp}; -use crate::storage::{Storage, TaskMap}; +use crate::storage::{ReplicaOp, Storage, TaskMap}; use uuid::Uuid; -#[cfg(test)] -use crate::storage::ReplicaOp; - mod apply; mod snapshot; mod sync; @@ -43,6 +40,13 @@ impl TaskDb { apply::apply(txn.as_mut(), op) } + /// Add an UndoPoint operation to the list of replica operations. + pub fn add_undo_point(&mut self) -> anyhow::Result<()> { + let mut txn = self.storage.txn()?; + txn.add_operation(ReplicaOp::UndoPoint)?; + txn.commit() + } + /// Get all tasks. pub fn all_tasks(&mut self) -> anyhow::Result> { let mut txn = self.storage.txn()?; @@ -171,6 +175,13 @@ mod tests { assert_eq!(db.operations(), vec![ReplicaOp::Create { uuid }]); } + #[test] + fn test_add_undo_point() { + let mut db = TaskDb::new_inmemory(); + db.add_undo_point().unwrap(); + assert_eq!(db.operations(), vec![ReplicaOp::UndoPoint]); + } + fn newdb() -> TaskDb { TaskDb::new(Box::new(InMemoryStorage::new())) } diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index b6c92f093..6ea11cc6f 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -75,7 +75,7 @@ pub(super) fn sync( let mut local_ops: Vec = txn .operations()? .drain(..) - .map(|op| op.into_sync()) + .filter_map(|op| op.into_sync()) .collect(); // first pull changes and "rebase" on top of them From 2456012ed6a63264f3d6810026e1958368848026 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 20 Dec 2021 16:16:25 +0000 Subject: [PATCH 404/548] Fix application of modifications during 'ta add' --- cli/src/invocation/cmd/add.rs | 36 ++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index 47df033bc..1957e6dfc 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -1,18 +1,24 @@ use crate::argparse::{DescriptionMod, Modification}; +use crate::invocation::apply_modification; use taskchampion::{Replica, Status}; use termcolor::WriteColor; pub(crate) fn execute( w: &mut W, replica: &mut Replica, - modification: Modification, + mut modification: Modification, ) -> Result<(), crate::Error> { + // extract the description from the modification to handle it specially let description = match modification.description { DescriptionMod::Set(ref s) => s.clone(), _ => "(no description)".to_owned(), }; - let t = replica.new_task(Status::Pending, description).unwrap(); - writeln!(w, "added task {}", t.get_uuid())?; + modification.description = DescriptionMod::None; + + let task = replica.new_task(Status::Pending, description).unwrap(); + let mut task = task.into_mut(replica); + apply_modification(&mut task, &modification)?; + writeln!(w, "added task {}", task.get_uuid())?; Ok(()) } @@ -43,4 +49,28 @@ mod test { assert_eq!(w.into_string(), format!("added task {}\n", task.get_uuid())); } + + #[test] + fn test_add_with_tags() { + let mut w = test_writer(); + let mut replica = test_replica(); + let modification = Modification { + description: DescriptionMod::Set(s!("my description")), + add_tags: vec![tag!("tag1")].drain(..).collect(), + ..Default::default() + }; + execute(&mut w, &mut replica, modification).unwrap(); + + // check that the task appeared.. + let working_set = replica.working_set().unwrap(); + let task = replica + .get_task(working_set.by_index(1).unwrap()) + .unwrap() + .unwrap(); + assert_eq!(task.get_description(), "my description"); + assert_eq!(task.get_status(), Status::Pending); + assert!(task.has_tag(&tag!("tag1"))); + + assert_eq!(w.into_string(), format!("added task {}\n", task.get_uuid())); + } } From 4fa1f9c6bc616c0df1cbd688925833b4e559623a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 20 Dec 2021 16:35:55 +0000 Subject: [PATCH 405/548] fix a few lints in clippy 1.57 --- cli/src/settings/report.rs | 2 +- cli/src/usage.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/settings/report.rs b/cli/src/settings/report.rs index 88ca1a3cb..3cc5e3e2c 100644 --- a/cli/src/settings/report.rs +++ b/cli/src/settings/report.rs @@ -130,7 +130,7 @@ impl TryFrom<&toml::Value> for Report { .map(|(i, v)| { v.as_str() .ok_or_else(|| anyhow!(".filter[{}]: not a string", i)) - .and_then(|s| Condition::parse_str(s)) + .and_then(Condition::parse_str) .map_err(|e| anyhow!(".filter[{}]: {}", i, e)) }) .collect::>>()?, diff --git a/cli/src/usage.rs b/cli/src/usage.rs index b3f688909..d341c3b52 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -274,6 +274,7 @@ impl Modification { /// Usage documentation for a report property (which may be used for sorting, as a column, or /// both). +#[allow(dead_code)] #[derive(Debug, Default)] pub(crate) struct ReportProperty { /// Name of the property From 9d93928996b01280da4366baec822683956f84ef Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 21 Dec 2021 00:43:26 +0000 Subject: [PATCH 406/548] support undo operations --- taskchampion/src/replica.rs | 6 ++ taskchampion/src/storage/op.rs | 86 +++++++++++++++++++++++ taskchampion/src/taskdb/apply.rs | 78 +++++++++++++++++---- taskchampion/src/taskdb/mod.rs | 10 ++- taskchampion/src/taskdb/sync.rs | 42 +----------- taskchampion/src/taskdb/undo.rs | 114 +++++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+), 56 deletions(-) create mode 100644 taskchampion/src/taskdb/undo.rs diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 57b1cfa56..271a39436 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -145,6 +145,12 @@ impl Replica { Ok(()) } + /// Undo local operations until the most recent UndoPoint, returning false if there are no + /// local operations to undo. + pub fn undo(&mut self) -> anyhow::Result { + self.taskdb.undo() + } + /// 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 diff --git a/taskchampion/src/storage/op.rs b/taskchampion/src/storage/op.rs index 283c876d9..bc20d99e9 100644 --- a/taskchampion/src/storage/op.rs +++ b/taskchampion/src/storage/op.rs @@ -58,11 +58,47 @@ impl ReplicaOp { Self::UndoPoint => None, } } + + /// Generate a sequence of SyncOp's to reverse the effects of this ReplicaOp. + pub fn reverse_ops(self) -> Vec { + match self { + Self::Create { uuid } => vec![SyncOp::Delete { uuid }], + Self::Delete { uuid, mut old_task } => { + let mut ops = vec![SyncOp::Create { uuid }]; + // We don't have the original update timestamp, but it doesn't + // matter because this SyncOp will just be applied and discarded. + let timestamp = Utc::now(); + for (property, value) in old_task.drain() { + ops.push(SyncOp::Update { + uuid, + property, + value: Some(value), + timestamp, + }); + } + ops + } + Self::Update { + uuid, + property, + old_value, + timestamp, + .. + } => vec![SyncOp::Update { + uuid, + property, + value: old_value, + timestamp, + }], + Self::UndoPoint => vec![], + } + } } #[cfg(test)] mod test { use super::*; + use crate::storage::taskmap_with; use chrono::Utc; use pretty_assertions::assert_eq; @@ -194,4 +230,54 @@ mod test { fn test_into_sync_undo_point() { assert_eq!(UndoPoint.into_sync(), None); } + + #[test] + fn test_reverse_create() { + let uuid = Uuid::new_v4(); + assert_eq!(Create { uuid }.reverse_ops(), vec![SyncOp::Delete { uuid }]); + } + + #[test] + fn test_reverse_delete() { + let uuid = Uuid::new_v4(); + let reversed = Delete { + uuid, + old_task: taskmap_with(vec![("prop1".into(), "v1".into())]), + } + .reverse_ops(); + assert_eq!(reversed.len(), 2); + assert_eq!(reversed[0], SyncOp::Create { uuid }); + assert!(matches!( + &reversed[1], + SyncOp::Update { uuid: u, property: p, value: Some(v), ..} + if u == &uuid && p == "prop1" && v == "v1" + )); + } + + #[test] + fn test_reverse_update() { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + assert_eq!( + Update { + uuid, + property: "prop".into(), + old_value: Some("foo".into()), + value: Some("v".into()), + timestamp, + } + .reverse_ops(), + vec![SyncOp::Update { + uuid, + property: "prop".into(), + value: Some("foo".into()), + timestamp, + }] + ); + } + + #[test] + fn test_reverse_undo_point() { + assert_eq!(UndoPoint.reverse_ops(), vec![]); + } } diff --git a/taskchampion/src/taskdb/apply.rs b/taskchampion/src/taskdb/apply.rs index 738a0550b..1be3864c9 100644 --- a/taskchampion/src/taskdb/apply.rs +++ b/taskchampion/src/taskdb/apply.rs @@ -5,7 +5,7 @@ use crate::storage::{ReplicaOp, StorageTxn, TaskMap}; /// Apply the given SyncOp to the replica, updating both the task data and adding a /// ReplicaOp to the list of operations. Returns the TaskMap of the task after the /// operation has been applied (or an empty TaskMap for Delete). -pub(super) fn apply(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow::Result { +pub(super) fn apply_and_record(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow::Result { match op { SyncOp::Create { uuid } => { let created = txn.create_task(uuid)?; @@ -63,6 +63,45 @@ pub(super) fn apply(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow::Result anyhow::Result<()> { + // TODO: test + // TODO: it'd be nice if this was integrated into apply() somehow, but that clones TaskMaps + // unnecessariliy + match op { + SyncOp::Create { uuid } => { + // insert if the task does not already exist + if !txn.create_task(*uuid)? { + return Err(Error::Database(format!("Task {} already exists", uuid)).into()); + } + } + SyncOp::Delete { ref uuid } => { + if !txn.delete_task(*uuid)? { + return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); + } + } + SyncOp::Update { + ref uuid, + ref property, + ref value, + timestamp: _, + } => { + // update if this task exists, otherwise ignore + if let Some(mut task) = txn.get_task(*uuid)? { + match value { + Some(ref val) => task.insert(property.to_string(), val.clone()), + None => task.remove(property), + }; + txn.set_task(*uuid, task)?; + } else { + return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); + } + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -80,7 +119,7 @@ mod tests { { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op)?; + let taskmap = apply_and_record(txn.as_mut(), op)?; assert_eq!(taskmap.len(), 0); txn.commit()?; } @@ -97,10 +136,13 @@ mod tests { let op = SyncOp::Create { uuid }; { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op.clone())?; + let taskmap = apply_and_record(txn.as_mut(), op.clone())?; assert_eq!(taskmap.len(), 0); assert_eq!( - apply(txn.as_mut(), op).err().unwrap().to_string(), + apply_and_record(txn.as_mut(), op) + .err() + .unwrap() + .to_string(), format!("Task Database Error: Task {} already exists", uuid) ); txn.commit()?; @@ -121,7 +163,7 @@ mod tests { { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op1)?; + let taskmap = apply_and_record(txn.as_mut(), op1)?; assert_eq!(taskmap.len(), 0); txn.commit()?; } @@ -134,7 +176,7 @@ mod tests { }; { let mut txn = db.storage.txn()?; - let mut taskmap = apply(txn.as_mut(), op2)?; + let mut taskmap = apply_and_record(txn.as_mut(), op2)?; assert_eq!( taskmap.drain().collect::>(), vec![("title".into(), "my task".into())] @@ -171,7 +213,7 @@ mod tests { let op1 = SyncOp::Create { uuid }; { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op1)?; + let taskmap = apply_and_record(txn.as_mut(), op1)?; assert_eq!(taskmap.len(), 0); txn.commit()?; } @@ -184,7 +226,7 @@ mod tests { }; { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op2)?; + let taskmap = apply_and_record(txn.as_mut(), op2)?; assert_eq!(taskmap.get("title"), Some(&"my task".to_owned())); txn.commit()?; } @@ -197,7 +239,7 @@ mod tests { }; { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op3)?; + let taskmap = apply_and_record(txn.as_mut(), op3)?; assert_eq!(taskmap.get("priority"), Some(&"H".to_owned())); txn.commit()?; } @@ -210,7 +252,7 @@ mod tests { }; { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op4)?; + let taskmap = apply_and_record(txn.as_mut(), op4)?; assert_eq!(taskmap.get("title"), None); assert_eq!(taskmap.get("priority"), Some(&"H".to_owned())); txn.commit()?; @@ -268,7 +310,10 @@ mod tests { { let mut txn = db.storage.txn()?; assert_eq!( - apply(txn.as_mut(), op).err().unwrap().to_string(), + apply_and_record(txn.as_mut(), op) + .err() + .unwrap() + .to_string(), format!("Task Database Error: Task {} does not exist", uuid) ); txn.commit()?; @@ -286,7 +331,7 @@ mod tests { let op1 = SyncOp::Create { uuid }; { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op1)?; + let taskmap = apply_and_record(txn.as_mut(), op1)?; assert_eq!(taskmap.len(), 0); } @@ -298,7 +343,7 @@ mod tests { }; { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op2)?; + let taskmap = apply_and_record(txn.as_mut(), op2)?; assert_eq!(taskmap.get("priority"), Some(&"H".to_owned())); txn.commit()?; } @@ -306,7 +351,7 @@ mod tests { let op3 = SyncOp::Delete { uuid }; { let mut txn = db.storage.txn()?; - let taskmap = apply(txn.as_mut(), op3)?; + let taskmap = apply_and_record(txn.as_mut(), op3)?; assert_eq!(taskmap.len(), 0); txn.commit()?; } @@ -340,7 +385,10 @@ mod tests { { let mut txn = db.storage.txn()?; assert_eq!( - apply(txn.as_mut(), op).err().unwrap().to_string(), + apply_and_record(txn.as_mut(), op) + .err() + .unwrap() + .to_string(), format!("Task Database Error: Task {} does not exist", uuid) ); txn.commit()?; diff --git a/taskchampion/src/taskdb/mod.rs b/taskchampion/src/taskdb/mod.rs index 4861746e5..7e802313f 100644 --- a/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -5,6 +5,7 @@ use uuid::Uuid; mod apply; mod snapshot; mod sync; +mod undo; mod working_set; /// A TaskDb is the backend for a replica. It manages the storage, operations, synchronization, @@ -37,7 +38,7 @@ impl TaskDb { /// (but leave the TaskDb in a consistent state). pub fn apply(&mut self, op: SyncOp) -> anyhow::Result { let mut txn = self.storage.txn()?; - apply::apply(txn.as_mut(), op) + apply::apply_and_record(txn.as_mut(), op) } /// Add an UndoPoint operation to the list of replica operations. @@ -120,6 +121,13 @@ impl TaskDb { sync::sync(server, txn.as_mut(), avoid_snapshots) } + /// Undo local operations until the most recent UndoPoint, returning false if there are no + /// local operations to undo. + pub fn undo(&mut self) -> anyhow::Result { + let mut txn = self.storage.txn()?; + undo::undo(txn.as_mut()) + } + // functions for supporting tests #[cfg(test)] diff --git a/taskchampion/src/taskdb/sync.rs b/taskchampion/src/taskdb/sync.rs index 6ea11cc6f..d479c6c03 100644 --- a/taskchampion/src/taskdb/sync.rs +++ b/taskchampion/src/taskdb/sync.rs @@ -1,4 +1,4 @@ -use super::snapshot; +use super::{apply, snapshot}; use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency, SyncOp}; use crate::storage::StorageTxn; use crate::Error; @@ -11,44 +11,6 @@ struct Version { operations: Vec, } -/// Apply an op to the TaskDb's set of tasks (without recording it in the list of operations) -pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &SyncOp) -> anyhow::Result<()> { - // TODO: it'd be nice if this was integrated into apply() somehow, but that clones TaskMaps - // unnecessariliy - match op { - SyncOp::Create { uuid } => { - // insert if the task does not already exist - if !txn.create_task(*uuid)? { - return Err(Error::Database(format!("Task {} already exists", uuid)).into()); - } - } - SyncOp::Delete { ref uuid } => { - if !txn.delete_task(*uuid)? { - return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); - } - } - SyncOp::Update { - ref uuid, - ref property, - ref value, - timestamp: _, - } => { - // update if this task exists, otherwise ignore - if let Some(mut task) = txn.get_task(*uuid)? { - match value { - Some(ref val) => task.insert(property.to_string(), val.clone()), - None => task.remove(property), - }; - txn.set_task(*uuid, task)?; - } else { - return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); - } - } - } - - Ok(()) -} - /// Sync to the given server, pulling remote changes and pushing local changes. pub(super) fn sync( server: &mut Box, @@ -209,7 +171,7 @@ fn apply_version( } } if let Some(o) = svr_op { - if let Err(e) = apply_op(txn, &o) { + if let Err(e) = apply::apply_op(txn, &o) { warn!("Invalid operation when syncing: {} (ignored)", e); } } diff --git a/taskchampion/src/taskdb/undo.rs b/taskchampion/src/taskdb/undo.rs new file mode 100644 index 000000000..f7636ebde --- /dev/null +++ b/taskchampion/src/taskdb/undo.rs @@ -0,0 +1,114 @@ +use super::apply; +use crate::storage::{ReplicaOp, StorageTxn}; + +/// Undo local operations until an UndoPoint. +pub(super) fn undo(txn: &mut dyn StorageTxn) -> anyhow::Result { + let mut applied = false; + let mut popped = false; + let mut local_ops = txn.operations()?; + + while let Some(op) = local_ops.pop() { + popped = true; + if op == ReplicaOp::UndoPoint { + break; + } + let rev_ops = op.reverse_ops(); + for op in rev_ops { + apply::apply_op(txn, &op)?; + applied = true; + } + } + + if popped { + txn.set_operations(local_ops)?; + txn.commit()?; + } + + Ok(applied) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::server::SyncOp; + use crate::taskdb::TaskDb; + use chrono::Utc; + use pretty_assertions::assert_eq; + use uuid::Uuid; + + #[test] + fn test_apply_create() -> anyhow::Result<()> { + let mut db = TaskDb::new_inmemory(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + let timestamp = Utc::now(); + + // apply a few ops, capture the DB state, make an undo point, and then apply a few more + // ops. + db.apply(SyncOp::Create { uuid: uuid1 })?; + db.apply(SyncOp::Update { + uuid: uuid1, + property: "prop".into(), + value: Some("v1".into()), + timestamp, + })?; + db.apply(SyncOp::Create { uuid: uuid2 })?; + db.apply(SyncOp::Update { + uuid: uuid2, + property: "prop".into(), + value: Some("v2".into()), + timestamp, + })?; + db.apply(SyncOp::Update { + uuid: uuid2, + property: "prop2".into(), + value: Some("v3".into()), + timestamp, + })?; + + let db_state = db.sorted_tasks(); + + db.add_undo_point()?; + db.apply(SyncOp::Delete { uuid: uuid1 })?; + db.apply(SyncOp::Update { + uuid: uuid2, + property: "prop".into(), + value: None, + timestamp, + })?; + db.apply(SyncOp::Update { + uuid: uuid2, + property: "prop2".into(), + value: Some("new-value".into()), + timestamp, + })?; + + assert_eq!(db.operations().len(), 9); + + { + let mut txn = db.storage.txn()?; + assert!(undo(txn.as_mut())?); + } + + // undo took db back to the snapshot + assert_eq!(db.operations().len(), 5); + assert_eq!(db.sorted_tasks(), db_state); + + { + let mut txn = db.storage.txn()?; + assert!(undo(txn.as_mut())?); + } + + // empty db + assert_eq!(db.operations().len(), 0); + assert_eq!(db.sorted_tasks(), vec![]); + + { + let mut txn = db.storage.txn()?; + // nothing left to undo, so undo() returns false + assert!(!undo(txn.as_mut())?); + } + + Ok(()) + } +} From caa62ba9a0c0fe3f60ff1f896315475c7fb19df5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 21 Dec 2021 01:05:52 +0000 Subject: [PATCH 407/548] add a 'ta undo' subcommand --- cli/src/argparse/subcommand.rs | 34 ++++++++++++++++++++++++++++++++++ cli/src/invocation/cmd/mod.rs | 1 + cli/src/invocation/cmd/undo.rs | 28 ++++++++++++++++++++++++++++ cli/src/invocation/mod.rs | 7 +++++++ 4 files changed, 70 insertions(+) create mode 100644 cli/src/invocation/cmd/undo.rs diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index d06244bec..06cc52490 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -59,6 +59,7 @@ pub(crate) enum Subcommand { /// Basic operations without args Gc, Sync, + Undo, } impl Subcommand { @@ -72,6 +73,7 @@ impl Subcommand { Info::parse, Gc::parse, Sync::parse, + Undo::parse, // This must come last since it accepts arbitrary report names Report::parse, )))(input) @@ -422,6 +424,29 @@ impl Sync { } } +struct Undo; + +impl Undo { + fn parse(input: ArgList) -> IResult { + fn to_subcommand(_: &str) -> Result { + Ok(Subcommand::Undo) + } + map_res(arg_matching(literal("undo")), to_subcommand)(input) + } + + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "undo", + syntax: "undo", + summary: "Undo the latest change made on this replica", + description: " + Undo the latest change made on this replica. + + Changes cannot be undone once they have been synchronized.", + }) + } +} + #[cfg(test)] mod test { use super::*; @@ -814,4 +839,13 @@ mod test { (&EMPTY[..], subcommand) ); } + + #[test] + fn test_undo() { + let subcommand = Subcommand::Undo; + assert_eq!( + Subcommand::parse(argv!["undo"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } } diff --git a/cli/src/invocation/cmd/mod.rs b/cli/src/invocation/cmd/mod.rs index 9371f1f2c..e7606ac90 100644 --- a/cli/src/invocation/cmd/mod.rs +++ b/cli/src/invocation/cmd/mod.rs @@ -8,4 +8,5 @@ pub(crate) mod info; pub(crate) mod modify; pub(crate) mod report; pub(crate) mod sync; +pub(crate) mod undo; pub(crate) mod version; diff --git a/cli/src/invocation/cmd/undo.rs b/cli/src/invocation/cmd/undo.rs new file mode 100644 index 000000000..d3f688e7a --- /dev/null +++ b/cli/src/invocation/cmd/undo.rs @@ -0,0 +1,28 @@ +use taskchampion::Replica; +use termcolor::WriteColor; + +pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Result<(), crate::Error> { + if replica.undo()? { + writeln!(w, "Undo successful.")?; + } else { + writeln!(w, "Nothing to undo.")?; + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::test::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_undo() { + let mut w = test_writer(); + let mut replica = test_replica(); + + // Note that the details of the actual undo operation are tested thoroughly in the taskchampion crate + execute(&mut w, &mut replica).unwrap(); + assert_eq!(&w.into_string(), "Nothing to undo.\n") + } +} diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 2f225f86a..41a8c4ce2 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -90,6 +90,13 @@ pub(crate) fn invoke(command: Command, settings: Settings) -> Result<(), crate:: return cmd::sync::execute(&mut w, &mut replica, &settings, &mut server); } + Command { + subcommand: Subcommand::Undo, + .. + } => { + return cmd::undo::execute(&mut w, &mut replica); + } + // handled in the first match, but here to ensure this match is exhaustive Command { subcommand: Subcommand::Help { .. }, From e328b86d97906cb2236df7b4703fb2f80f6c50fd Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 21 Dec 2021 01:10:08 +0000 Subject: [PATCH 408/548] add user docs for 'ta undo' --- docs/src/SUMMARY.md | 1 + docs/src/undo.md | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 docs/src/undo.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 061dbad5b..942db92d2 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -10,6 +10,7 @@ * [Dates and Durations](./time.md) * [Configuration](./config-file.md) * [Environment](./environment.md) + * [Undo](./undo.md) * [Synchronization](./task-sync.md) * [Running the Sync Server](./running-sync-server.md) - [Internal Details](./internals.md) diff --git a/docs/src/undo.md b/docs/src/undo.md new file mode 100644 index 000000000..350aebb7e --- /dev/null +++ b/docs/src/undo.md @@ -0,0 +1,7 @@ +# Undo + +It's easy to make a mistake: mark the wrong task as done, or hit enter before noticing a typo in a tag name. +The `ta undo` command makes it just as easy to fix the mistake, but effectively reversing the most recent change. +Multiple invocations of `ta undo` can be used to undo multiple changes. + +The limit of this functionality is that changes which have been synchronized to the server (via `ta sync`) cannot be undone. From 5fb3f700c0adc821699753e23b3289e68a09ae55 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 21 Dec 2021 01:12:30 +0000 Subject: [PATCH 409/548] add some logging for undo --- taskchampion/src/taskdb/undo.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/taskchampion/src/taskdb/undo.rs b/taskchampion/src/taskdb/undo.rs index f7636ebde..57bba0fa2 100644 --- a/taskchampion/src/taskdb/undo.rs +++ b/taskchampion/src/taskdb/undo.rs @@ -1,5 +1,6 @@ use super::apply; use crate::storage::{ReplicaOp, StorageTxn}; +use log::{debug, trace}; /// Undo local operations until an UndoPoint. pub(super) fn undo(txn: &mut dyn StorageTxn) -> anyhow::Result { @@ -12,8 +13,10 @@ pub(super) fn undo(txn: &mut dyn StorageTxn) -> anyhow::Result { if op == ReplicaOp::UndoPoint { break; } + debug!("Reversing operation {:?}", op); let rev_ops = op.reverse_ops(); for op in rev_ops { + trace!("Applying reversed operation {:?}", op); apply::apply_op(txn, &op)?; applied = true; } From 36c51d2d937f79178705fcaab520fec5a12c7b55 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 22 Dec 2021 00:31:46 +0000 Subject: [PATCH 410/548] fix clippy --- taskchampion/src/task/task.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 6cc3c5126..2e3ba8169 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -261,9 +261,9 @@ impl<'r> TaskMut<'r> { if !self.updated_modified { let now = format!("{}", Utc::now().timestamp()); trace!("task {}: set property modified={:?}", self.task.uuid, now); - self.task.taskmap = - self.replica - .update_task(self.task.uuid, "modified", Some(now.clone()))?; + self.task.taskmap = self + .replica + .update_task(self.task.uuid, "modified", Some(now))?; self.updated_modified = true; } Ok(()) From 691a3e49e8c85b9d77935ec5c9c2d5a29a22a20d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 22 Dec 2021 00:43:15 +0000 Subject: [PATCH 411/548] Update clippy toolchain to 1.57 --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 26a73dae6..88a094618 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -31,7 +31,7 @@ jobs: with: # Fixed version for clippy lints. Bump this as necesary. It must not # be older than the MSRV in tests.yml. - toolchain: "1.54" + toolchain: "1.57" override: true - uses: actions-rs/cargo@v1.0.1 From 8195b187c437b82cccbcb9cf92f3cabc5705db83 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 23 Dec 2021 09:06:19 -0500 Subject: [PATCH 412/548] fix docs typo --- docs/src/undo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/undo.md b/docs/src/undo.md index 350aebb7e..3d6702240 100644 --- a/docs/src/undo.md +++ b/docs/src/undo.md @@ -1,7 +1,7 @@ # Undo It's easy to make a mistake: mark the wrong task as done, or hit enter before noticing a typo in a tag name. -The `ta undo` command makes it just as easy to fix the mistake, but effectively reversing the most recent change. +The `ta undo` command makes it just as easy to fix the mistake, by effectively reversing the most recent change. Multiple invocations of `ta undo` can be used to undo multiple changes. The limit of this functionality is that changes which have been synchronized to the server (via `ta sync`) cannot be undone. From b255ad2a7de446aeb84455042b624edab7c8838c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 27 Dec 2021 00:01:14 +0000 Subject: [PATCH 413/548] use namespace.key for UDAs in the API, with legacy support --- docs/src/tasks.md | 2 +- taskchampion/src/task/task.rs | 178 ++++++++++++++++++++++++++++------ 2 files changed, 147 insertions(+), 33 deletions(-) diff --git a/docs/src/tasks.md b/docs/src/tasks.md index f8debd20c..eb6756e22 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -48,4 +48,4 @@ The application defining a UDA defines the format of the value. UDAs _should_ have a namespaced structure of the form `.`, where `` identifies the application defining the UDA. For example, a service named "DevSync" synchronizing tasks from GitHub might use UDAs like `devsync.github.issue-id`. -Note that many existing UDAs for Taskwarrior integrations do not follow this pattern. +Note that many existing UDAs for Taskwarrior integrations do not follow this pattern; these are referred to as legacy UDAs. diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 5d01293d1..ca4363ad6 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -59,6 +59,25 @@ enum Prop { Wait, } +#[allow(clippy::ptr_arg)] +fn uda_string_to_tuple(key: &String) -> (&str, &str) { + if let Some((ns, key)) = key.split_once('.') { + (ns, key) + } else { + ("", key.as_ref()) + } +} + +fn uda_tuple_to_string(namespace: impl Into, key: impl Into) -> String { + // TODO: maybe not Into + let namespace = namespace.into(); + if namespace.is_empty() { + key.into() + } else { + format!("{}.{}", namespace, key.into()) + } +} + impl Task { pub(crate) fn new(uuid: Uuid, taskmap: TaskMap) -> Task { Task { uuid, taskmap } @@ -177,15 +196,31 @@ impl Task { /// Get the named user defined attributes (UDA). This will return None /// for any key defined in the Task data model, regardless of whether /// it is set or not. - pub fn get_uda(&self, key: &str) -> Option<&str> { + pub fn get_uda(&self, namespace: &str, key: &str) -> Option<&str> { + self.get_legacy_uda(uda_tuple_to_string(namespace, key).as_ref()) + } + + /// Get the user defined attributes (UDAs) of this task, in arbitrary order. Each key is split + /// on the first `.` character. Legacy keys that do not contain `.` are represented as `("", + /// key)`. + pub fn get_udas(&self) -> impl Iterator + '_ { + self.taskmap + .iter() + .filter(|(k, _)| !Task::is_known_key(k)) + .map(|(k, v)| (uda_string_to_tuple(k), v.as_ref())) + } + + /// Get the named user defined attribute (UDA) in a legacy format. This will return None for + /// any key defined in the Task data model, regardless of whether it is set or not. + pub fn get_legacy_uda(&self, key: &str) -> Option<&str> { if Task::is_known_key(key) { return None; } self.taskmap.get(key).map(|s| s.as_ref()) } - /// Get the user defined attributes (UDAs) of this task, in arbitrary order. - pub fn get_udas(&self) -> impl Iterator + '_ { + /// Like `get_udas`, but returning each UDA key as a single string. + pub fn get_legacy_udas(&self) -> impl Iterator + '_ { self.taskmap .iter() .filter(|(p, _)| !Task::is_known_key(p)) @@ -298,11 +333,33 @@ impl<'r> TaskMut<'r> { /// Set a user-defined attribute (UDA). This will fail if the key is defined by the data /// model. - pub fn set_uda(&mut self, key: S1, value: S2) -> anyhow::Result<()> - where - S1: Into, - S2: Into, - { + pub fn set_uda( + &mut self, + namespace: impl Into, + key: impl Into, + value: impl Into, + ) -> anyhow::Result<()> { + let key = uda_tuple_to_string(namespace, key); + self.set_legacy_uda(key, value) + } + + /// Remove a user-defined attribute (UDA). This will fail if the key is defined by the data + /// model. + pub fn remove_uda( + &mut self, + namespace: impl Into, + key: impl Into, + ) -> anyhow::Result<()> { + let key = uda_tuple_to_string(namespace, key); + self.remove_legacy_uda(key) + } + + /// Set a user-defined attribute (UDA), where the key is a legacy key. + pub fn set_legacy_uda( + &mut self, + key: impl Into, + value: impl Into, + ) -> anyhow::Result<()> { let key = key.into(); if Task::is_known_key(&key) { anyhow::bail!( @@ -313,12 +370,8 @@ impl<'r> TaskMut<'r> { self.set_string(key, Some(value.into())) } - /// Remove a user-defined attribute (UDA). This will fail if the key is defined by the data - /// model. - pub fn remove_uda(&mut self, key: S) -> anyhow::Result<()> - where - S: Into, - { + /// Remove a user-defined attribute (UDA), where the key is a legacy key. + pub fn remove_legacy_uda(&mut self, key: impl Into) -> anyhow::Result<()> { let key = key.into(); if Task::is_known_key(&key) { anyhow::bail!( @@ -726,13 +779,18 @@ mod test { ("dep_1234".into(), "not a uda".into()), ("annotation_1234".into(), "not a uda".into()), ("githubid".into(), "123".into()), + ("jira.url".into(), "h://x".into()), ] .drain(..) .collect(), ); - let udas: Vec<_> = task.get_udas().collect(); - assert_eq!(udas, vec![("githubid", "123")]); + let mut udas: Vec<_> = task.get_udas().collect(); + udas.sort_unstable(); + assert_eq!( + udas, + vec![(("", "githubid"), "123"), (("jira", "url"), "h://x")] + ); } #[test] @@ -741,48 +799,102 @@ mod test { Uuid::new_v4(), vec![ ("description".into(), "not a uda".into()), - ("dep_1234".into(), "not a uda".into()), ("githubid".into(), "123".into()), + ("jira.url".into(), "h://x".into()), ] .drain(..) .collect(), ); - assert_eq!(task.get_uda("description"), None); // invalid UDA - assert_eq!(task.get_uda("dep_1234"), None); // invalid UDA - assert_eq!(task.get_uda("githubid"), Some("123")); - assert_eq!(task.get_uda("jiraid"), None); + assert_eq!(task.get_uda("", "description"), None); // invalid UDA + assert_eq!(task.get_uda("", "githubid"), Some("123")); + assert_eq!(task.get_uda("jira", "url"), Some("h://x")); + assert_eq!(task.get_uda("bugzilla", "url"), None); + } + + #[test] + fn test_get_legacy_uda() { + let task = Task::new( + Uuid::new_v4(), + vec![ + ("description".into(), "not a uda".into()), + ("dep_1234".into(), "not a uda".into()), + ("githubid".into(), "123".into()), + ("jira.url".into(), "h://x".into()), + ] + .drain(..) + .collect(), + ); + + assert_eq!(task.get_legacy_uda("description"), None); // invalid UDA + assert_eq!(task.get_legacy_uda("dep_1234"), None); // invalid UDA + assert_eq!(task.get_legacy_uda("githubid"), Some("123")); + assert_eq!(task.get_legacy_uda("jira.url"), Some("h://x")); + assert_eq!(task.get_legacy_uda("bugzilla.url"), None); } #[test] fn test_set_uda() { with_mut_task(|mut task| { - task.set_uda("githubid", "123").unwrap(); - + task.set_uda("jira", "url", "h://y").unwrap(); let udas: Vec<_> = task.get_udas().collect(); - assert_eq!(udas, vec![("githubid", "123")]); + assert_eq!(udas, vec![(("jira", "url"), "h://y")]); - task.set_uda("jiraid", "TW-1234").unwrap(); + task.set_uda("", "jiraid", "TW-1234").unwrap(); let mut udas: Vec<_> = task.get_udas().collect(); udas.sort_unstable(); - assert_eq!(udas, vec![("githubid", "123"), ("jiraid", "TW-1234")]); + assert_eq!( + udas, + vec![(("", "jiraid"), "TW-1234"), (("jira", "url"), "h://y")] + ); + }) + } + + #[test] + fn test_set_legacy_uda() { + with_mut_task(|mut task| { + task.set_legacy_uda("jira.url", "h://y").unwrap(); + let udas: Vec<_> = task.get_udas().collect(); + assert_eq!(udas, vec![(("jira", "url"), "h://y")]); + + task.set_legacy_uda("jiraid", "TW-1234").unwrap(); + + let mut udas: Vec<_> = task.get_udas().collect(); + udas.sort_unstable(); + assert_eq!( + udas, + vec![(("", "jiraid"), "TW-1234"), (("jira", "url"), "h://y")] + ); }) } #[test] fn test_set_uda_invalid() { with_mut_task(|mut task| { - assert!(task.set_uda("modified", "123").is_err()); - assert!(task.set_uda("tag_abc", "123").is_err()); + assert!(task.set_uda("", "modified", "123").is_err()); + assert!(task.set_uda("", "tag_abc", "123").is_err()); + assert!(task.set_legacy_uda("modified", "123").is_err()); + assert!(task.set_legacy_uda("tag_abc", "123").is_err()); }) } #[test] - fn test_rmmove_uda() { + fn test_remove_uda() { + with_mut_task(|mut task| { + task.set_string("github.id", Some("123".into())).unwrap(); + task.remove_uda("github", "id").unwrap(); + + let udas: Vec<_> = task.get_udas().collect(); + assert_eq!(udas, vec![]); + }) + } + + #[test] + fn test_remove_legacy_uda() { with_mut_task(|mut task| { task.set_string("githubid", Some("123".into())).unwrap(); - task.remove_uda("githubid").unwrap(); + task.remove_legacy_uda("githubid").unwrap(); let udas: Vec<_> = task.get_udas().collect(); assert_eq!(udas, vec![]); @@ -792,8 +904,10 @@ mod test { #[test] fn test_remove_uda_invalid() { with_mut_task(|mut task| { - assert!(task.remove_uda("modified").is_err()); - assert!(task.remove_uda("tag_abc").is_err()); + assert!(task.remove_uda("", "modified").is_err()); + assert!(task.remove_uda("", "tag_abc").is_err()); + assert!(task.remove_legacy_uda("modified").is_err()); + assert!(task.remove_legacy_uda("tag_abc").is_err()); }) } } From e94c29ae2ff278b88a951c86a078f5ed09ce7d11 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 27 Dec 2021 00:09:02 +0000 Subject: [PATCH 414/548] use better trait bounds --- taskchampion/src/task/task.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index d52c8d3a4..fa22b026a 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -60,21 +60,21 @@ enum Prop { } #[allow(clippy::ptr_arg)] -fn uda_string_to_tuple(key: &String) -> (&str, &str) { +fn uda_string_to_tuple(key: &str) -> (&str, &str) { if let Some((ns, key)) = key.split_once('.') { (ns, key) } else { - ("", key.as_ref()) + ("", key) } } -fn uda_tuple_to_string(namespace: impl Into, key: impl Into) -> String { - // TODO: maybe not Into - let namespace = namespace.into(); +fn uda_tuple_to_string(namespace: impl AsRef, key: impl AsRef) -> String { + let namespace = namespace.as_ref(); + let key = key.as_ref(); if namespace.is_empty() { key.into() } else { - format!("{}.{}", namespace, key.into()) + format!("{}.{}", namespace, key) } } @@ -335,8 +335,8 @@ impl<'r> TaskMut<'r> { /// model. pub fn set_uda( &mut self, - namespace: impl Into, - key: impl Into, + namespace: impl AsRef, + key: impl AsRef, value: impl Into, ) -> anyhow::Result<()> { let key = uda_tuple_to_string(namespace, key); @@ -347,8 +347,8 @@ impl<'r> TaskMut<'r> { /// model. pub fn remove_uda( &mut self, - namespace: impl Into, - key: impl Into, + namespace: impl AsRef, + key: impl AsRef, ) -> anyhow::Result<()> { let key = uda_tuple_to_string(namespace, key); self.remove_legacy_uda(key) @@ -388,9 +388,9 @@ impl<'r> TaskMut<'r> { if !self.updated_modified { let now = format!("{}", Utc::now().timestamp()); trace!("task {}: set property modified={:?}", self.task.uuid, now); - self.task.taskmap = self - .replica - .update_task(self.task.uuid, Prop::Modified.as_ref(), Some(now))?; + self.task.taskmap = + self.replica + .update_task(self.task.uuid, Prop::Modified.as_ref(), Some(now))?; self.updated_modified = true; } Ok(()) From bc8bb5255110856c2dd50a64b9887dab7b3728a3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 27 Dec 2021 00:14:40 +0000 Subject: [PATCH 415/548] do not use str.split_once, as it is not in MSRV --- taskchampion/src/task/task.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index fa22b026a..40a85ba87 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -61,10 +61,13 @@ enum Prop { #[allow(clippy::ptr_arg)] fn uda_string_to_tuple(key: &str) -> (&str, &str) { - if let Some((ns, key)) = key.split_once('.') { - (ns, key) + let mut iter = key.splitn(2, '.'); + let first = iter.next().unwrap(); + let second = iter.next(); + if let Some(second) = second { + (first, second) } else { - ("", key) + ("", first) } } From 6a1d1a8c3c8f6df971a892cff0b8af052a51431e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 20 Dec 2021 16:26:52 +0000 Subject: [PATCH 416/548] use strum_macros::Display to display Status --- taskchampion/src/replica.rs | 6 ++-- taskchampion/src/task/status.rs | 50 +++++++++++++++++---------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 271a39436..2b833a323 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -274,7 +274,7 @@ mod tests { uuid: t.get_uuid(), property: "status".into(), old_value: None, - value: Some("P".into()), + value: Some("pending".into()), timestamp: now, }, ReplicaOp::Update { @@ -294,8 +294,8 @@ mod tests { ReplicaOp::Update { uuid: t.get_uuid(), property: "status".into(), - old_value: Some("P".into()), - value: Some("C".into()), + old_value: Some("pending".into()), + value: Some("completed".into()), timestamp: now, }, ] diff --git a/taskchampion/src/task/status.rs b/taskchampion/src/task/status.rs index fbc4ed866..958fbc1c1 100644 --- a/taskchampion/src/task/status.rs +++ b/taskchampion/src/task/status.rs @@ -1,5 +1,5 @@ /// The status of a task. The default status in "Pending". -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, strum_macros::Display)] pub enum Status { Pending, Completed, @@ -11,9 +11,9 @@ impl Status { /// defaulting to Pending pub(crate) fn from_taskmap(s: &str) -> Status { match s { - "P" => Status::Pending, - "C" => Status::Completed, - "D" => Status::Deleted, + "pending" => Status::Pending, + "completed" => Status::Completed, + "deleted" => Status::Deleted, _ => Status::Pending, } } @@ -21,19 +21,9 @@ impl Status { /// Get the 1-character value for this status to use in the TaskMap. pub(crate) fn to_taskmap(&self) -> &str { match self { - Status::Pending => "P", - Status::Completed => "C", - Status::Deleted => "D", - } - } - - /// Get the full-name value for this status to use in the TaskMap. - pub fn to_string(&self) -> &str { - // TODO: should be impl Display - match self { - Status::Pending => "Pending", - Status::Completed => "Completed", - Status::Deleted => "Deleted", + Status::Pending => "pending", + Status::Completed => "completed", + Status::Deleted => "deleted", } } } @@ -44,12 +34,24 @@ mod test { use pretty_assertions::assert_eq; #[test] - fn test_status() { - assert_eq!(Status::Pending.to_taskmap(), "P"); - assert_eq!(Status::Completed.to_taskmap(), "C"); - assert_eq!(Status::Deleted.to_taskmap(), "D"); - assert_eq!(Status::from_taskmap("P"), Status::Pending); - assert_eq!(Status::from_taskmap("C"), Status::Completed); - assert_eq!(Status::from_taskmap("D"), Status::Deleted); + fn to_taskmap() { + assert_eq!(Status::Pending.to_taskmap(), "pending"); + assert_eq!(Status::Completed.to_taskmap(), "completed"); + assert_eq!(Status::Deleted.to_taskmap(), "deleted"); + } + + #[test] + fn from_taskmap() { + assert_eq!(Status::from_taskmap("pending"), Status::Pending); + assert_eq!(Status::from_taskmap("completed"), Status::Completed); + assert_eq!(Status::from_taskmap("deleted"), Status::Deleted); + assert_eq!(Status::from_taskmap("something-else"), Status::Pending); + } + + #[test] + fn display() { + assert_eq!(format!("{}", Status::Pending), "Pending"); + assert_eq!(format!("{}", Status::Completed), "Completed"); + assert_eq!(format!("{}", Status::Deleted), "Deleted"); } } From 9965d10736c9c72e3e97b05a0a8b3d8a31da9547 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 24 Dec 2021 16:05:33 +0000 Subject: [PATCH 417/548] Maintain unrecognized statuses --- taskchampion/src/task/status.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/taskchampion/src/task/status.rs b/taskchampion/src/task/status.rs index 958fbc1c1..2b2afb6ba 100644 --- a/taskchampion/src/task/status.rs +++ b/taskchampion/src/task/status.rs @@ -1,9 +1,14 @@ -/// The status of a task. The default status in "Pending". +/// The status of a task, as defined by the task data model. #[derive(Debug, PartialEq, Clone, strum_macros::Display)] pub enum Status { Pending, Completed, Deleted, + /// Unknown signifies a status in the task DB that was not + /// recognized. This supports forward-compatibility if a + /// new status is added. Tasks with unknown status should + /// be ignored (but not deleted). + Unknown(String), } impl Status { @@ -14,7 +19,7 @@ impl Status { "pending" => Status::Pending, "completed" => Status::Completed, "deleted" => Status::Deleted, - _ => Status::Pending, + v => Status::Unknown(v.to_string()), } } @@ -24,6 +29,7 @@ impl Status { Status::Pending => "pending", Status::Completed => "completed", Status::Deleted => "deleted", + Status::Unknown(v) => v.as_ref(), } } } @@ -38,6 +44,7 @@ mod test { assert_eq!(Status::Pending.to_taskmap(), "pending"); assert_eq!(Status::Completed.to_taskmap(), "completed"); assert_eq!(Status::Deleted.to_taskmap(), "deleted"); + assert_eq!(Status::Unknown("wishful".into()).to_taskmap(), "wishful"); } #[test] @@ -45,7 +52,10 @@ mod test { assert_eq!(Status::from_taskmap("pending"), Status::Pending); assert_eq!(Status::from_taskmap("completed"), Status::Completed); assert_eq!(Status::from_taskmap("deleted"), Status::Deleted); - assert_eq!(Status::from_taskmap("something-else"), Status::Pending); + assert_eq!( + Status::from_taskmap("something-else"), + Status::Unknown("something-else".into()) + ); } #[test] @@ -53,5 +63,6 @@ mod test { assert_eq!(format!("{}", Status::Pending), "Pending"); assert_eq!(format!("{}", Status::Completed), "Completed"); assert_eq!(format!("{}", Status::Deleted), "Deleted"); + assert_eq!(format!("{}", Status::Unknown("wishful".into())), "Unknown"); } } From e3f438d9faac11f50d04c34f72630fc4426c3f17 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 5 Jan 2022 02:49:04 +0000 Subject: [PATCH 418/548] make taskdb.apply for create/delete not fail if already exists/doesn't exist --- taskchampion/src/replica.rs | 17 +++++++---- taskchampion/src/taskdb/apply.rs | 51 ++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 2b833a323..576de2f7a 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -99,18 +99,25 @@ impl Replica { .map(move |tm| Task::new(uuid, tm))) } - /// Create a new task. The task must not already exist. + /// Create a new task. pub fn new_task(&mut self, status: Status, description: String) -> anyhow::Result { - self.add_undo_point(false)?; let uuid = Uuid::new_v4(); - let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; - trace!("task {} created", uuid); - let mut task = Task::new(uuid, taskmap).into_mut(self); + let mut task = self.create_task(uuid)?.into_mut(self); task.set_description(description)?; task.set_status(status)?; + trace!("task {} created", uuid); Ok(task.into_immut()) } + /// Create a new, empty task with the given UUID. This is useful for importing tasks, but + /// otherwise should be avoided in favor of `new_task`. If the task already exists, this + /// does nothing and returns the existing task. + pub fn create_task(&mut self, uuid: Uuid) -> anyhow::Result { + self.add_undo_point(false)?; + let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; + Ok(Task::new(uuid, taskmap)) + } + /// Delete a task. The task must exist. Note that this is different from setting status to /// Deleted; this is the final purge of the task. This is not a public method as deletion /// should only occur through expiration. diff --git a/taskchampion/src/taskdb/apply.rs b/taskchampion/src/taskdb/apply.rs index 1be3864c9..1e3a3fa83 100644 --- a/taskchampion/src/taskdb/apply.rs +++ b/taskchampion/src/taskdb/apply.rs @@ -4,7 +4,8 @@ use crate::storage::{ReplicaOp, StorageTxn, TaskMap}; /// Apply the given SyncOp to the replica, updating both the task data and adding a /// ReplicaOp to the list of operations. Returns the TaskMap of the task after the -/// operation has been applied (or an empty TaskMap for Delete). +/// operation has been applied (or an empty TaskMap for Delete). It is not an error +/// to create an existing task, nor to delete a nonexistent task. pub(super) fn apply_and_record(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow::Result { match op { SyncOp::Create { uuid } => { @@ -14,8 +15,9 @@ pub(super) fn apply_and_record(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow:: txn.commit()?; Ok(TaskMap::new()) } else { - // TODO: differentiate error types here? - Err(Error::Database(format!("Task {} already exists", uuid)).into()) + Ok(txn + .get_task(uuid)? + .expect("create_task failed but task does not exist")) } } SyncOp::Delete { uuid } => { @@ -29,7 +31,7 @@ pub(super) fn apply_and_record(txn: &mut dyn StorageTxn, op: SyncOp) -> anyhow:: txn.commit()?; Ok(TaskMap::new()) } else { - Err(Error::Database(format!("Task {} does not exist", uuid)).into()) + Ok(TaskMap::new()) } } SyncOp::Update { @@ -105,6 +107,7 @@ pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &SyncOp) -> anyhow::Result< #[cfg(test)] mod tests { use super::*; + use crate::storage::TaskMap; use crate::taskdb::TaskDb; use chrono::Utc; use pretty_assertions::assert_eq; @@ -133,24 +136,33 @@ mod tests { fn test_apply_create_exists() -> anyhow::Result<()> { let mut db = TaskDb::new_inmemory(); let uuid = Uuid::new_v4(); + { + let mut txn = db.storage.txn()?; + txn.create_task(uuid)?; + let mut taskmap = TaskMap::new(); + taskmap.insert("foo".into(), "bar".into()); + txn.set_task(uuid, taskmap)?; + txn.commit()?; + } + let op = SyncOp::Create { uuid }; { let mut txn = db.storage.txn()?; let taskmap = apply_and_record(txn.as_mut(), op.clone())?; - assert_eq!(taskmap.len(), 0); - assert_eq!( - apply_and_record(txn.as_mut(), op) - .err() - .unwrap() - .to_string(), - format!("Task Database Error: Task {} already exists", uuid) - ); + + assert_eq!(taskmap.len(), 1); + assert_eq!(taskmap.get("foo").unwrap(), "bar"); + txn.commit()?; } - // first op was applied - assert_eq!(db.sorted_tasks(), vec![(uuid, vec![])]); - assert_eq!(db.operations(), vec![ReplicaOp::Create { uuid }]); + // create did not delete the old task.. + assert_eq!( + db.sorted_tasks(), + vec![(uuid, vec![("foo".into(), "bar".into())])] + ); + // create was done "manually" above, and no new op was added + assert_eq!(db.operations(), vec![]); Ok(()) } @@ -384,13 +396,8 @@ mod tests { let op = SyncOp::Delete { uuid }; { let mut txn = db.storage.txn()?; - assert_eq!( - apply_and_record(txn.as_mut(), op) - .err() - .unwrap() - .to_string(), - format!("Task Database Error: Task {} does not exist", uuid) - ); + let taskmap = apply_and_record(txn.as_mut(), op)?; + assert_eq!(taskmap.len(), 0); txn.commit()?; } From e2e0951c816821c80ba022d38ea971e73cc4f20a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 5 Jan 2022 03:12:44 +0000 Subject: [PATCH 419/548] Make a public method --- taskchampion/src/replica.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 576de2f7a..17b058184 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -47,7 +47,10 @@ impl Replica { /// Update an existing task. If the value is Some, the property is added or updated. If the /// value is None, the property is deleted. It is not an error to delete a nonexistent /// property. - pub(crate) fn update_task( + /// + /// This is a low-level method, and requires knowledge of the Task data model. Prefer to + /// use the [`TaskMut`] methods to modify tasks, where possible. + pub fn update_task( &mut self, uuid: Uuid, property: S1, From 63804b56523d46ad9e2c2bf5413ada90bf8c1a4c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 5 Jan 2022 03:12:55 +0000 Subject: [PATCH 420/548] Implement 'ta import' Tests include "TODO" notes for data not handled by TaskChampion, including links to the associated GitHub issues. --- Cargo.lock | 1 + cli/Cargo.toml | 7 +- cli/src/argparse/subcommand.rs | 33 ++++ cli/src/invocation/cmd/import.rs | 257 +++++++++++++++++++++++++++++++ cli/src/invocation/cmd/mod.rs | 1 + cli/src/invocation/mod.rs | 7 + taskchampion/src/task/task.rs | 9 +- 7 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 cli/src/invocation/cmd/import.rs diff --git a/Cargo.lock b/Cargo.lock index 158717feb..b63a0a2de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2997,6 +2997,7 @@ dependencies = [ "pretty_assertions", "prettytable-rs", "rstest", + "serde", "serde_json", "taskchampion", "tempfile", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 37fc20b19..9c3e63974 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -22,7 +22,9 @@ termcolor = "^1.1.2" atty = "^0.2.14" toml = "^0.5.8" toml_edit = "^0.2.0" -chrono = "0.4" +serde = { version = "^1.0.125", features = ["derive"] } +serde_json = "^1.0" +chrono = { version = "^0.4.10", features = ["serde"] } lazy_static = "1" iso8601-duration = "0.1" dialoguer = "0.8" @@ -30,7 +32,6 @@ dialoguer = "0.8" # only needed for usage-docs # if the mdbook version changes, change it in .github/workflows/publish-docs.yml and .github/workflows/checks.yml as well mdbook = { version = "0.4.10", optional = true } -serde_json = { version = "*", optional = true } [dependencies.taskchampion] path = "../taskchampion" @@ -46,7 +47,7 @@ rstest = "0.10" pretty_assertions = "1" [features] -usage-docs = [ "mdbook", "serde_json" ] +usage-docs = [ "mdbook" ] [[bin]] name = "ta" diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 06cc52490..99de0c46f 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -59,6 +59,7 @@ pub(crate) enum Subcommand { /// Basic operations without args Gc, Sync, + Import, Undo, } @@ -73,6 +74,7 @@ impl Subcommand { Info::parse, Gc::parse, Sync::parse, + Import::parse, Undo::parse, // This must come last since it accepts arbitrary report names Report::parse, @@ -88,6 +90,8 @@ impl Subcommand { Info::get_usage(u); Gc::get_usage(u); Sync::get_usage(u); + Import::get_usage(u); + Undo::get_usage(u); Report::get_usage(u); } } @@ -424,6 +428,35 @@ impl Sync { } } +struct Import; + +impl Import { + fn parse(input: ArgList) -> IResult { + fn to_subcommand(_: &str) -> Result { + Ok(Subcommand::Import) + } + map_res(arg_matching(literal("import")), to_subcommand)(input) + } + + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "import", + syntax: "import", + summary: "Import tasks", + description: " + Import tasks into this replica. + + The tasks must be provided in the TaskWarrior JSON format on stdin. If tasks + in the import already exist, they are 'merged'. + + Because TaskChampion lacks the information about the types of UDAs that is stored + in the TaskWarrior configuration, UDA values are imported as simple strings, in the + format they appear in the JSON export. This may cause undesirable results. + ", + }) + } +} + struct Undo; impl Undo { diff --git a/cli/src/invocation/cmd/import.rs b/cli/src/invocation/cmd/import.rs new file mode 100644 index 000000000..b017c73ba --- /dev/null +++ b/cli/src/invocation/cmd/import.rs @@ -0,0 +1,257 @@ +use anyhow::{anyhow, bail}; +use chrono::{DateTime, TimeZone, Utc}; +use serde::{self, Deserialize, Deserializer}; +use serde_json::Value; +use std::collections::HashMap; +use taskchampion::{Replica, Uuid}; +use termcolor::WriteColor; + +pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Result<(), crate::Error> { + writeln!(w, "Importing tasks from stdin.")?; + let tasks: Vec> = + serde_json::from_reader(std::io::stdin()).map_err(|_| anyhow!("Invalid JSON"))?; + + for task_json in &tasks { + import_task(w, replica, task_json)?; + } + + writeln!(w, "{} tasks imported.", tasks.len())?; + Ok(()) +} + +/// Convert the given value to a string, failing on compound types (arrays +/// and objects). +fn stringify(v: &Value) -> anyhow::Result { + Ok(match v { + Value::String(ref s) => s.clone(), + Value::Number(n) => n.to_string(), + Value::Bool(true) => "true".to_string(), + Value::Bool(false) => "false".to_string(), + Value::Null => "null".to_string(), + _ => bail!("{:?} cannot be converted to a string", v), + }) +} + +pub fn deserialize_tw_datetime<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + const FORMAT: &str = "%Y%m%dT%H%M%SZ"; + let s = String::deserialize(deserializer)?; + Utc.datetime_from_str(&s, FORMAT) + .map_err(serde::de::Error::custom) +} + +/// Deserialize a string in the TaskWarrior format into a DateTime +#[derive(Deserialize)] +struct TwDateTime(#[serde(deserialize_with = "deserialize_tw_datetime")] DateTime); + +impl TwDateTime { + /// Generate the data-model style UNIX timestamp for this DateTime + fn tc_timestamp(&self) -> String { + self.0.timestamp().to_string() + } +} + +#[derive(Deserialize)] +struct Annotation { + entry: TwDateTime, + description: String, +} + +fn import_task( + w: &mut W, + replica: &mut Replica, + // TOOD: take this by value and consume it + task_json: &HashMap, +) -> anyhow::Result<()> { + let uuid = task_json + .get("uuid") + .ok_or_else(|| anyhow!("task has no uuid"))?; + let uuid = uuid + .as_str() + .ok_or_else(|| anyhow!("uuid is not a string"))?; + let uuid = Uuid::parse_str(uuid)?; + replica.create_task(uuid)?; + + let mut description = None; + for (k, v) in task_json.iter() { + match k.as_ref() { + // `id` is the working-set ID and is not stored + "id" => {} + + // `urgency` is also calculated and not stored + "urgency" => {} + + // `uuid` was already handled + "uuid" => {} + + // `annotations` is a sub-aray + "annotations" => { + let annotations: Vec = serde_json::from_value(v.clone())?; + for ann in annotations { + let k = format!("annotation_{}", ann.entry.tc_timestamp()); + replica.update_task(uuid, k, Some(ann.description))?; + } + } + + // `depends` is a sub-aray + "depends" => { + let deps: Vec = serde_json::from_value(v.clone())?; + for dep in deps { + let k = format!("dep_{}", dep); + replica.update_task(uuid, k, Some("".to_owned()))?; + } + } + + // `tags` is a sub-aray + "tags" => { + let tags: Vec = serde_json::from_value(v.clone())?; + for tag in tags { + let k = format!("tag_{}", tag); + replica.update_task(uuid, k, Some("".to_owned()))?; + } + } + + // convert all datetimes -> epoch integers + "end" | "entry" | "modified" | "wait" | "due" => { + let v: TwDateTime = serde_json::from_value(v.clone())?; + replica.update_task(uuid, k, Some(v.tc_timestamp()))?; + } + + // everything else is inserted directly + _ => { + let v = stringify(v)?; + replica.update_task(uuid, k, Some(v.clone()))?; + if k == "description" { + description = Some(v); + } + } + } + } + + writeln!( + w, + "{} {}", + uuid, + description.unwrap_or_else(|| "(no description)".into()) + )?; + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::test::*; + use chrono::{TimeZone, Utc}; + use pretty_assertions::assert_eq; + use serde_json::json; + use std::convert::TryInto; + use taskchampion::{Priority, Status}; + + #[test] + fn stringify_string() { + assert_eq!(stringify(&json!("foo")).unwrap(), "foo".to_string()); + } + + #[test] + fn stringify_number() { + assert_eq!(stringify(&json!(2.14)).unwrap(), "2.14".to_string()); + } + + #[test] + fn stringify_bool() { + assert_eq!(stringify(&json!(true)).unwrap(), "true".to_string()); + assert_eq!(stringify(&json!(false)).unwrap(), "false".to_string()); + } + + #[test] + fn stringify_null() { + assert_eq!(stringify(&json!(null)).unwrap(), "null".to_string()); + } + + #[test] + fn stringify_invalid() { + assert!(stringify(&json!([1])).is_err()); + assert!(stringify(&json!({"a": 1})).is_err()); + } + + #[test] + fn test_import() -> anyhow::Result<()> { + let mut w = test_writer(); + let mut replica = test_replica(); + + let task_json = serde_json::from_value(json!({ + "id": 0, + "description": "repair window", + "end": "20211231T175614Z", // TODO (#327) + "entry": "20211117T022410Z", // TODO (#326) + "modified": "20211231T175614Z", + "priority": "M", + "status": "completed", + "uuid": "fa01e916-1587-4c7d-a646-f7be62be8ee7", + "wait": "20211225T001523Z", + "due": "20211225T040000Z", // TODO (#82) + + // TODO: recurrence (#81) + "imask": 2, + "recur": "monthly", + "rtype": "periodic", + "mask": "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--", + + // (legacy) UDAs + "githubcreatedon": "20211110T175919Z", + "githubnamespace": "djmitche", + "githubnumber": 228, + + "tags": [ + "house" + ], + "depends": [ // TODO (#84) + "4f71035d-1704-47f0-885c-6f9134bcefb2" + ], + "annotations": [ + { + "entry": "20211223T142031Z", + "description": "ordered from website" + } + ], + "urgency": 4.16849 + }))?; + import_task(&mut w, &mut replica, &task_json)?; + + let task = replica + .get_task(Uuid::parse_str("fa01e916-1587-4c7d-a646-f7be62be8ee7").unwrap()) + .unwrap() + .unwrap(); + assert_eq!(task.get_description(), "repair window"); + assert_eq!(task.get_status(), Status::Completed); + assert_eq!(task.get_priority(), Priority::M); + assert_eq!( + task.get_wait(), + Some(Utc.ymd(2021, 12, 25).and_hms(00, 15, 23)) + ); + assert_eq!( + task.get_modified(), + Some(Utc.ymd(2021, 12, 31).and_hms(17, 56, 14)) + ); + assert!(task.has_tag(&"house".try_into().unwrap())); + assert!(!task.has_tag(&"PENDING".try_into().unwrap())); + assert_eq!( + task.get_annotations().collect::>(), + vec![taskchampion::Annotation { + entry: Utc.ymd(2021, 12, 23).and_hms(14, 20, 31), + description: "ordered from website".into(), + }] + ); + assert_eq!( + task.get_legacy_uda("githubcreatedon"), + Some("20211110T175919Z") + ); + assert_eq!(task.get_legacy_uda("githubnamespace"), Some("djmitche")); + assert_eq!(task.get_legacy_uda("githubnumber"), Some("228")); + + Ok(()) + } +} diff --git a/cli/src/invocation/cmd/mod.rs b/cli/src/invocation/cmd/mod.rs index e7606ac90..5aa3bce12 100644 --- a/cli/src/invocation/cmd/mod.rs +++ b/cli/src/invocation/cmd/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod add; pub(crate) mod config; pub(crate) mod gc; pub(crate) mod help; +pub(crate) mod import; pub(crate) mod info; pub(crate) mod modify; pub(crate) mod report; diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 41a8c4ce2..e3b468060 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -90,6 +90,13 @@ pub(crate) fn invoke(command: Command, settings: Settings) -> Result<(), crate:: return cmd::sync::execute(&mut w, &mut replica, &settings, &mut server); } + Command { + subcommand: Subcommand::Import, + .. + } => { + return cmd::import::execute(&mut w, &mut replica); + } + Command { subcommand: Subcommand::Undo, .. diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 40a85ba87..9771bc3d0 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -1,5 +1,5 @@ use super::tag::{SyntheticTag, TagInner}; -use super::{Annotation, Status, Tag, Timestamp}; +use super::{Annotation, Priority, Status, Tag, Timestamp}; use crate::replica::Replica; use crate::storage::TaskMap; use chrono::prelude::*; @@ -118,6 +118,13 @@ impl Task { .unwrap_or("") } + pub fn get_priority(&self) -> Priority { + self.taskmap + .get(Prop::Status.as_ref()) + .map(|s| Priority::from_taskmap(s)) + .unwrap_or(Priority::M) + } + /// Get the wait time. If this value is set, it will be returned, even /// if it is in the past. pub fn get_wait(&self) -> Option> { From 4b2ef1913aab5c297a1848ce3361f5b0f49ad1f2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 6 Jan 2022 00:17:01 +0000 Subject: [PATCH 421/548] use owned values to avoid unnecessary cloning --- cli/src/invocation/cmd/import.rs | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/cli/src/invocation/cmd/import.rs b/cli/src/invocation/cmd/import.rs index b017c73ba..2ff2ebcb0 100644 --- a/cli/src/invocation/cmd/import.rs +++ b/cli/src/invocation/cmd/import.rs @@ -8,10 +8,10 @@ use termcolor::WriteColor; pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Result<(), crate::Error> { writeln!(w, "Importing tasks from stdin.")?; - let tasks: Vec> = + let mut tasks: Vec> = serde_json::from_reader(std::io::stdin()).map_err(|_| anyhow!("Invalid JSON"))?; - for task_json in &tasks { + for task_json in tasks.drain(..) { import_task(w, replica, task_json)?; } @@ -21,9 +21,9 @@ pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Result /// Convert the given value to a string, failing on compound types (arrays /// and objects). -fn stringify(v: &Value) -> anyhow::Result { +fn stringify(v: Value) -> anyhow::Result { Ok(match v { - Value::String(ref s) => s.clone(), + Value::String(s) => s, Value::Number(n) => n.to_string(), Value::Bool(true) => "true".to_string(), Value::Bool(false) => "false".to_string(), @@ -62,8 +62,7 @@ struct Annotation { fn import_task( w: &mut W, replica: &mut Replica, - // TOOD: take this by value and consume it - task_json: &HashMap, + mut task_json: HashMap, ) -> anyhow::Result<()> { let uuid = task_json .get("uuid") @@ -75,7 +74,7 @@ fn import_task( replica.create_task(uuid)?; let mut description = None; - for (k, v) in task_json.iter() { + for (k, v) in task_json.drain() { match k.as_ref() { // `id` is the working-set ID and is not stored "id" => {} @@ -88,7 +87,7 @@ fn import_task( // `annotations` is a sub-aray "annotations" => { - let annotations: Vec = serde_json::from_value(v.clone())?; + let annotations: Vec = serde_json::from_value(v)?; for ann in annotations { let k = format!("annotation_{}", ann.entry.tc_timestamp()); replica.update_task(uuid, k, Some(ann.description))?; @@ -97,7 +96,7 @@ fn import_task( // `depends` is a sub-aray "depends" => { - let deps: Vec = serde_json::from_value(v.clone())?; + let deps: Vec = serde_json::from_value(v)?; for dep in deps { let k = format!("dep_{}", dep); replica.update_task(uuid, k, Some("".to_owned()))?; @@ -106,7 +105,7 @@ fn import_task( // `tags` is a sub-aray "tags" => { - let tags: Vec = serde_json::from_value(v.clone())?; + let tags: Vec = serde_json::from_value(v)?; for tag in tags { let k = format!("tag_{}", tag); replica.update_task(uuid, k, Some("".to_owned()))?; @@ -115,17 +114,18 @@ fn import_task( // convert all datetimes -> epoch integers "end" | "entry" | "modified" | "wait" | "due" => { - let v: TwDateTime = serde_json::from_value(v.clone())?; + let v: TwDateTime = serde_json::from_value(v)?; replica.update_task(uuid, k, Some(v.tc_timestamp()))?; } // everything else is inserted directly _ => { let v = stringify(v)?; - replica.update_task(uuid, k, Some(v.clone()))?; if k == "description" { - description = Some(v); + // keep a copy of the description for console output + description = Some(v.clone()); } + replica.update_task(uuid, k, Some(v))?; } } } @@ -152,29 +152,29 @@ mod test { #[test] fn stringify_string() { - assert_eq!(stringify(&json!("foo")).unwrap(), "foo".to_string()); + assert_eq!(stringify(json!("foo")).unwrap(), "foo".to_string()); } #[test] fn stringify_number() { - assert_eq!(stringify(&json!(2.14)).unwrap(), "2.14".to_string()); + assert_eq!(stringify(json!(2.14)).unwrap(), "2.14".to_string()); } #[test] fn stringify_bool() { - assert_eq!(stringify(&json!(true)).unwrap(), "true".to_string()); - assert_eq!(stringify(&json!(false)).unwrap(), "false".to_string()); + assert_eq!(stringify(json!(true)).unwrap(), "true".to_string()); + assert_eq!(stringify(json!(false)).unwrap(), "false".to_string()); } #[test] fn stringify_null() { - assert_eq!(stringify(&json!(null)).unwrap(), "null".to_string()); + assert_eq!(stringify(json!(null)).unwrap(), "null".to_string()); } #[test] fn stringify_invalid() { - assert!(stringify(&json!([1])).is_err()); - assert!(stringify(&json!({"a": 1})).is_err()); + assert!(stringify(json!([1])).is_err()); + assert!(stringify(json!({"a": 1})).is_err()); } #[test] @@ -219,7 +219,7 @@ mod test { ], "urgency": 4.16849 }))?; - import_task(&mut w, &mut replica, &task_json)?; + import_task(&mut w, &mut replica, task_json)?; let task = replica .get_task(Uuid::parse_str("fa01e916-1587-4c7d-a646-f7be62be8ee7").unwrap()) From b66374589273a4a4f80f750fa43f73104a91529a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 6 Jan 2022 01:58:22 +0000 Subject: [PATCH 422/548] Support an 'end' key in task maps This definition matches how TaskWarrior uses the same key. --- docs/src/tasks.md | 7 +++ taskchampion/src/replica.rs | 9 +++- taskchampion/src/task/task.rs | 80 +++++++++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/docs/src/tasks.md b/docs/src/tasks.md index eb6756e22..b3285af88 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -17,6 +17,12 @@ The result of this reconciliation will be `oldtag,newtag2`, while the user almos The key names given below avoid this issue, allowing user updates such as adding a tag or deleting a dependency to be represented in a single `Update` operation. +## Validity + +_Any_ key/value map is a valid task. +Consumers of task data must make a best effort to interpret any map, even if it contains apparently contradictory information. +For example, a task with status "completed" but no "end" key present should be interpreted as completed at an unknown time. + ## Representations Integers are stored in decimal notation. @@ -31,6 +37,7 @@ The following keys, and key formats, are defined: * `description` - the one-line summary of the task * `modified` - the time of the last modification of this task * `start` - the most recent time at which this task was started (a task with no `start` key is not active) +* `end` - if present, the time at which this task was completed or deleted (note that this key may not agree with `status`: it may be present for a pending task, or absent for a deleted or completed task) * `tag_` - indicates this task has tag `` (value is an empty string) * `wait` - indicates the time before which this task should be hidden, as it is not actionable * `annotation_` - value is an annotation created at the given time diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 2b833a323..af94f72ba 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -228,7 +228,7 @@ mod tests { .. } = op { - if property == "modified" { + if property == "modified" || property == "end" { if value.is_some() { value = Some("just-now".into()); } @@ -291,6 +291,13 @@ mod tests { value: Some("past tense".into()), timestamp: now, }, + ReplicaOp::Update { + uuid: t.get_uuid(), + property: "end".into(), + old_value: None, + value: Some("just-now".into()), + timestamp: now, + }, ReplicaOp::Update { uuid: t.get_uuid(), property: "status".into(), diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 40a85ba87..e151f88d5 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -57,6 +57,7 @@ enum Prop { Start, Status, Wait, + End, } #[allow(clippy::ptr_arg)] @@ -263,9 +264,23 @@ impl<'r> TaskMut<'r> { /// Set the task's status. This also adds the task to the working set if the /// new status puts it in that set. pub fn set_status(&mut self, status: Status) -> anyhow::Result<()> { - if status == Status::Pending { - let uuid = self.uuid; - self.replica.add_to_working_set(uuid)?; + if status == Status::Pending {} + match status { + Status::Pending => { + // clear "end" when a task becomes "pending" + if self.taskmap.contains_key(Prop::End.as_ref()) { + self.set_timestamp(Prop::End.as_ref(), None)?; + } + let uuid = self.uuid; + self.replica.add_to_working_set(uuid)?; + } + Status::Completed | Status::Deleted => { + // set "end" when a task is deleted or completed + if !self.taskmap.contains_key(Prop::End.as_ref()) { + self.set_timestamp(Prop::End.as_ref(), Some(Utc::now()))?; + } + } + _ => {} } self.set_string( Prop::Status.as_ref(), @@ -304,6 +319,14 @@ impl<'r> TaskMut<'r> { self.set_status(Status::Completed) } + /// Mark this task as deleted. + /// + /// Note that this does not delete the task. It merely marks the task as + /// deleted. + pub fn delete(&mut self) -> anyhow::Result<()> { + self.set_status(Status::Deleted) + } + /// Add a tag to this task. Does nothing if the tag is already present. pub fn add_tag(&mut self, tag: &Tag) -> anyhow::Result<()> { if tag.is_synthetic() { @@ -676,6 +699,41 @@ mod test { }); } + #[test] + fn test_set_status_pending() { + with_mut_task(|mut task| { + task.done().unwrap(); + + task.set_status(Status::Pending).unwrap(); + assert_eq!(task.get_status(), Status::Pending); + assert!(!task.taskmap.contains_key("end")); + assert!(task.has_tag(&stag(SyntheticTag::Pending))); + assert!(!task.has_tag(&stag(SyntheticTag::Completed))); + }); + } + + #[test] + fn test_set_status_completed() { + with_mut_task(|mut task| { + task.set_status(Status::Completed).unwrap(); + assert_eq!(task.get_status(), Status::Completed); + assert!(task.taskmap.contains_key("end")); + assert!(!task.has_tag(&stag(SyntheticTag::Pending))); + assert!(task.has_tag(&stag(SyntheticTag::Completed))); + }); + } + + #[test] + fn test_set_status_deleted() { + with_mut_task(|mut task| { + task.set_status(Status::Deleted).unwrap(); + assert_eq!(task.get_status(), Status::Deleted); + assert!(task.taskmap.contains_key("end")); + assert!(!task.has_tag(&stag(SyntheticTag::Pending))); + assert!(!task.has_tag(&stag(SyntheticTag::Completed))); + }); + } + #[test] fn test_start() { with_mut_task(|mut task| { @@ -718,6 +776,7 @@ mod test { with_mut_task(|mut task| { task.done().unwrap(); assert_eq!(task.get_status(), Status::Completed); + assert!(task.taskmap.contains_key("end")); assert!(task.has_tag(&stag(SyntheticTag::Completed))); // redundant call does nothing.. @@ -727,6 +786,21 @@ mod test { }); } + #[test] + fn test_delete() { + with_mut_task(|mut task| { + task.delete().unwrap(); + assert_eq!(task.get_status(), Status::Deleted); + assert!(task.taskmap.contains_key("end")); + assert!(!task.has_tag(&stag(SyntheticTag::Completed))); + + // redundant call does nothing.. + task.delete().unwrap(); + assert_eq!(task.get_status(), Status::Deleted); + assert!(!task.has_tag(&stag(SyntheticTag::Completed))); + }); + } + #[test] fn test_add_tags() { with_mut_task(|mut task| { From 9824ac1fd32acf8e8887ec864827f038c1e51e6d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 6 Jan 2022 02:18:32 +0000 Subject: [PATCH 423/548] add 'entry' key to tasks when created --- docs/src/tasks.md | 1 + taskchampion/src/replica.rs | 10 +++++++++- taskchampion/src/task/task.rs | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/src/tasks.md b/docs/src/tasks.md index eb6756e22..c2a1258ba 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -33,6 +33,7 @@ The following keys, and key formats, are defined: * `start` - the most recent time at which this task was started (a task with no `start` key is not active) * `tag_` - indicates this task has tag `` (value is an empty string) * `wait` - indicates the time before which this task should be hidden, as it is not actionable +* `entry` - the time at which the task was created * `annotation_` - value is an annotation created at the given time The following are not yet implemented: diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 2b833a323..5a02a59d6 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -108,6 +108,7 @@ impl Replica { let mut task = Task::new(uuid, taskmap).into_mut(self); task.set_description(description)?; task.set_status(status)?; + task.set_entry(Utc::now())?; Ok(task.into_immut()) } @@ -228,7 +229,7 @@ mod tests { .. } = op { - if property == "modified" { + if property == "modified" || property == "entry" { if value.is_some() { value = Some("just-now".into()); } @@ -277,6 +278,13 @@ mod tests { value: Some("pending".into()), timestamp: now, }, + ReplicaOp::Update { + uuid: t.get_uuid(), + property: "entry".into(), + old_value: None, + value: Some("just-now".into()), + timestamp: now, + }, ReplicaOp::Update { uuid: t.get_uuid(), property: "modified".into(), diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 40a85ba87..12832103a 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -57,6 +57,7 @@ enum Prop { Start, Status, Wait, + Entry, } #[allow(clippy::ptr_arg)] @@ -277,6 +278,10 @@ impl<'r> TaskMut<'r> { self.set_string(Prop::Description.as_ref(), Some(description)) } + pub(crate) fn set_entry(&mut self, entry: DateTime) -> anyhow::Result<()> { + self.set_timestamp(Prop::Entry.as_ref(), Some(entry)) + } + pub fn set_wait(&mut self, wait: Option>) -> anyhow::Result<()> { self.set_timestamp(Prop::Wait.as_ref(), wait) } From 162a9eae95d982651637f90d860be9751f72cf7e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 6 Jan 2022 03:49:26 +0000 Subject: [PATCH 424/548] Support parsing TDB2 files --- cli/src/lib.rs | 1 + cli/src/tdb2/mod.rs | 325 +++++++++++++++++++++++++++++++++++++++++ cli/src/tdb2/test.data | 6 + 3 files changed, 332 insertions(+) create mode 100644 cli/src/tdb2/mod.rs create mode 100644 cli/src/tdb2/test.data diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 8b61c12f8..3b3258f21 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -41,6 +41,7 @@ mod errors; mod invocation; mod settings; mod table; +mod tdb2; mod usage; /// See https://docs.rs/built diff --git a/cli/src/tdb2/mod.rs b/cli/src/tdb2/mod.rs new file mode 100644 index 000000000..f91c8eca1 --- /dev/null +++ b/cli/src/tdb2/mod.rs @@ -0,0 +1,325 @@ +//! TDB2 is TaskWarrior's on-disk database format. The set of tasks is represented in +//! `pending.data` and `completed.data`. There are other `.data` files as well, but those are not +//! used in TaskChampion. +use nom::{branch::*, character::complete::*, combinator::*, multi::*, sequence::*, IResult}; +use std::fmt; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct File { + pub(crate) lines: Vec, +} + +#[derive(Clone, PartialEq)] +pub(crate) struct Line { + pub(crate) attrs: Vec, +} + +#[derive(Clone, PartialEq)] +pub(crate) struct Attr { + pub(crate) name: String, + pub(crate) value: String, +} + +impl File { + pub(crate) fn from_str(input: &str) -> Result { + Ok(File::parse(input).map(|(_, res)| res).map_err(|_| ())?) + } + + fn parse(input: &str) -> IResult<&str, File> { + all_consuming(fold_many0( + terminated(Line::parse, char('\n')), + File { lines: vec![] }, + |mut file, line| { + file.lines.push(line); + file + }, + ))(input) + } +} + +impl Line { + /// Parse a line in a TDB2 file. See TaskWarrior's Task::Parse. + fn parse(input: &str) -> IResult<&str, Line> { + fn to_line(input: Vec) -> Result { + Ok(Line { attrs: input }) + } + map_res( + delimited( + char('['), + separated_list0(char(' '), Attr::parse), + char(']'), + ), + to_line, + )(input) + } +} + +impl fmt::Debug for Line { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("line!")?; + f.debug_list().entries(self.attrs.iter()).finish() + } +} + +impl Attr { + /// Parse an attribute (name-value pair). + fn parse(input: &str) -> IResult<&str, Attr> { + fn to_attr(input: (&str, String)) -> Result { + Ok(Attr { + name: input.0.into(), + value: input.1, + }) + } + map_res( + separated_pair(Attr::parse_name, char(':'), Attr::parse_value), + to_attr, + )(input) + } + + /// Parse an attribute name, which is composed of any character but `:`. + fn parse_name(input: &str) -> IResult<&str, &str> { + recognize(many1(none_of(":")))(input) + } + + /// Parse and interpret a quoted string. Note that this does _not_ reverse the effects of + + fn parse_value(input: &str) -> IResult<&str, String> { + // For the parsing part of the job, see Pig::getQuoted in TaskWarrior's libshared, which + // merely finds the end of a string. + // + // The interpretation is defined in json::decode in libshared. Fortunately, the data we + // are reading was created with json::encode, which does not perform unicode escaping. + + fn escaped_string_char(input: &str) -> IResult<&str, char> { + alt(( + // reverse the escaping performed in json::encode + preceded( + char('\\'), + alt(( + // some characters are simply escaped + one_of(r#""\/"#), + // others translate to control characters + value('\x08', char('b')), + value('\x0c', char('f')), + value('\n', char('n')), + value('\r', char('r')), + value('\t', char('t')), + )), + ), + // not a backslash or double-quote + none_of("\"\\"), + ))(input) + } + + let inner = fold_many0( + escaped_string_char, + String::new(), + |mut string, fragment| { + string.push(fragment); + string + }, + ); + + delimited(char('"'), inner, char('"'))(input) + } +} + +impl fmt::Debug for Attr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{:?} => {:?}", self.name, self.value)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! line { + ($($n:expr => $v:expr),* $(,)?) => ( + Line{attrs: vec![$(Attr{name: $n.into(), value: $v.into()}),*]} + ); + } + + #[test] + fn file() { + assert_eq!( + File::parse(include_str!("test.data")).unwrap(), + ( + "", + File { + lines: vec![ + line![ + "description" => "snake ðŸ", + "entry" => "1641670385", + "modified" => "1641670385", + "priority" => "M", + "status" => "pending", + "uuid" => "f19086c2-1f8d-4a6c-9b8d-f94901fb8e62", + ], + line![ + "annotation_1585711454" => + "https://blog.tensorflow.org/2020/03/face-and-hand-tracking-in-browser-with-mediapipe-and-tensorflowjs.html?linkId=83993617", + "description" => "try facemesh", + "entry" => "1585711451", + "modified" => "1592947544", + "priority" => "M", + "project" => "lists", + "status" => "pending", + "tags" => "idea", + "tags_idea" => "x", + "uuid" => "ee855dc7-6f61-408c-bc95-ebb52f7d529c", + ], + line![ + "description" => "testing", + "entry" => "1554074416", + "modified" => "1554074416", + "priority" => "M", + "status" => "pending", + "uuid" => "4578fb67-359b-4483-afe4-fef15925ccd6", + ], + line![ + "description" => "testing2", + "entry" => "1576352411", + "modified" => "1576352411", + "priority" => "M", + "status" => "pending", + "uuid" => "f5982cca-2ea1-4bfd-832c-9bd571dc0743", + ], + line![ + "description" => "new-task", + "entry" => "1576352696", + "modified" => "1576352696", + "priority" => "M", + "status" => "pending", + "uuid" => "cfee3170-f153-4075-aa1d-e20bcac2841b", + ], + line![ + "description" => "foo", + "entry" => "1579398776", + "modified" => "1579398776", + "priority" => "M", + "status" => "pending", + "uuid" => "df74ea94-5122-44fa-965a-637412fbbffc", + ], + ] + } + ) + ); + } + + #[test] + fn empty_line() { + assert_eq!(Line::parse("[]").unwrap(), ("", line![])); + } + + #[test] + fn escaped_line() { + assert_eq!( + Line::parse(r#"[annotation_1585711454:"\"\\\"" abc:"xx"]"#).unwrap(), + ( + "", + line!["annotation_1585711454" => "\"\\\"", "abc" => "xx"] + ) + ); + } + + #[test] + fn escaped_line_backslash() { + assert_eq!( + Line::parse(r#"[abc:"xx" 123:"x\\x"]"#).unwrap(), + ("", line!["abc" => "xx", "123" => "x\\x"]) + ); + } + + #[test] + fn escaped_line_quote() { + assert_eq!( + Line::parse(r#"[abc:"xx" 123:"x\"x"]"#).unwrap(), + ("", line!["abc" => "xx", "123" => "x\"x"]) + ); + } + + #[test] + fn unicode_line() { + assert_eq!( + Line::parse(r#"[description:"snake ðŸ" entry:"1641670385" modified:"1641670385" priority:"M" status:"pending" uuid:"f19086c2-1f8d-4a6c-9b8d-f94901fb8e62"]"#).unwrap(), + ("", line![ + "description" => "snake ðŸ", + "entry" => "1641670385", + "modified" => "1641670385", + "priority" => "M", + "status" => "pending", + "uuid" => "f19086c2-1f8d-4a6c-9b8d-f94901fb8e62", + ])); + } + + #[test] + fn backslashed_attr() { + assert!(Attr::parse(r#"one:"\""#).is_err()); + assert_eq!( + Attr::parse(r#"two:"\\""#).unwrap(), + ( + "", + Attr { + name: "two".into(), + value: r#"\"#.into(), + } + ) + ); + assert!(Attr::parse(r#"three:"\\\""#).is_err()); + assert_eq!( + Attr::parse(r#"four:"\\\\""#).unwrap(), + ( + "", + Attr { + name: "four".into(), + value: r#"\\"#.into(), + } + ) + ); + } + + #[test] + fn backslash_frontslash() { + assert_eq!( + Attr::parse(r#"front:"\/""#).unwrap(), + ( + "", + Attr { + name: "front".into(), + value: r#"/"#.into(), + } + ) + ); + } + + #[test] + fn backslash_control_chars() { + assert_eq!( + Attr::parse(r#"control:"\b\f\n\r\t""#).unwrap(), + ( + "", + Attr { + name: "control".into(), + value: "\x08\x0c\x0a\x0d\x09".into(), + } + ) + ); + } + + #[test] + fn url_attr() { + assert_eq!( + Attr::parse(r#"annotation_1585711454:"https:\/\/blog.tensorflow.org\/2020\/03\/""#) + .unwrap(), + ( + "", + Attr { + name: "annotation_1585711454".into(), + value: "https://blog.tensorflow.org/2020/03/".into(), + } + ) + ); + } +} diff --git a/cli/src/tdb2/test.data b/cli/src/tdb2/test.data new file mode 100644 index 000000000..f57b9101b --- /dev/null +++ b/cli/src/tdb2/test.data @@ -0,0 +1,6 @@ +[description:"snake ðŸ" entry:"1641670385" modified:"1641670385" priority:"M" status:"pending" uuid:"f19086c2-1f8d-4a6c-9b8d-f94901fb8e62"] +[annotation_1585711454:"https:\/\/blog.tensorflow.org\/2020\/03\/face-and-hand-tracking-in-browser-with-mediapipe-and-tensorflowjs.html?linkId=83993617" description:"try facemesh" entry:"1585711451" modified:"1592947544" priority:"M" project:"lists" status:"pending" tags:"idea" tags_idea:"x" uuid:"ee855dc7-6f61-408c-bc95-ebb52f7d529c"] +[description:"testing" entry:"1554074416" modified:"1554074416" priority:"M" status:"pending" uuid:"4578fb67-359b-4483-afe4-fef15925ccd6"] +[description:"testing2" entry:"1576352411" modified:"1576352411" priority:"M" status:"pending" uuid:"f5982cca-2ea1-4bfd-832c-9bd571dc0743"] +[description:"new-task" entry:"1576352696" modified:"1576352696" priority:"M" status:"pending" uuid:"cfee3170-f153-4075-aa1d-e20bcac2841b"] +[description:"foo" entry:"1579398776" modified:"1579398776" priority:"M" status:"pending" uuid:"df74ea94-5122-44fa-965a-637412fbbffc"] From 69d052603d6cd829de2ad7719063ef961067a8b3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 8 Jan 2022 22:11:27 +0000 Subject: [PATCH 425/548] ta import-tdb2 --- cli/src/argparse/subcommand.rs | 36 +++++++ cli/src/invocation/cmd/completed.data | 1 + cli/src/invocation/cmd/import.rs | 14 ++- cli/src/invocation/cmd/import_tdb2.rs | 142 ++++++++++++++++++++++++++ cli/src/invocation/cmd/mod.rs | 1 + cli/src/invocation/cmd/pending.data | 1 + cli/src/invocation/mod.rs | 7 ++ cli/src/tdb2/mod.rs | 2 +- 8 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 cli/src/invocation/cmd/completed.data create mode 100644 cli/src/invocation/cmd/import_tdb2.rs create mode 100644 cli/src/invocation/cmd/pending.data diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 99de0c46f..30f56812b 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -60,6 +60,9 @@ pub(crate) enum Subcommand { Gc, Sync, Import, + ImportTDB2 { + path: String, + }, Undo, } @@ -75,6 +78,7 @@ impl Subcommand { Gc::parse, Sync::parse, Import::parse, + ImportTDB2::parse, Undo::parse, // This must come last since it accepts arbitrary report names Report::parse, @@ -91,6 +95,7 @@ impl Subcommand { Gc::get_usage(u); Sync::get_usage(u); Import::get_usage(u); + ImportTDB2::get_usage(u); Undo::get_usage(u); Report::get_usage(u); } @@ -457,6 +462,37 @@ impl Import { } } +struct ImportTDB2; + +impl ImportTDB2 { + fn parse(input: ArgList) -> IResult { + fn to_subcommand(input: (&str, &str)) -> Result { + Ok(Subcommand::ImportTDB2 { + path: input.1.into(), + }) + } + map_res( + pair(arg_matching(literal("import-tdb2")), arg_matching(any)), + to_subcommand, + )(input) + } + + fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "import-tdb2", + syntax: "import-tdb2 ", + summary: "Import tasks from the TaskWarrior data directory", + description: " + Import tasks into this replica from a TaskWarrior data directory. If tasks in the + import already exist, they are 'merged'. This mode of import supports UDAs better + than the `import` subcommand, but requires access to the \"raw\" TaskWarrior data. + + This command supports task directories written by TaskWarrior-2.6.1 or later. + ", + }) + } +} + struct Undo; impl Undo { diff --git a/cli/src/invocation/cmd/completed.data b/cli/src/invocation/cmd/completed.data new file mode 100644 index 000000000..3a48b9cd1 --- /dev/null +++ b/cli/src/invocation/cmd/completed.data @@ -0,0 +1 @@ +[description:"&open;TEST&close; foo" entry:"1554074416" modified:"1554074416" priority:"M" status:"completed" uuid:"4578fb67-359b-4483-afe4-fef15925ccd6"] diff --git a/cli/src/invocation/cmd/import.rs b/cli/src/invocation/cmd/import.rs index 2ff2ebcb0..5e33e22ef 100644 --- a/cli/src/invocation/cmd/import.rs +++ b/cli/src/invocation/cmd/import.rs @@ -4,10 +4,13 @@ use serde::{self, Deserialize, Deserializer}; use serde_json::Value; use std::collections::HashMap; use taskchampion::{Replica, Uuid}; -use termcolor::WriteColor; +use termcolor::{Color, ColorSpec, WriteColor}; pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Result<(), crate::Error> { + w.set_color(ColorSpec::new().set_bold(true))?; writeln!(w, "Importing tasks from stdin.")?; + w.reset()?; + let mut tasks: Vec> = serde_json::from_reader(std::io::stdin()).map_err(|_| anyhow!("Invalid JSON"))?; @@ -15,7 +18,10 @@ pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Result import_task(w, replica, task_json)?; } + w.set_color(ColorSpec::new().set_bold(true))?; writeln!(w, "{} tasks imported.", tasks.len())?; + w.reset()?; + Ok(()) } @@ -130,10 +136,12 @@ fn import_task( } } + w.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?; + write!(w, "{}", uuid)?; + w.reset()?; writeln!( w, - "{} {}", - uuid, + " {}", description.unwrap_or_else(|| "(no description)".into()) )?; diff --git a/cli/src/invocation/cmd/import_tdb2.rs b/cli/src/invocation/cmd/import_tdb2.rs new file mode 100644 index 000000000..e441652c5 --- /dev/null +++ b/cli/src/invocation/cmd/import_tdb2.rs @@ -0,0 +1,142 @@ +use crate::tdb2; +use anyhow::anyhow; +use std::fs; +use std::path::PathBuf; +use taskchampion::{Replica, Uuid}; +use termcolor::{Color, ColorSpec, WriteColor}; + +pub(crate) fn execute( + w: &mut W, + replica: &mut Replica, + path: &str, +) -> Result<(), crate::Error> { + let path: PathBuf = path.into(); + + let mut count = 0; + for file in &["pending.data", "completed.data"] { + let file = path.join(file); + w.set_color(ColorSpec::new().set_bold(true))?; + writeln!(w, "Importing tasks from {:?}.", file)?; + w.reset()?; + + let data = fs::read_to_string(file)?; + let content = + tdb2::File::from_str(&data).map_err(|_| anyhow!("Could not parse TDB2 file format"))?; + count += content.lines.len(); + for line in content.lines { + import_task(w, replica, line)?; + } + } + w.set_color(ColorSpec::new().set_bold(true))?; + writeln!(w, "{} tasks imported.", count)?; + w.reset()?; + + Ok(()) +} + +fn import_task( + w: &mut W, + replica: &mut Replica, + mut line: tdb2::Line, +) -> anyhow::Result<()> { + let mut uuid = None; + for attr in line.attrs.iter() { + if &attr.name == "uuid" { + uuid = Some(Uuid::parse_str(&attr.value)?); + break; + } + } + let uuid = uuid.ok_or_else(|| anyhow!("task has no uuid"))?; + replica.create_task(uuid)?; + + let mut description = None; + for attr in line.attrs.drain(..) { + // oddly, TaskWarrior represents [ and ] with their HTML entity equivalents + let value = attr.value.replace("&open;", "[").replace("&close;", "]"); + match attr.name.as_ref() { + // `uuid` was already handled + "uuid" => {} + + // everything else is inserted directly + _ => { + if attr.name == "description" { + // keep a copy of the description for console output + description = Some(value.clone()); + } + replica.update_task(uuid, attr.name, Some(value))?; + } + } + } + + w.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?; + write!(w, "{}", uuid)?; + w.reset()?; + writeln!( + w, + " {}", + description.unwrap_or_else(|| "(no description)".into()) + )?; + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::test::*; + use chrono::{TimeZone, Utc}; + use pretty_assertions::assert_eq; + use std::convert::TryInto; + use taskchampion::{Priority, Status}; + use tempfile::TempDir; + + #[test] + fn test_import() -> anyhow::Result<()> { + let mut w = test_writer(); + let mut replica = test_replica(); + let tmp_dir = TempDir::new()?; + + fs::write( + tmp_dir.path().join("pending.data"), + include_bytes!("pending.data"), + )?; + fs::write( + tmp_dir.path().join("completed.data"), + include_bytes!("completed.data"), + )?; + + execute(&mut w, &mut replica, tmp_dir.path().to_str().unwrap())?; + + let task = replica + .get_task(Uuid::parse_str("f19086c2-1f8d-4a6c-9b8d-f94901fb8e62").unwrap()) + .unwrap() + .unwrap(); + assert_eq!(task.get_description(), "snake ðŸ"); + assert_eq!(task.get_status(), Status::Pending); + assert_eq!(task.get_priority(), Priority::M); + assert_eq!(task.get_wait(), None); + assert_eq!( + task.get_modified(), + Some(Utc.ymd(2022, 1, 8).and_hms(19, 33, 5)) + ); + assert!(task.has_tag(&"reptile".try_into().unwrap())); + assert!(!task.has_tag(&"COMPLETED".try_into().unwrap())); + + let task = replica + .get_task(Uuid::parse_str("4578fb67-359b-4483-afe4-fef15925ccd6").unwrap()) + .unwrap() + .unwrap(); + assert_eq!(task.get_description(), "[TEST] foo"); + assert_eq!(task.get_status(), Status::Completed); + assert_eq!(task.get_priority(), Priority::M); + assert_eq!(task.get_wait(), None); + assert_eq!( + task.get_modified(), + Some(Utc.ymd(2019, 3, 31).and_hms(23, 20, 16)) + ); + assert!(!task.has_tag(&"reptile".try_into().unwrap())); + assert!(task.has_tag(&"COMPLETED".try_into().unwrap())); + + Ok(()) + } +} diff --git a/cli/src/invocation/cmd/mod.rs b/cli/src/invocation/cmd/mod.rs index 5aa3bce12..59484ea0b 100644 --- a/cli/src/invocation/cmd/mod.rs +++ b/cli/src/invocation/cmd/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod config; pub(crate) mod gc; pub(crate) mod help; pub(crate) mod import; +pub(crate) mod import_tdb2; pub(crate) mod info; pub(crate) mod modify; pub(crate) mod report; diff --git a/cli/src/invocation/cmd/pending.data b/cli/src/invocation/cmd/pending.data new file mode 100644 index 000000000..5f5590945 --- /dev/null +++ b/cli/src/invocation/cmd/pending.data @@ -0,0 +1 @@ +[description:"snake ðŸ" entry:"1641670385" modified:"1641670385" priority:"M" status:"pending" tag_reptile:"" uuid:"f19086c2-1f8d-4a6c-9b8d-f94901fb8e62"] diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index e3b468060..7bc1c5616 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -97,6 +97,13 @@ pub(crate) fn invoke(command: Command, settings: Settings) -> Result<(), crate:: return cmd::import::execute(&mut w, &mut replica); } + Command { + subcommand: Subcommand::ImportTDB2 { path }, + .. + } => { + return cmd::import_tdb2::execute(&mut w, &mut replica, path.as_ref()); + } + Command { subcommand: Subcommand::Undo, .. diff --git a/cli/src/tdb2/mod.rs b/cli/src/tdb2/mod.rs index f91c8eca1..e23ad585b 100644 --- a/cli/src/tdb2/mod.rs +++ b/cli/src/tdb2/mod.rs @@ -22,7 +22,7 @@ pub(crate) struct Attr { impl File { pub(crate) fn from_str(input: &str) -> Result { - Ok(File::parse(input).map(|(_, res)| res).map_err(|_| ())?) + File::parse(input).map(|(_, res)| res).map_err(|_| ()) } fn parse(input: &str) -> IResult<&str, File> { From 5019ecb4f812acd64a5422e620c9ed77e958aa3e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 8 Jan 2022 22:34:29 +0000 Subject: [PATCH 426/548] allow windows newlines in TDB2 files --- cli/src/tdb2/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/tdb2/mod.rs b/cli/src/tdb2/mod.rs index e23ad585b..0ff59a311 100644 --- a/cli/src/tdb2/mod.rs +++ b/cli/src/tdb2/mod.rs @@ -27,7 +27,8 @@ impl File { fn parse(input: &str) -> IResult<&str, File> { all_consuming(fold_many0( - terminated(Line::parse, char('\n')), + // allow windows or normal newlines + terminated(Line::parse, pair(opt(char('\r')), char('\n'))), File { lines: vec![] }, |mut file, line| { file.lines.push(line); From 656f7e9ea0a10245a5bfac5576a870eb3db80050 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 15:22:41 +0000 Subject: [PATCH 427/548] replica.create_task -> import_task_with_uuid --- cli/src/invocation/cmd/import.rs | 2 +- cli/src/invocation/cmd/import_tdb2.rs | 2 +- taskchampion/src/replica.rs | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cli/src/invocation/cmd/import.rs b/cli/src/invocation/cmd/import.rs index 5e33e22ef..249c1ad46 100644 --- a/cli/src/invocation/cmd/import.rs +++ b/cli/src/invocation/cmd/import.rs @@ -77,7 +77,7 @@ fn import_task( .as_str() .ok_or_else(|| anyhow!("uuid is not a string"))?; let uuid = Uuid::parse_str(uuid)?; - replica.create_task(uuid)?; + replica.import_task_with_uuid(uuid)?; let mut description = None; for (k, v) in task_json.drain() { diff --git a/cli/src/invocation/cmd/import_tdb2.rs b/cli/src/invocation/cmd/import_tdb2.rs index e441652c5..8db967699 100644 --- a/cli/src/invocation/cmd/import_tdb2.rs +++ b/cli/src/invocation/cmd/import_tdb2.rs @@ -47,7 +47,7 @@ fn import_task( } } let uuid = uuid.ok_or_else(|| anyhow!("task has no uuid"))?; - replica.create_task(uuid)?; + replica.import_task_with_uuid(uuid)?; let mut description = None; for attr in line.attrs.drain(..) { diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index e7862dab5..8d673b23c 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -105,7 +105,9 @@ impl Replica { /// Create a new task. pub fn new_task(&mut self, status: Status, description: String) -> anyhow::Result { let uuid = Uuid::new_v4(); - let mut task = self.create_task(uuid)?.into_mut(self); + self.add_undo_point(false)?; + let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; + let mut task = Task::new(uuid, taskmap).into_mut(self); task.set_description(description)?; task.set_status(status)?; task.set_entry(Utc::now())?; @@ -116,7 +118,7 @@ impl Replica { /// Create a new, empty task with the given UUID. This is useful for importing tasks, but /// otherwise should be avoided in favor of `new_task`. If the task already exists, this /// does nothing and returns the existing task. - pub fn create_task(&mut self, uuid: Uuid) -> anyhow::Result { + pub fn import_task_with_uuid(&mut self, uuid: Uuid) -> anyhow::Result { self.add_undo_point(false)?; let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; Ok(Task::new(uuid, taskmap)) From 210eb60c86d502dc489d5a98181d3a35ccc60cf1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 15:27:13 +0000 Subject: [PATCH 428/548] 'ta import' -> 'ta import-tw' --- cli/src/argparse/subcommand.rs | 20 +++++++++---------- .../cmd/{import.rs => import_tw.rs} | 0 cli/src/invocation/cmd/mod.rs | 2 +- cli/src/invocation/mod.rs | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) rename cli/src/invocation/cmd/{import.rs => import_tw.rs} (100%) diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 30f56812b..22c270ba3 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -59,7 +59,7 @@ pub(crate) enum Subcommand { /// Basic operations without args Gc, Sync, - Import, + ImportTW, ImportTDB2 { path: String, }, @@ -77,7 +77,7 @@ impl Subcommand { Info::parse, Gc::parse, Sync::parse, - Import::parse, + ImportTW::parse, ImportTDB2::parse, Undo::parse, // This must come last since it accepts arbitrary report names @@ -94,7 +94,7 @@ impl Subcommand { Info::get_usage(u); Gc::get_usage(u); Sync::get_usage(u); - Import::get_usage(u); + ImportTW::get_usage(u); ImportTDB2::get_usage(u); Undo::get_usage(u); Report::get_usage(u); @@ -433,21 +433,21 @@ impl Sync { } } -struct Import; +struct ImportTW; -impl Import { +impl ImportTW { fn parse(input: ArgList) -> IResult { fn to_subcommand(_: &str) -> Result { - Ok(Subcommand::Import) + Ok(Subcommand::ImportTW) } - map_res(arg_matching(literal("import")), to_subcommand)(input) + map_res(arg_matching(literal("import-tw")), to_subcommand)(input) } fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "import", - syntax: "import", - summary: "Import tasks", + name: "import-tw", + syntax: "import-tw", + summary: "Import tasks from TaskWarrior export", description: " Import tasks into this replica. diff --git a/cli/src/invocation/cmd/import.rs b/cli/src/invocation/cmd/import_tw.rs similarity index 100% rename from cli/src/invocation/cmd/import.rs rename to cli/src/invocation/cmd/import_tw.rs diff --git a/cli/src/invocation/cmd/mod.rs b/cli/src/invocation/cmd/mod.rs index 59484ea0b..b5d1a21d6 100644 --- a/cli/src/invocation/cmd/mod.rs +++ b/cli/src/invocation/cmd/mod.rs @@ -4,8 +4,8 @@ pub(crate) mod add; pub(crate) mod config; pub(crate) mod gc; pub(crate) mod help; -pub(crate) mod import; pub(crate) mod import_tdb2; +pub(crate) mod import_tw; pub(crate) mod info; pub(crate) mod modify; pub(crate) mod report; diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 7bc1c5616..0ae3f44e0 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -91,10 +91,10 @@ pub(crate) fn invoke(command: Command, settings: Settings) -> Result<(), crate:: } Command { - subcommand: Subcommand::Import, + subcommand: Subcommand::ImportTW, .. } => { - return cmd::import::execute(&mut w, &mut replica); + return cmd::import_tw::execute(&mut w, &mut replica); } Command { From 50300c4ad7eb92c13cc4c092f48e0ee26170606b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 15:31:58 +0000 Subject: [PATCH 429/548] remove empty conditional --- taskchampion/src/task/task.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 4b20cf60d..74b6854bf 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -265,7 +265,6 @@ impl<'r> TaskMut<'r> { /// Set the task's status. This also adds the task to the working set if the /// new status puts it in that set. pub fn set_status(&mut self, status: Status) -> anyhow::Result<()> { - if status == Status::Pending {} match status { Status::Pending => { // clear "end" when a task becomes "pending" From 33f5f056b14c35a3bfee6e467719c16971b1993c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 22 Jan 2022 22:48:40 +0000 Subject: [PATCH 430/548] first bits of a dynamc lib --- Cargo.lock | 27 + Cargo.toml | 3 +- binding-tests/.gitignore | 2 + binding-tests/Makefile | 19 + binding-tests/doctest.cpp | 2 + binding-tests/doctest.h | 6816 +++++++++++++++++++++++++++++++++++++ binding-tests/uuid.cpp | 7 + lib/Cargo.toml | 15 + lib/Makefile | 2 + lib/build.rs | 18 + lib/src/lib.rs | 1 + lib/src/storage.rs | 16 + lib/taskchampion.h | 15 + 13 files changed, 6942 insertions(+), 1 deletion(-) create mode 100644 binding-tests/.gitignore create mode 100644 binding-tests/Makefile create mode 100644 binding-tests/doctest.cpp create mode 100644 binding-tests/doctest.h create mode 100644 binding-tests/uuid.cpp create mode 100644 lib/Cargo.toml create mode 100644 lib/Makefile create mode 100644 lib/build.rs create mode 100644 lib/src/lib.rs create mode 100644 lib/src/storage.rs create mode 100644 lib/taskchampion.h diff --git a/Cargo.lock b/Cargo.lock index b63a0a2de..caa3684ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -591,6 +591,25 @@ dependencies = [ "url", ] +[[package]] +name = "cbindgen" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e3973b165dc0f435831a9e426de67e894de532754ff7a3f307c03ee5dec7dc" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] + [[package]] name = "cc" version = "1.0.68" @@ -3008,6 +3027,14 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "taskchampion-lib" +version = "0.1.0" +dependencies = [ + "cbindgen", + "taskchampion", +] + [[package]] name = "taskchampion-sync-server" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index eef6d1ce7..a552a0dda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,6 @@ members = [ "taskchampion", "cli", "sync-server", - "replica-server-tests" + "replica-server-tests", + "lib" ] diff --git a/binding-tests/.gitignore b/binding-tests/.gitignore new file mode 100644 index 000000000..fb93ddffb --- /dev/null +++ b/binding-tests/.gitignore @@ -0,0 +1,2 @@ +*.o +doctest diff --git a/binding-tests/Makefile b/binding-tests/Makefile new file mode 100644 index 000000000..e5f09d787 --- /dev/null +++ b/binding-tests/Makefile @@ -0,0 +1,19 @@ +CXX=g++ +INC=-I ../lib +LIB=-L ../target/debug +RPATH=-Wl,-rpath,../target/debug + +TESTS = uuid.cpp + +.PHONY: all test + +all: test + +test: doctest + @./doctest --no-version --no-intro + +%.o: %.cpp ../lib/taskchampion.h + $(CXX) $(INC) -c $< -o $@ + +doctest: doctest.o $(subst .cpp,.o,$(TESTS)) + $(CXX) $(LIB) $(RPATH) $< $(subst .cpp,.o,$(TESTS)) -ltaskchampion -o $@ diff --git a/binding-tests/doctest.cpp b/binding-tests/doctest.cpp new file mode 100644 index 000000000..a3f832e49 --- /dev/null +++ b/binding-tests/doctest.cpp @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" diff --git a/binding-tests/doctest.h b/binding-tests/doctest.h new file mode 100644 index 000000000..d25f52682 --- /dev/null +++ b/binding-tests/doctest.h @@ -0,0 +1,6816 @@ +// ====================================================================== lgtm [cpp/missing-header-guard] +// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == +// ====================================================================== +// +// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD +// +// Copyright (c) 2016-2021 Viktor Kirilov +// +// Distributed under the MIT Software License +// See accompanying file LICENSE.txt or copy at +// https://opensource.org/licenses/MIT +// +// The documentation can be found at the library's page: +// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= +// +// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt +// +// The concept of subcases (sections in Catch) and expression decomposition are from there. +// Some parts of the code are taken directly: +// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> +// - the Approx() helper class for floating point comparison +// - colors in the console +// - breaking into a debugger +// - signal / SEH handling +// - timer +// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) +// +// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= + +#ifndef DOCTEST_LIBRARY_INCLUDED +#define DOCTEST_LIBRARY_INCLUDED + +// ================================================================================================= +// == VERSION ====================================================================================== +// ================================================================================================= + +#define DOCTEST_VERSION_MAJOR 2 +#define DOCTEST_VERSION_MINOR 4 +#define DOCTEST_VERSION_PATCH 8 + +// util we need here +#define DOCTEST_TOSTR_IMPL(x) #x +#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x) + +#define DOCTEST_VERSION_STR \ + DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_PATCH) + +#define DOCTEST_VERSION \ + (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) + +// ================================================================================================= +// == COMPILER VERSION ============================================================================= +// ================================================================================================= + +// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect + +#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) + +// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... +#if defined(_MSC_VER) && defined(_MSC_FULL_VER) +#if _MSC_VER == _MSC_FULL_VER / 10000 +#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) +#else // MSVC +#define DOCTEST_MSVC \ + DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) +#endif // MSVC +#endif // MSVC +#if defined(__clang__) && defined(__clang_minor__) +#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ + !defined(__INTEL_COMPILER) +#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#endif // GCC + +#ifndef DOCTEST_MSVC +#define DOCTEST_MSVC 0 +#endif // DOCTEST_MSVC +#ifndef DOCTEST_CLANG +#define DOCTEST_CLANG 0 +#endif // DOCTEST_CLANG +#ifndef DOCTEST_GCC +#define DOCTEST_GCC 0 +#endif // DOCTEST_GCC + +// ================================================================================================= +// == COMPILER WARNINGS HELPERS ==================================================================== +// ================================================================================================= + +#if DOCTEST_CLANG +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) +#else // DOCTEST_CLANG +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_CLANG + +#if DOCTEST_GCC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") +#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) +#else // DOCTEST_GCC +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH +#define DOCTEST_GCC_SUPPRESS_WARNING(w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_GCC + +#if DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) +#else // DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_MSVC + +// ================================================================================================= +// == COMPILER WARNINGS ============================================================================ +// ================================================================================================= + +// both the header and the implementation suppress all of these, +// so it only makes sense to aggregrate them like so +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \ + \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \ + \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + /* these 4 also disabled globally via cmake: */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \ + /* */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + /* static analysis */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */ + +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP + +// ================================================================================================= +// == FEATURE DETECTION ============================================================================ +// ================================================================================================= + +// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support +// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx +// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html +// MSVC version table: +// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022) +// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) +// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) +// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) + +// Universal Windows Platform support +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_WINDOWS_SEH +#endif // WINAPI_FAMILY +#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) +#define DOCTEST_CONFIG_WINDOWS_SEH +#endif // MSVC +#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) +#undef DOCTEST_CONFIG_WINDOWS_SEH +#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH + +#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ + !defined(__EMSCRIPTEN__) +#define DOCTEST_CONFIG_POSIX_SIGNALS +#endif // _WIN32 +#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) +#undef DOCTEST_CONFIG_POSIX_SIGNALS +#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // no exceptions +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) +#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) +#define DOCTEST_CONFIG_IMPLEMENT +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#if defined(_WIN32) || defined(__CYGWIN__) +#if DOCTEST_MSVC +#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) +#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) +#else // MSVC +#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) +#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) +#endif // MSVC +#else // _WIN32 +#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) +#define DOCTEST_SYMBOL_IMPORT +#endif // _WIN32 + +#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#ifdef DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT +#else // DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT +#endif // DOCTEST_CONFIG_IMPLEMENT +#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#define DOCTEST_INTERFACE +#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL + +#define DOCTEST_EMPTY + +#if DOCTEST_MSVC +#define DOCTEST_NOINLINE __declspec(noinline) +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0) +#define DOCTEST_NOINLINE +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else +#define DOCTEST_NOINLINE __attribute__((noinline)) +#define DOCTEST_UNUSED __attribute__((unused)) +#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) +#endif + +#ifndef DOCTEST_NORETURN +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NORETURN +#else // DOCTEST_MSVC +#define DOCTEST_NORETURN [[noreturn]] +#endif // DOCTEST_MSVC +#endif // DOCTEST_NORETURN + +#ifndef DOCTEST_NOEXCEPT +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NOEXCEPT +#else // DOCTEST_MSVC +#define DOCTEST_NOEXCEPT noexcept +#endif // DOCTEST_MSVC +#endif // DOCTEST_NOEXCEPT + +#ifndef DOCTEST_CONSTEXPR +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_CONSTEXPR const +#else // DOCTEST_MSVC +#define DOCTEST_CONSTEXPR constexpr +#endif // DOCTEST_MSVC +#endif // DOCTEST_CONSTEXPR + +// ================================================================================================= +// == FEATURE DETECTION END ======================================================================== +// ================================================================================================= + +// internal macros for string concatenation and anonymous variable name generation +#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 +#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) +#ifdef __COUNTER__ // not standard and may be missing for some compilers +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) +#else // __COUNTER__ +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) +#endif // __COUNTER__ + +#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x& +#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x +#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE + +// not using __APPLE__ because... this is how Catch does it +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#define DOCTEST_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define DOCTEST_PLATFORM_IPHONE +#elif defined(_WIN32) +#define DOCTEST_PLATFORM_WINDOWS +#else // DOCTEST_PLATFORM +#define DOCTEST_PLATFORM_LINUX +#endif // DOCTEST_PLATFORM + +namespace doctest { namespace detail { + static DOCTEST_CONSTEXPR int consume(const int*, int) { return 0; } +}} + +#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ + static const int var = doctest::detail::consume(&var, __VA_ARGS__); \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#ifndef DOCTEST_BREAK_INTO_DEBUGGER +// should probably take a look at https://github.com/scottt/debugbreak +#ifdef DOCTEST_PLATFORM_LINUX +#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) +// Break at the location of the failing check if possible +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#else +#include +#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) +#endif +#elif defined(DOCTEST_PLATFORM_MAC) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#else +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler) +#endif +#elif DOCTEST_MSVC +#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() +#elif defined(__MINGW32__) +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() +#else // linux +#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast(0)) +#endif // linux +#endif // DOCTEST_BREAK_INTO_DEBUGGER + +// this is kept here for backwards compatibility since the config option was changed +#ifdef DOCTEST_CONFIG_USE_IOSFWD +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif // DOCTEST_CONFIG_USE_IOSFWD + +// for clang - always include ciso646 (which drags some std stuff) because +// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in +// which case we don't want to forward declare stuff from std - for reference: +// https://github.com/doctest/doctest/issues/126 +// https://github.com/doctest/doctest/issues/356 +#if DOCTEST_CLANG +#include +#ifdef _LIBCPP_VERSION +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif // _LIBCPP_VERSION +#endif // clang + +#ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include +#include +#include +#else // DOCTEST_CONFIG_USE_STD_HEADERS + +// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) + +namespace std { // NOLINT (cert-dcl58-cpp) +typedef decltype(nullptr) nullptr_t; +template +struct char_traits; +template <> +struct char_traits; +template +class basic_ostream; +typedef basic_ostream> ostream; +template +class basic_istream; +typedef basic_istream> istream; +template +class tuple; +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +template +class allocator; +template +class basic_string; +using string = basic_string, allocator>; +#endif // VS 2019 +} // namespace std + +DOCTEST_MSVC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_USE_STD_HEADERS + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +namespace doctest { + +DOCTEST_INTERFACE extern bool is_running_in_test; + +// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length +// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: +// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) +// - if small - capacity left before going on the heap - using the lowest 5 bits +// - if small - 2 bits are left unused - the second and third highest ones +// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) +// and the "is small" bit remains "0" ("as well as the capacity left") so its OK +// Idea taken from this lecture about the string implementation of facebook/folly - fbstring +// https://www.youtube.com/watch?v=kPR8h4-qZdk +// TODO: +// - optimizations - like not deleting memory unnecessarily in operator= and etc. +// - resize/reserve/clear +// - substr +// - replace +// - back/front +// - iterator stuff +// - find & friends +// - push_back/pop_back +// - assign/insert/erase +// - relational operators as free functions - taking const char* as one of the params +class DOCTEST_INTERFACE String +{ + static const unsigned len = 24; //!OCLINT avoid private static members + static const unsigned last = len - 1; //!OCLINT avoid private static members + + struct view // len should be more than sizeof(view) - because of the final byte for flags + { + char* ptr; + unsigned size; + unsigned capacity; + }; + + union + { + char buf[len]; + view data; + }; + + char* allocate(unsigned sz); + + bool isOnStack() const { return (buf[last] & 128) == 0; } + void setOnHeap(); + void setLast(unsigned in = last); + + void copy(const String& other); + +public: + String(); + ~String(); + + // cppcheck-suppress noExplicitConstructor + String(const char* in); + String(const char* in, unsigned in_size); + + String(std::istream& in, unsigned in_size); + + String(const String& other); + String& operator=(const String& other); + + String& operator+=(const String& other); + + String(String&& other); + String& operator=(String&& other); + + char operator[](unsigned i) const; + char& operator[](unsigned i); + + // the only functions I'm willing to leave in the interface - available for inlining + const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT + char* c_str() { + if(isOnStack()) + return reinterpret_cast(buf); + return data.ptr; + } + + unsigned size() const; + unsigned capacity() const; + + int compare(const char* other, bool no_case = false) const; + int compare(const String& other, bool no_case = false) const; +}; + +DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs); + +DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); + +DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); + +namespace Color { + enum Enum + { + None = 0, + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White + }; + + DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); +} // namespace Color + +namespace assertType { + enum Enum + { + // macro traits + + is_warn = 1, + is_check = 2 * is_warn, + is_require = 2 * is_check, + + is_normal = 2 * is_require, + is_throws = 2 * is_normal, + is_throws_as = 2 * is_throws, + is_throws_with = 2 * is_throws_as, + is_nothrow = 2 * is_throws_with, + + is_false = 2 * is_nothrow, + is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types + + is_eq = 2 * is_unary, + is_ne = 2 * is_eq, + + is_lt = 2 * is_ne, + is_gt = 2 * is_lt, + + is_ge = 2 * is_gt, + is_le = 2 * is_ge, + + // macro types + + DT_WARN = is_normal | is_warn, + DT_CHECK = is_normal | is_check, + DT_REQUIRE = is_normal | is_require, + + DT_WARN_FALSE = is_normal | is_false | is_warn, + DT_CHECK_FALSE = is_normal | is_false | is_check, + DT_REQUIRE_FALSE = is_normal | is_false | is_require, + + DT_WARN_THROWS = is_throws | is_warn, + DT_CHECK_THROWS = is_throws | is_check, + DT_REQUIRE_THROWS = is_throws | is_require, + + DT_WARN_THROWS_AS = is_throws_as | is_warn, + DT_CHECK_THROWS_AS = is_throws_as | is_check, + DT_REQUIRE_THROWS_AS = is_throws_as | is_require, + + DT_WARN_THROWS_WITH = is_throws_with | is_warn, + DT_CHECK_THROWS_WITH = is_throws_with | is_check, + DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, + + DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, + DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, + DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, + + DT_WARN_NOTHROW = is_nothrow | is_warn, + DT_CHECK_NOTHROW = is_nothrow | is_check, + DT_REQUIRE_NOTHROW = is_nothrow | is_require, + + DT_WARN_EQ = is_normal | is_eq | is_warn, + DT_CHECK_EQ = is_normal | is_eq | is_check, + DT_REQUIRE_EQ = is_normal | is_eq | is_require, + + DT_WARN_NE = is_normal | is_ne | is_warn, + DT_CHECK_NE = is_normal | is_ne | is_check, + DT_REQUIRE_NE = is_normal | is_ne | is_require, + + DT_WARN_GT = is_normal | is_gt | is_warn, + DT_CHECK_GT = is_normal | is_gt | is_check, + DT_REQUIRE_GT = is_normal | is_gt | is_require, + + DT_WARN_LT = is_normal | is_lt | is_warn, + DT_CHECK_LT = is_normal | is_lt | is_check, + DT_REQUIRE_LT = is_normal | is_lt | is_require, + + DT_WARN_GE = is_normal | is_ge | is_warn, + DT_CHECK_GE = is_normal | is_ge | is_check, + DT_REQUIRE_GE = is_normal | is_ge | is_require, + + DT_WARN_LE = is_normal | is_le | is_warn, + DT_CHECK_LE = is_normal | is_le | is_check, + DT_REQUIRE_LE = is_normal | is_le | is_require, + + DT_WARN_UNARY = is_normal | is_unary | is_warn, + DT_CHECK_UNARY = is_normal | is_unary | is_check, + DT_REQUIRE_UNARY = is_normal | is_unary | is_require, + + DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, + DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, + DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, + }; +} // namespace assertType + +DOCTEST_INTERFACE const char* assertString(assertType::Enum at); +DOCTEST_INTERFACE const char* failureString(assertType::Enum at); +DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); + +struct DOCTEST_INTERFACE TestCaseData +{ + String m_file; // the file in which the test was registered (using String - see #350) + unsigned m_line; // the line where the test was registered + const char* m_name; // name of the test case + const char* m_test_suite; // the test suite in which the test was added + const char* m_description; + bool m_skip; + bool m_no_breaks; + bool m_no_output; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; +}; + +struct DOCTEST_INTERFACE AssertData +{ + // common - for all asserts + const TestCaseData* m_test_case; + assertType::Enum m_at; + const char* m_file; + int m_line; + const char* m_expr; + bool m_failed; + + // exception-related - for all asserts + bool m_threw; + String m_exception; + + // for normal asserts + String m_decomp; + + // for specific exception-related asserts + bool m_threw_as; + const char* m_exception_type; + const char* m_exception_string; +}; + +struct DOCTEST_INTERFACE MessageData +{ + String m_string; + const char* m_file; + int m_line; + assertType::Enum m_severity; +}; + +struct DOCTEST_INTERFACE SubcaseSignature +{ + String m_name; + const char* m_file; + int m_line; + + bool operator<(const SubcaseSignature& other) const; +}; + +struct DOCTEST_INTERFACE IContextScope +{ + IContextScope(); + virtual ~IContextScope(); + virtual void stringify(std::ostream*) const = 0; +}; + +namespace detail { + struct DOCTEST_INTERFACE TestCase; +} // namespace detail + +struct ContextOptions //!OCLINT too many fields +{ + std::ostream* cout = nullptr; // stdout stream + String binary_name; // the test binary name + + const detail::TestCase* currentTest = nullptr; + + // == parameters from the command line + String out; // output filename + String order_by; // how tests should be ordered + unsigned rand_seed; // the seed for rand ordering + + unsigned first; // the first (matching) test to be executed + unsigned last; // the last (matching) test to be executed + + int abort_after; // stop tests after this many failed assertions + int subcase_filter_levels; // apply the subcase filters for the first N levels + + bool success; // include successful assertions in output + bool case_sensitive; // if filtering should be case sensitive + bool exit; // if the program should be exited after the tests are ran/whatever + bool duration; // print the time duration of each test case + bool minimal; // minimal console output (only test failures) + bool quiet; // no console output + bool no_throw; // to skip exceptions-related assertion macros + bool no_exitcode; // if the framework should return 0 as the exitcode + bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_intro; // to not print the intro of the framework + bool no_version; // to not print the version of the framework + bool no_colors; // if output to the console should be colorized + bool force_colors; // forces the use of colors even when a tty cannot be detected + bool no_breaks; // to not break into the debugger + bool no_skip; // don't skip test cases which are marked to be skipped + bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): + bool no_path_in_filenames; // if the path to files should be removed from the output + bool no_line_numbers; // if source code line numbers should be omitted from the output + bool no_debug_output; // no output in the debug console when a debugger is attached + bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! + bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!! + + bool help; // to print the help + bool version; // to print the version + bool count; // if only the count of matching tests is to be retrieved + bool list_test_cases; // to list all tests matching the filters + bool list_test_suites; // to list all suites matching the filters + bool list_reporters; // lists all registered reporters +}; + +namespace detail { + template + struct enable_if + {}; + + template + struct enable_if + { typedef TYPE type; }; + + // clang-format off + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + + template U declval(int); + + template T declval(long); + + template auto declval() DOCTEST_NOEXCEPT -> decltype(declval(0)) ; + + template struct is_lvalue_reference { const static bool value=false; }; + template struct is_lvalue_reference { const static bool value=true; }; + + template struct is_rvalue_reference { const static bool value=false; }; + template struct is_rvalue_reference { const static bool value=true; }; + + template + inline T&& forward(typename remove_reference::type& t) DOCTEST_NOEXCEPT + { + return static_cast(t); + } + + template + inline T&& forward(typename remove_reference::type&& t) DOCTEST_NOEXCEPT + { + static_assert(!is_lvalue_reference::value, + "Can not forward an rvalue as an lvalue."); + return static_cast(t); + } + + template struct remove_const { typedef T type; }; + template struct remove_const { typedef T type; }; +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template struct is_enum : public std::is_enum {}; + template struct underlying_type : public std::underlying_type {}; +#else + // Use compiler intrinsics + template struct is_enum { DOCTEST_CONSTEXPR static bool value = __is_enum(T); }; + template struct underlying_type { typedef __underlying_type(T) type; }; +#endif + // clang-format on + + template + struct deferred_false + // cppcheck-suppress unusedStructMember + { static const bool value = false; }; + + namespace has_insertion_operator_impl { + std::ostream &os(); + template + DOCTEST_REF_WRAP(T) val(); + + template + struct check { + static DOCTEST_CONSTEXPR bool value = false; + }; + + template + struct check(), void())> { + static DOCTEST_CONSTEXPR bool value = true; + }; + } // namespace has_insertion_operator_impl + + template + using has_insertion_operator = has_insertion_operator_impl::check; + + DOCTEST_INTERFACE std::ostream* tlssPush(); + DOCTEST_INTERFACE String tlssPop(); + + + template + struct StringMakerBase + { + template + static String convert(const DOCTEST_REF_WRAP(T)) { + return "{?}"; + } + }; + + // Vector and various type other than pointer or array. + template + struct filldata + { + static void fill(std::ostream* stream, const T &in) { + *stream << in; + } + }; + + template + struct filldata + { + static void fill(std::ostream* stream, const T (&in)[N]) { + for (unsigned long i = 0; i < N; i++) { + *stream << in[i]; + } + } + }; + + // Specialized since we don't want the terminating null byte! + template + struct filldata + { + static void fill(std::ostream* stream, const char(&in)[N]) { + *stream << in; + } + }; + + template + void filloss(std::ostream* stream, const T& in) { + filldata::fill(stream, in); + } + + template + void filloss(std::ostream* stream, const T (&in)[N]) { + // T[N], T(&)[N], T(&&)[N] have same behaviour. + // Hence remove reference. + filldata::type>::fill(stream, in); + } + + template <> + struct StringMakerBase + { + template + static String convert(const DOCTEST_REF_WRAP(T) in) { + /* When parameter "in" is a null terminated const char* it works. + * When parameter "in" is a T arr[N] without '\0' we can fill the + * stringstream with N objects (T=char).If in is char pointer * + * without '\0' , it would cause segfault + * stepping over unaccessible memory. + */ + + std::ostream* stream = tlssPush(); + filloss(stream, in); + return tlssPop(); + } + }; + + DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); + + template + String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { + return rawMemoryToString(&object, sizeof(object)); + } + + template + const char* type_to_string() { + return "<>"; + } +} // namespace detail + +template +struct StringMaker : public detail::StringMakerBase::value> +{}; + +template +struct StringMaker +{ + template + static String convert(U* p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template +struct StringMaker +{ + static String convert(R C::*p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template ::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + return StringMaker::convert(value); +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(char* in); +DOCTEST_INTERFACE String toString(const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(bool in); +DOCTEST_INTERFACE String toString(float in); +DOCTEST_INTERFACE String toString(double in); +DOCTEST_INTERFACE String toString(double long in); + +DOCTEST_INTERFACE String toString(char in); +DOCTEST_INTERFACE String toString(char signed in); +DOCTEST_INTERFACE String toString(char unsigned in); +DOCTEST_INTERFACE String toString(int short in); +DOCTEST_INTERFACE String toString(int short unsigned in); +DOCTEST_INTERFACE String toString(int in); +DOCTEST_INTERFACE String toString(int unsigned in); +DOCTEST_INTERFACE String toString(int long in); +DOCTEST_INTERFACE String toString(int long unsigned in); +DOCTEST_INTERFACE String toString(int long long in); +DOCTEST_INTERFACE String toString(int long long unsigned in); +DOCTEST_INTERFACE String toString(std::nullptr_t in); + +template ::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + typedef typename detail::underlying_type::type UT; + return toString(static_cast(value)); +} + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +DOCTEST_INTERFACE String toString(const std::string& in); +#endif // VS 2019 + +class DOCTEST_INTERFACE Approx +{ +public: + explicit Approx(double value); + + Approx operator()(double value) const; + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + explicit Approx(const T& value, + typename detail::enable_if::value>::type* = + static_cast(nullptr)) { + *this = Approx(static_cast(value)); + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& epsilon(double newEpsilon); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename detail::enable_if::value, Approx&>::type epsilon( + const T& newEpsilon) { + m_epsilon = static_cast(newEpsilon); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& scale(double newScale); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename detail::enable_if::value, Approx&>::type scale( + const T& newScale) { + m_scale = static_cast(newScale); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); + + DOCTEST_INTERFACE friend String toString(const Approx& in); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_APPROX_PREFIX \ + template friend typename detail::enable_if::value, bool>::type + + DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } + DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } + DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } +#undef DOCTEST_APPROX_PREFIX +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format on + +private: + double m_epsilon; + double m_scale; + double m_value; +}; + +DOCTEST_INTERFACE String toString(const Approx& in); + +DOCTEST_INTERFACE const ContextOptions* getContextOptions(); + +#if !defined(DOCTEST_CONFIG_DISABLE) + +namespace detail { + // clang-format off +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + template struct decay_array { typedef T type; }; + template struct decay_array { typedef T* type; }; + template struct decay_array { typedef T* type; }; + + template struct not_char_pointer { enum { value = 1 }; }; + template<> struct not_char_pointer { enum { value = 0 }; }; + template<> struct not_char_pointer { enum { value = 0 }; }; + + template struct can_use_op : public not_char_pointer::type> {}; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + + struct DOCTEST_INTERFACE TestFailureException + { + }; + + DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_NORETURN +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_INTERFACE void throwException(); + + struct DOCTEST_INTERFACE Subcase + { + SubcaseSignature m_signature; + bool m_entered = false; + + Subcase(const String& name, const char* file, int line); + ~Subcase(); + + operator bool() const; + }; + + template + String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, + const DOCTEST_REF_WRAP(R) rhs) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + return toString(lhs) + op + toString(rhs); + } + +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") +#endif + +// This will check if there is any way it could find a operator like member or friend and uses it. +// If not it doesn't find the operator or if the operator at global scope is defined after +// this template, the template won't be instantiated due to SFINAE. Once the template is not +// instantiated it can look for global operator using normal conversions. +#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval() op doctest::detail::declval()),ret{}) + +#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ + template \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(const R&& rhs) { \ + bool res = op_macro(doctest::detail::forward(lhs), doctest::detail::forward(rhs)); \ + if(m_at & assertType::is_false) \ + res = !res; \ + if(!res || doctest::getContextOptions()->success) \ + return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ + return Result(res); \ + } \ + template ::value, void >::type* = nullptr> \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(const R& rhs) { \ + bool res = op_macro(doctest::detail::forward(lhs), rhs); \ + if(m_at & assertType::is_false) \ + res = !res; \ + if(!res || doctest::getContextOptions()->success) \ + return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ + return Result(res); \ + } + + // more checks could be added - like in Catch: + // https://github.com/catchorg/Catch2/pull/1480/files + // https://github.com/catchorg/Catch2/pull/1481/files +#define DOCTEST_FORBIT_EXPRESSION(rt, op) \ + template \ + rt& operator op(const R&) { \ + static_assert(deferred_false::value, \ + "Expression Too Complex Please Rewrite As Binary Comparison!"); \ + return *this; \ + } + + struct DOCTEST_INTERFACE Result + { + bool m_passed; + String m_decomp; + + Result() = default; + Result(bool passed, const String& decomposition = String()); + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Result, &) + DOCTEST_FORBIT_EXPRESSION(Result, ^) + DOCTEST_FORBIT_EXPRESSION(Result, |) + DOCTEST_FORBIT_EXPRESSION(Result, &&) + DOCTEST_FORBIT_EXPRESSION(Result, ||) + DOCTEST_FORBIT_EXPRESSION(Result, ==) + DOCTEST_FORBIT_EXPRESSION(Result, !=) + DOCTEST_FORBIT_EXPRESSION(Result, <) + DOCTEST_FORBIT_EXPRESSION(Result, >) + DOCTEST_FORBIT_EXPRESSION(Result, <=) + DOCTEST_FORBIT_EXPRESSION(Result, >=) + DOCTEST_FORBIT_EXPRESSION(Result, =) + DOCTEST_FORBIT_EXPRESSION(Result, +=) + DOCTEST_FORBIT_EXPRESSION(Result, -=) + DOCTEST_FORBIT_EXPRESSION(Result, *=) + DOCTEST_FORBIT_EXPRESSION(Result, /=) + DOCTEST_FORBIT_EXPRESSION(Result, %=) + DOCTEST_FORBIT_EXPRESSION(Result, <<=) + DOCTEST_FORBIT_EXPRESSION(Result, >>=) + DOCTEST_FORBIT_EXPRESSION(Result, &=) + DOCTEST_FORBIT_EXPRESSION(Result, ^=) + DOCTEST_FORBIT_EXPRESSION(Result, |=) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_GCC_SUPPRESS_WARNING_PUSH + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH + // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 + DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch + //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + // clang-format off +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE bool +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if::value || can_use_op::value, bool>::type + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } + inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } + inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } + inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } + inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } + inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ + const DOCTEST_REF_WRAP(R) rhs) { \ + return lhs op rhs; \ + } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) + +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) l == r +#define DOCTEST_CMP_NE(l, r) l != r +#define DOCTEST_CMP_GT(l, r) l > r +#define DOCTEST_CMP_LT(l, r) l < r +#define DOCTEST_CMP_GE(l, r) l >= r +#define DOCTEST_CMP_LE(l, r) l <= r +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) eq(l, r) +#define DOCTEST_CMP_NE(l, r) ne(l, r) +#define DOCTEST_CMP_GT(l, r) gt(l, r) +#define DOCTEST_CMP_LT(l, r) lt(l, r) +#define DOCTEST_CMP_GE(l, r) ge(l, r) +#define DOCTEST_CMP_LE(l, r) le(l, r) +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + + template + // cppcheck-suppress copyCtorAndEqOperator + struct Expression_lhs + { + L lhs; + assertType::Enum m_at; + + explicit Expression_lhs(L&& in, assertType::Enum at) + : lhs(doctest::detail::forward(in)) + , m_at(at) {} + + DOCTEST_NOINLINE operator Result() { +// this is needed only for MSVC 2015 +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool + bool res = static_cast(lhs); +DOCTEST_MSVC_SUPPRESS_WARNING_POP + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + res = !res; + + if(!res || getContextOptions()->success) + return Result(res, toString(lhs)); + return Result(res); + } + + /* This is required for user-defined conversions from Expression_lhs to L */ + operator L() const { return lhs; } + + // clang-format off + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional + // clang-format on + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) + // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the + // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_POP +#endif + + struct DOCTEST_INTERFACE ExpressionDecomposer + { + assertType::Enum m_at; + + ExpressionDecomposer(assertType::Enum at); + + // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) + // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... + // https://github.com/catchorg/Catch2/issues/870 + // https://github.com/catchorg/Catch2/issues/565 + template + Expression_lhs operator<<(const L &&operand) { + return Expression_lhs(doctest::detail::forward(operand), m_at); + } + + template ::value,void >::type* = nullptr> + Expression_lhs operator<<(const L &operand) { + return Expression_lhs(operand, m_at); + } + }; + + struct DOCTEST_INTERFACE TestSuite + { + const char* m_test_suite = nullptr; + const char* m_description = nullptr; + bool m_skip = false; + bool m_no_breaks = false; + bool m_no_output = false; + bool m_may_fail = false; + bool m_should_fail = false; + int m_expected_failures = 0; + double m_timeout = 0; + + TestSuite& operator*(const char* in); + + template + TestSuite& operator*(const T& in) { + in.fill(*this); + return *this; + } + }; + + typedef void (*funcType)(); + + struct DOCTEST_INTERFACE TestCase : public TestCaseData + { + funcType m_test; // a function pointer to the test case + + const char* m_type; // for templated test cases - gets appended to the real name + int m_template_id; // an ID used to distinguish between the different versions of a templated test case + String m_full_name; // contains the name (only for templated test cases!) + the template type + + TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type = "", int template_id = -1); + + TestCase(const TestCase& other); + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + TestCase& operator=(const TestCase& other); + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& operator*(const char* in); + + template + TestCase& operator*(const T& in) { + in.fill(*this); + return *this; + } + + bool operator<(const TestCase& other) const; + }; + + // forward declarations of functions used by the macros + DOCTEST_INTERFACE int regTest(const TestCase& tc); + DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); + DOCTEST_INTERFACE bool isDebuggerActive(); + + template + int instantiationHelper(const T&) { return 0; } + + namespace binaryAssertComparison { + enum Enum + { + eq = 0, + ne, + gt, + lt, + ge, + le + }; + } // namespace binaryAssertComparison + + // clang-format off + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; + +#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; + // clang-format on + + DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq) + DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne) + DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt) + DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt) + DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge) + DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le) + + struct DOCTEST_INTERFACE ResultBuilder : public AssertData + { + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type = "", const char* exception_string = ""); + + void setResult(const Result& res); + + template + DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + m_failed = !RelationalComparator()(lhs, rhs); + if(m_failed || getContextOptions()->success) + m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + return !m_failed; + } + + template + DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) { + m_failed = !val; + + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + m_failed = !m_failed; + + if(m_failed || getContextOptions()->success) + m_decomp = toString(val); + + return !m_failed; + } + + void translateException(); + + bool log(); + void react() const; + }; + + namespace assertAction { + enum Enum + { + nothing = 0, + dbgbreak = 1, + shouldthrow = 2 + }; + } // namespace assertAction + + DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); + + DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line, + const char* expr, Result result); + +#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ + do { \ + if(!is_running_in_test) { \ + if(failed) { \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + rb.m_decomp = decomp; \ + failed_out_of_a_testing_context(rb); \ + if(isDebuggerActive() && !getContextOptions()->no_breaks) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(checkIfShouldThrow(at)) \ + throwException(); \ + } \ + return !failed; \ + } \ + } while(false) + +#define DOCTEST_ASSERT_IN_TESTS(decomp) \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + if(rb.m_failed || getContextOptions()->success) \ + rb.m_decomp = decomp; \ + if(rb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(rb.m_failed && checkIfShouldThrow(at)) \ + throwException() + + template + DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + bool failed = !RelationalComparator()(lhs, rhs); + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + return !failed; + } + + template + DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) val) { + bool failed = !val; + + if(at & assertType::is_false) //!OCLINT bitwise operator in conditional + failed = !failed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); + DOCTEST_ASSERT_IN_TESTS(toString(val)); + return !failed; + } + + struct DOCTEST_INTERFACE IExceptionTranslator + { + IExceptionTranslator(); + virtual ~IExceptionTranslator(); + virtual bool translate(String&) const = 0; + }; + + template + class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class + { + public: + explicit ExceptionTranslator(String (*translateFunction)(T)) + : m_translateFunction(translateFunction) {} + + bool translate(String& res) const override { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { + throw; // lgtm [cpp/rethrow-no-exception] + // cppcheck-suppress catchExceptionByValue + } catch(T ex) { // NOLINT + res = m_translateFunction(ex); //!OCLINT parameter reassignment + return true; + } catch(...) {} //!OCLINT - empty catch statement +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + static_cast(res); // to silence -Wunused-parameter + return false; + } + + private: + String (*m_translateFunction)(T); + }; + + DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); + + template + struct StringStreamBase + { + template + static void convert(std::ostream* s, const T& in) { + *s << toString(in); + } + + // always treat char* as a string in this context - no matter + // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined + static void convert(std::ostream* s, const char* in) { *s << String(in); } + }; + + template <> + struct StringStreamBase + { + template + static void convert(std::ostream* s, const T& in) { + *s << in; + } + }; + + template + struct StringStream : public StringStreamBase::value> + {}; + + template + void toStream(std::ostream* s, const T& value) { + StringStream::convert(s, value); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); + DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); + DOCTEST_INTERFACE void toStream(std::ostream* s, float in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); + + DOCTEST_INTERFACE void toStream(std::ostream* s, char in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); + + // ContextScope base class used to allow implementing methods of ContextScope + // that don't depend on the template parameter in doctest.cpp. + class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { + protected: + ContextScopeBase(); + ContextScopeBase(ContextScopeBase&& other); + + void destroy(); + bool need_to_destroy{true}; + }; + + template class ContextScope : public ContextScopeBase + { + const L lambda_; + + public: + explicit ContextScope(const L &lambda) : lambda_(lambda) {} + + ContextScope(ContextScope &&other) : ContextScopeBase(static_cast(other)), lambda_(other.lambda_) {} + + void stringify(std::ostream* s) const override { lambda_(s); } + + ~ContextScope() override { + if (need_to_destroy) { + destroy(); + } + } + }; + + struct DOCTEST_INTERFACE MessageBuilder : public MessageData + { + std::ostream* m_stream; + bool logged = false; + + MessageBuilder(const char* file, int line, assertType::Enum severity); + MessageBuilder() = delete; + ~MessageBuilder(); + + // the preferred way of chaining parameters for stringification + template + MessageBuilder& operator,(const T& in) { + toStream(m_stream, in); + return *this; + } + + // kept here just for backwards-compatibility - the comma operator should be preferred now + template + MessageBuilder& operator<<(const T& in) { return this->operator,(in); } + + // the `,` operator has the lowest operator precedence - if `<<` is used by the user then + // the `,` operator will be called last which is not what we want and thus the `*` operator + // is used first (has higher operator precedence compared to `<<`) so that we guarantee that + // an operator of the MessageBuilder class is called first before the rest of the parameters + template + MessageBuilder& operator*(const T& in) { return this->operator,(in); } + + bool log(); + void react(); + }; + + template + ContextScope MakeContextScope(const L &lambda) { + return ContextScope(lambda); + } +} // namespace detail + +#define DOCTEST_DEFINE_DECORATOR(name, type, def) \ + struct name \ + { \ + type data; \ + name(type in = def) \ + : data(in) {} \ + void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + } + +DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); +DOCTEST_DEFINE_DECORATOR(description, const char*, ""); +DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true); +DOCTEST_DEFINE_DECORATOR(no_output, bool, true); +DOCTEST_DEFINE_DECORATOR(timeout, double, 0); +DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); + +template +int registerExceptionTranslator(String (*translateFunction)(T)) { + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") + static detail::ExceptionTranslator exceptionTranslator(translateFunction); + DOCTEST_CLANG_SUPPRESS_WARNING_POP + detail::registerExceptionTranslatorImpl(&exceptionTranslator); + return 0; +} + +} // namespace doctest + +// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro +// introduces an anonymous namespace in which getCurrentTestSuite gets overridden +namespace doctest_detail_test_suite_ns { +DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +#else // DOCTEST_CONFIG_DISABLE +template +int registerExceptionTranslator(String (*)(T)) { + return 0; +} +#endif // DOCTEST_CONFIG_DISABLE + +namespace detail { + typedef void (*assert_handler)(const AssertData&); + struct ContextState; +} // namespace detail + +class DOCTEST_INTERFACE Context +{ + detail::ContextState* p; + + void parseArgs(int argc, const char* const* argv, bool withDefaults = false); + +public: + explicit Context(int argc = 0, const char* const* argv = nullptr); + + ~Context(); + + void applyCommandLine(int argc, const char* const* argv); + + void addFilter(const char* filter, const char* value); + void clearFilters(); + void setOption(const char* option, bool value); + void setOption(const char* option, int value); + void setOption(const char* option, const char* value); + + bool shouldExit(); + + void setAsDefaultForAssertsOutOfTestCases(); + + void setAssertHandler(detail::assert_handler ah); + + void setCout(std::ostream* out); + + int run(); +}; + +namespace TestCaseFailureReason { + enum Enum + { + None = 0, + AssertFailure = 1, // an assertion has failed in the test case + Exception = 2, // test case threw an exception + Crash = 4, // a crash... + TooManyFailedAsserts = 8, // the abort-after option + Timeout = 16, // see the timeout decorator + ShouldHaveFailedButDidnt = 32, // see the should_fail decorator + ShouldHaveFailedAndDid = 64, // see the should_fail decorator + DidntFailExactlyNumTimes = 128, // see the expected_failures decorator + FailedExactlyNumTimes = 256, // see the expected_failures decorator + CouldHaveFailedAndDid = 512 // see the may_fail decorator + }; +} // namespace TestCaseFailureReason + +struct DOCTEST_INTERFACE CurrentTestCaseStats +{ + int numAssertsCurrentTest; + int numAssertsFailedCurrentTest; + double seconds; + int failure_flags; // use TestCaseFailureReason::Enum + bool testCaseSuccess; +}; + +struct DOCTEST_INTERFACE TestCaseException +{ + String error_string; + bool is_crash; +}; + +struct DOCTEST_INTERFACE TestRunStats +{ + unsigned numTestCases; + unsigned numTestCasesPassingFilters; + unsigned numTestSuitesPassingFilters; + unsigned numTestCasesFailed; + int numAsserts; + int numAssertsFailed; +}; + +struct QueryData +{ + const TestRunStats* run_stats = nullptr; + const TestCaseData** data = nullptr; + unsigned num_data = 0; +}; + +struct DOCTEST_INTERFACE IReporter +{ + // The constructor has to accept "const ContextOptions&" as a single argument + // which has most of the options for the run + a pointer to the stdout stream + // Reporter(const ContextOptions& in) + + // called when a query should be reported (listing test cases, printing the version, etc.) + virtual void report_query(const QueryData&) = 0; + + // called when the whole test run starts + virtual void test_run_start() = 0; + // called when the whole test run ends (caching a pointer to the input doesn't make sense here) + virtual void test_run_end(const TestRunStats&) = 0; + + // called when a test case is started (safe to cache a pointer to the input) + virtual void test_case_start(const TestCaseData&) = 0; + // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) + virtual void test_case_reenter(const TestCaseData&) = 0; + // called when a test case has ended + virtual void test_case_end(const CurrentTestCaseStats&) = 0; + + // called when an exception is thrown from the test case (or it crashes) + virtual void test_case_exception(const TestCaseException&) = 0; + + // called whenever a subcase is entered (don't cache pointers to the input) + virtual void subcase_start(const SubcaseSignature&) = 0; + // called whenever a subcase is exited (don't cache pointers to the input) + virtual void subcase_end() = 0; + + // called for each assert (don't cache pointers to the input) + virtual void log_assert(const AssertData&) = 0; + // called for each message (don't cache pointers to the input) + virtual void log_message(const MessageData&) = 0; + + // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator + // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) + virtual void test_case_skipped(const TestCaseData&) = 0; + + // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have + virtual ~IReporter(); + + // can obtain all currently active contexts and stringify them if one wishes to do so + static int get_num_active_contexts(); + static const IContextScope* const* get_active_contexts(); + + // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown + static int get_num_stringified_contexts(); + static const String* get_stringified_contexts(); +}; + +namespace detail { + typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); + + DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); + + template + IReporter* reporterCreator(const ContextOptions& o) { + return new Reporter(o); + } +} // namespace detail + +template +int registerReporter(const char* name, int priority, bool isReporter) { + detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); + return 0; +} +} // namespace doctest + +// if registering is not disabled +#if !defined(DOCTEST_CONFIG_DISABLE) + +// common code in asserts - for convenience +#define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \ + if(b.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + b.react(); \ + return !b.m_failed + +#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) x; +#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) \ + try { \ + x; \ + } catch(...) { DOCTEST_RB.translateException(); } +#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...) \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ + static_cast(__VA_ARGS__); \ + DOCTEST_GCC_SUPPRESS_WARNING_POP +#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; +#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS + +// registers the test by initializing a dummy var with a function +#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ + doctest::detail::regTest( \ + doctest::detail::TestCase( \ + f, __FILE__, __LINE__, \ + doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ + decorators)) + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ + namespace { \ + struct der : public base \ + { \ + void f(); \ + }; \ + static void func() { \ + der v; \ + v.f(); \ + } \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ + } \ + inline DOCTEST_NOINLINE void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ + static void f(); \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ + static void f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ + static doctest::detail::funcType proxy() { return f; } \ + DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \ + static void f() + +// for registering tests +#define DOCTEST_TEST_CASE(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) + +// for registering tests in classes - requires C++17 for inline variables! +#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) +#define DOCTEST_TEST_CASE_CLASS(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \ + decorators) +#else // DOCTEST_TEST_CASE_CLASS +#define DOCTEST_TEST_CASE_CLASS(...) \ + TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER +#endif // DOCTEST_TEST_CASE_CLASS + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ + template <> \ + inline const char* type_to_string<__VA_ARGS__>() { \ + return "<" #__VA_ARGS__ ">"; \ + } +#define DOCTEST_TYPE_TO_STRING(...) \ + namespace doctest { namespace detail { \ + DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ + } \ + } \ + static_assert(true, "") + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ + template \ + static void func(); \ + namespace { \ + template \ + struct iter; \ + template \ + struct iter> \ + { \ + iter(const char* file, unsigned line, int index) { \ + doctest::detail::regTest(doctest::detail::TestCase(func, file, line, \ + doctest_detail_test_suite_ns::getCurrentTestSuite(), \ + doctest::detail::type_to_string(), \ + int(line) * 1000 + index) \ + * dec); \ + iter>(file, line, index + 1); \ + } \ + }; \ + template <> \ + struct iter> \ + { \ + iter(const char*, unsigned, int) {} \ + }; \ + } \ + template \ + static void func() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)) + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), \ + doctest::detail::instantiationHelper( \ + DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0))) + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ + static_assert(true, "") + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \ + static_assert(true, "") + +#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \ + template \ + static void anon() + +#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) + +// for subcases +#define DOCTEST_SUBCASE(name) \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + doctest::detail::Subcase(name, __FILE__, __LINE__)) + +// for grouping tests in test suites by using code blocks +#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ + namespace ns_name { namespace doctest_detail_test_suite_ns { \ + static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ + static doctest::detail::TestSuite data{}; \ + static bool inited = false; \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + if(!inited) { \ + data* decorators; \ + inited = true; \ + } \ + return data; \ + } \ + } \ + } \ + namespace ns_name + +#define DOCTEST_TEST_SUITE(decorators) \ + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_)) + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \ + static_assert(true, "") + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \ + typedef int DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering exception translators +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ + inline doctest::String translatorName(signature); \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ + doctest::registerExceptionTranslator(translatorName)) \ + doctest::String translatorName(signature) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ + signature) + +// for registering reporters +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), \ + doctest::registerReporter(name, priority, true)) \ + static_assert(true, "") + +// for registering listeners +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), \ + doctest::registerReporter(name, priority, false)) \ + static_assert(true, "") + +// clang-format off +// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557 +#define DOCTEST_INFO(...) \ + DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \ + DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \ + __VA_ARGS__) +// clang-format on + +#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ + auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ + [&](std::ostream* s_name) { \ + doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ + mb_name.m_stream = s_name; \ + mb_name * __VA_ARGS__; \ + }) + +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) + +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ + [&] { \ + doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ + mb * __VA_ARGS__; \ + if(mb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + mb.react(); \ + }() + +// clang-format off +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +// clang-format on + +#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__) + +#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + [&] { \ + DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ + }() + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +// necessary for _MESSAGE +#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::decomp_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) +#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) +#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) +#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) + +// clang-format off +#define DOCTEST_WARN_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); }() +#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); }() +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); }() +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); }() +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); }() +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); }() +// clang-format on + +#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ + [&] { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, #__VA_ARGS__, message); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(const typename doctest::detail::remove_const< \ + typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ + DOCTEST_RB.translateException(); \ + DOCTEST_RB.m_threw_as = true; \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { \ + return false; \ + } \ + }() + +#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ + [&] { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, expr_str, "", __VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { \ + return false; \ + } \ + }() + +#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ + [&] { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + }() + +// clang-format off +#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") +#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") + +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) + +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); }() +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); }() +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); }() +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); }() +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); }() +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); }() +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); }() +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); }() +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); }() +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); }() +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); }() +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); }() +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); }() +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); }() +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); }() +// clang-format on + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ + [&] { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY( \ + DOCTEST_RB.binary_assert( \ + __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + }() + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + [&] { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + }() + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ + doctest::detail::binary_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ + #__VA_ARGS__, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#undef DOCTEST_WARN_THROWS +#undef DOCTEST_CHECK_THROWS +#undef DOCTEST_REQUIRE_THROWS +#undef DOCTEST_WARN_THROWS_AS +#undef DOCTEST_CHECK_THROWS_AS +#undef DOCTEST_REQUIRE_THROWS_AS +#undef DOCTEST_WARN_THROWS_WITH +#undef DOCTEST_CHECK_THROWS_WITH +#undef DOCTEST_REQUIRE_THROWS_WITH +#undef DOCTEST_WARN_THROWS_WITH_AS +#undef DOCTEST_CHECK_THROWS_WITH_AS +#undef DOCTEST_REQUIRE_THROWS_WITH_AS +#undef DOCTEST_WARN_NOTHROW +#undef DOCTEST_CHECK_NOTHROW +#undef DOCTEST_REQUIRE_NOTHROW + +#undef DOCTEST_WARN_THROWS_MESSAGE +#undef DOCTEST_CHECK_THROWS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_MESSAGE +#undef DOCTEST_WARN_THROWS_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_WARN_NOTHROW_MESSAGE +#undef DOCTEST_CHECK_NOTHROW_MESSAGE +#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS(...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS(...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_WARN_NOTHROW(...) ([] { return false; }) +#define DOCTEST_CHECK_NOTHROW(...) ([] { return false; }) +#define DOCTEST_REQUIRE_NOTHROW(...) ([] { return false; }) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) + +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// ================================================================================================= +// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == +// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == +// ================================================================================================= +#else // DOCTEST_CONFIG_DISABLE + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ + namespace { \ + template \ + struct der : public base \ + { void f(); }; \ + } \ + template \ + inline void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ + template \ + static inline void f() + +// for registering tests +#define DOCTEST_TEST_CASE(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) + +// for registering tests in classes +#define DOCTEST_TEST_CASE_CLASS(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "") +#define DOCTEST_TYPE_TO_STRING_IMPL(...) + +// for typed tests +#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ + template \ + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ + template \ + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "") +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "") + +// for subcases +#define DOCTEST_SUBCASE(name) + +// for a testsuite block +#define DOCTEST_TEST_SUITE(name) namespace + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "") + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + template \ + static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature) + +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) + +#define DOCTEST_INFO(...) (static_cast(0)) +#define DOCTEST_CAPTURE(x) (static_cast(0)) +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_MESSAGE(...) (static_cast(0)) +#define DOCTEST_FAIL_CHECK(...) (static_cast(0)) +#define DOCTEST_FAIL(...) (static_cast(0)) + +#ifdef DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() + +namespace doctest { +namespace detail { +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) +} // namespace detail +} // namespace doctest + +#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +#define DOCTEST_WARN(...) ([] { return false; }) +#define DOCTEST_CHECK(...) ([] { return false; }) +#define DOCTEST_REQUIRE(...) ([] { return false; }) +#define DOCTEST_WARN_FALSE(...) ([] { return false; }) +#define DOCTEST_CHECK_FALSE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_FALSE(...) ([] { return false; }) + +#define DOCTEST_WARN_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_CHECK_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) ([] { return false; }) + +#define DOCTEST_WARN_EQ(...) ([] { return false; }) +#define DOCTEST_CHECK_EQ(...) ([] { return false; }) +#define DOCTEST_REQUIRE_EQ(...) ([] { return false; }) +#define DOCTEST_WARN_NE(...) ([] { return false; }) +#define DOCTEST_CHECK_NE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_NE(...) ([] { return false; }) +#define DOCTEST_WARN_GT(...) ([] { return false; }) +#define DOCTEST_CHECK_GT(...) ([] { return false; }) +#define DOCTEST_REQUIRE_GT(...) ([] { return false; }) +#define DOCTEST_WARN_LT(...) ([] { return false; }) +#define DOCTEST_CHECK_LT(...) ([] { return false; }) +#define DOCTEST_REQUIRE_LT(...) ([] { return false; }) +#define DOCTEST_WARN_GE(...) ([] { return false; }) +#define DOCTEST_CHECK_GE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_GE(...) ([] { return false; }) +#define DOCTEST_WARN_LE(...) ([] { return false; }) +#define DOCTEST_CHECK_LE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_LE(...) ([] { return false; }) + +#define DOCTEST_WARN_UNARY(...) ([] { return false; }) +#define DOCTEST_CHECK_UNARY(...) ([] { return false; }) +#define DOCTEST_REQUIRE_UNARY(...) ([] { return false; }) +#define DOCTEST_WARN_UNARY_FALSE(...) ([] { return false; }) +#define DOCTEST_CHECK_UNARY_FALSE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) ([] { return false; }) + +#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +// TODO: think about if these also need to work properly even when doctest is disabled +#define DOCTEST_WARN_THROWS(...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS(...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS(...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_WARN_NOTHROW(...) ([] { return false; }) +#define DOCTEST_CHECK_NOTHROW(...) ([] { return false; }) +#define DOCTEST_REQUIRE_NOTHROW(...) ([] { return false; }) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) + +#endif // DOCTEST_CONFIG_DISABLE + +// clang-format off +// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS +#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ +#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ +#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE +#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE +#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE +#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT +#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT +#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT +#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT +#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT +#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT +#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE +#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE +#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE +#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE +#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE +#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE + +#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY +#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY +#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__) +// clang-format on + +// BDD style macros +// clang-format off +#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) +#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) +#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) +#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) + +#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name) +#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name) +#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name) +#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name) +#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name) +// clang-format on + +// == SHORT VERSIONS OF THE MACROS +#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) + +#define TEST_CASE(name) DOCTEST_TEST_CASE(name) +#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) +#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) +#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) +#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) +#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__) +#define SUBCASE(name) DOCTEST_SUBCASE(name) +#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators) +#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name) +#define TEST_SUITE_END DOCTEST_TEST_SUITE_END +#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) +#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter) +#define INFO(...) DOCTEST_INFO(__VA_ARGS__) +#define CAPTURE(x) DOCTEST_CAPTURE(x) +#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__) +#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__) +#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__) +#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__) +#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__) + +#define WARN(...) DOCTEST_WARN(__VA_ARGS__) +#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__) +#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__) +#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__) +#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__) +#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__) +#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__) +#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__) +#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__) +#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__) +#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__) +#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__) +#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__) +#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__) + +#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__) +#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__) +#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__) +#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__) +#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__) +#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__) +#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__) + +#define SCENARIO(name) DOCTEST_SCENARIO(name) +#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name) +#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__) +#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) +#define GIVEN(name) DOCTEST_GIVEN(name) +#define WHEN(name) DOCTEST_WHEN(name) +#define AND_WHEN(name) DOCTEST_AND_WHEN(name) +#define THEN(name) DOCTEST_THEN(name) +#define AND_THEN(name) DOCTEST_AND_THEN(name) + +#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__) +#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__) +#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__) +#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__) +#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__) +#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__) +#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__) +#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__) +#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__) +#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__) +#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__) +#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__) +#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__) +#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__) +#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__) +#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__) +#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__) +#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__) +#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__) +#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__) +#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__) +#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__) +#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__) + +// KEPT FOR BACKWARDS COMPATIBILITY +#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__) +#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__) +#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__) +#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__) +#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__) +#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__) +#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__) +#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__) +#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__) +#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__) +#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__) +#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__) +#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__) +#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__) +#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__) +#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__) +#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__) +#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__) + +#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__) +#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__) +#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__) +#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__) +#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__) + +#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#if !defined(DOCTEST_CONFIG_DISABLE) + +// this is here to clear the 'current test suite' for the current translation unit - at the top +DOCTEST_TEST_SUITE_END(); + +// add stringification for primitive/fundamental types +namespace doctest { namespace detail { + DOCTEST_TYPE_TO_STRING_IMPL(bool) + DOCTEST_TYPE_TO_STRING_IMPL(float) + DOCTEST_TYPE_TO_STRING_IMPL(double) + DOCTEST_TYPE_TO_STRING_IMPL(long double) + DOCTEST_TYPE_TO_STRING_IMPL(char) + DOCTEST_TYPE_TO_STRING_IMPL(signed char) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) +#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) + DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) +#endif // not MSVC or wchar_t support enabled + DOCTEST_TYPE_TO_STRING_IMPL(short int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) + DOCTEST_TYPE_TO_STRING_IMPL(int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) + DOCTEST_TYPE_TO_STRING_IMPL(long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) + DOCTEST_TYPE_TO_STRING_IMPL(long long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) +}} // namespace doctest::detail + +#endif // DOCTEST_CONFIG_DISABLE + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + +#endif // DOCTEST_LIBRARY_INCLUDED + +#ifndef DOCTEST_SINGLE_HEADER +#define DOCTEST_SINGLE_HEADER +#endif // DOCTEST_SINGLE_HEADER + +#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) + +#ifndef DOCTEST_SINGLE_HEADER +#include "doctest_fwd.h" +#endif // DOCTEST_SINGLE_HEADER + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") + +#ifndef DOCTEST_LIBRARY_IMPLEMENTATION +#define DOCTEST_LIBRARY_IMPLEMENTATION + +DOCTEST_CLANG_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data +DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled +DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified +DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal +DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch +DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C +DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) +DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN + +// required includes - will go only in one translation unit! +#include +#include +#include +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37 +#ifdef __BORLANDC__ +#include +#endif // __BORLANDC__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DOCTEST_PLATFORM_MAC +#include +#include +#include +#endif // DOCTEST_PLATFORM_MAC + +#ifdef DOCTEST_PLATFORM_WINDOWS + +// defines for a leaner windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// not sure what AfxWin.h is for - here I do what Catch does +#ifdef __AFXDLL +#include +#else +#include +#endif +#include + +#else // DOCTEST_PLATFORM_WINDOWS + +#include +#include + +#endif // DOCTEST_PLATFORM_WINDOWS + +// this is a fix for https://github.com/doctest/doctest/issues/348 +// https://mail.gnome.org/archives/xml/2012-January/msg00000.html +#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) +#define STDOUT_FILENO fileno(stdout) +#endif // HAVE_UNISTD_H + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END + +// counts the number of elements in a C array +#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) + +#ifdef DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled +#else // DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled +#endif // DOCTEST_CONFIG_DISABLE + +#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX +#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" +#endif + +#ifndef DOCTEST_THREAD_LOCAL +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_THREAD_LOCAL +#else // DOCTEST_MSVC +#define DOCTEST_THREAD_LOCAL thread_local +#endif // DOCTEST_MSVC +#endif // DOCTEST_THREAD_LOCAL + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES +#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 +#endif + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE +#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64 +#endif + +#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX +#else +#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" +#endif + +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS +#endif + +#ifndef DOCTEST_CDECL +#define DOCTEST_CDECL __cdecl +#endif + +namespace doctest { + +bool is_running_in_test = false; + +namespace { + using namespace detail; + + template + DOCTEST_NORETURN void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + std::cerr << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#ifndef DOCTEST_INTERNAL_ERROR +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) +#endif // DOCTEST_INTERNAL_ERROR + + // case insensitive strcmp + int stricmp(const char* a, const char* b) { + for(;; a++, b++) { + const int d = tolower(*a) - tolower(*b); + if(d != 0 || !*a) + return d; + } + } + + template + String fpToString(T value, int precision) { + std::ostringstream oss; + oss << std::setprecision(precision) << std::fixed << value; + std::string d = oss.str(); + size_t i = d.find_last_not_of('0'); + if(i != std::string::npos && i != d.size() - 1) { + if(d[i] == '.') + i++; + d = d.substr(0, i + 1); + } + return d.c_str(); + } + + struct Endianness + { + enum Arch + { + Big, + Little + }; + + static Arch which() { + int x = 1; + // casting any data pointer to char* is allowed + auto ptr = reinterpret_cast(&x); + if(*ptr) + return Little; + return Big; + } + }; +} // namespace + +namespace detail { + String rawMemoryToString(const void* object, unsigned size) { + // Reverse order for little endian architectures + int i = 0, end = static_cast(size), inc = 1; + if(Endianness::which() == Endianness::Little) { + i = end - 1; + end = inc = -1; + } + + unsigned const char* bytes = static_cast(object); + std::ostream* oss = tlssPush(); + *oss << "0x" << std::setfill('0') << std::hex; + for(; i != end; i += inc) + *oss << std::setw(2) << static_cast(bytes[i]); + return tlssPop(); + } + + DOCTEST_THREAD_LOCAL class + { + std::vector stack; + std::stringstream ss; + + public: + std::ostream* push() { + stack.push_back(ss.tellp()); + return &ss; + } + + String pop() { + if (stack.empty()) + DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!"); + + std::streampos pos = stack.back(); + stack.pop_back(); + unsigned sz = static_cast(ss.tellp() - pos); + ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out); + return String(ss, sz); + } + } g_oss; + + std::ostream* tlssPush() { + return g_oss.push(); + } + + String tlssPop() { + return g_oss.pop(); + } + +#ifndef DOCTEST_CONFIG_DISABLE + +namespace timer_large_integer +{ + +#if defined(DOCTEST_PLATFORM_WINDOWS) + typedef ULONGLONG type; +#else // DOCTEST_PLATFORM_WINDOWS + typedef std::uint64_t type; +#endif // DOCTEST_PLATFORM_WINDOWS +} + +typedef timer_large_integer::type ticks_t; + +#ifdef DOCTEST_CONFIG_GETCURRENTTICKS + ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } +#elif defined(DOCTEST_PLATFORM_WINDOWS) + ticks_t getCurrentTicks() { + static LARGE_INTEGER hz = {0}, hzo = {0}; + if(!hz.QuadPart) { + QueryPerformanceFrequency(&hz); + QueryPerformanceCounter(&hzo); + } + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; + } +#else // DOCTEST_PLATFORM_WINDOWS + ticks_t getCurrentTicks() { + timeval t; + gettimeofday(&t, nullptr); + return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); + } +#endif // DOCTEST_PLATFORM_WINDOWS + + struct Timer + { + void start() { m_ticks = getCurrentTicks(); } + unsigned int getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + //unsigned int getElapsedMilliseconds() const { + // return static_cast(getElapsedMicroseconds() / 1000); + //} + double getElapsedSeconds() const { return static_cast(getCurrentTicks() - m_ticks) / 1000000.0; } + + private: + ticks_t m_ticks = 0; + }; + +#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + template + using AtomicOrMultiLaneAtomic = std::atomic; +#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + // Provides a multilane implementation of an atomic variable that supports add, sub, load, + // store. Instead of using a single atomic variable, this splits up into multiple ones, + // each sitting on a separate cache line. The goal is to provide a speedup when most + // operations are modifying. It achieves this with two properties: + // + // * Multiple atomics are used, so chance of congestion from the same atomic is reduced. + // * Each atomic sits on a separate cache line, so false sharing is reduced. + // + // The disadvantage is that there is a small overhead due to the use of TLS, and load/store + // is slower because all atomics have to be accessed. + template + class MultiLaneAtomic + { + struct CacheLineAlignedAtomic + { + std::atomic atomic{}; + char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic)]; + }; + CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; + + static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE, + "guarantee one atomic takes exactly one cache line"); + + public: + T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; } + + T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); } + + T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_add(arg, order); + } + + T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_sub(arg, order); + } + + operator T() const DOCTEST_NOEXCEPT { return load(); } + + T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT { + auto result = T(); + for(auto const& c : m_atomics) { + result += c.atomic.load(order); + } + return result; + } + + T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this] + store(desired); + return desired; + } + + void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + // first value becomes desired", all others become 0. + for(auto& c : m_atomics) { + c.atomic.store(desired, order); + desired = {}; + } + } + + private: + // Each thread has a different atomic that it operates on. If more than NumLanes threads + // use this, some will use the same atomic. So performance will degrade a bit, but still + // everything will work. + // + // The logic here is a bit tricky. The call should be as fast as possible, so that there + // is minimal to no overhead in determining the correct atomic for the current thread. + // + // 1. A global static counter laneCounter counts continuously up. + // 2. Each successive thread will use modulo operation of that counter so it gets an atomic + // assigned in a round-robin fashion. + // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with + // little overhead. + std::atomic& myAtomic() DOCTEST_NOEXCEPT { + static std::atomic laneCounter; + DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = + laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; + + return m_atomics[tlsLaneIdx].atomic; + } + }; + + template + using AtomicOrMultiLaneAtomic = MultiLaneAtomic; +#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + + // this holds both parameters from the command line and runtime data for tests + struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats + { + AtomicOrMultiLaneAtomic numAssertsCurrentTest_atomic; + AtomicOrMultiLaneAtomic numAssertsFailedCurrentTest_atomic; + + std::vector> filters = decltype(filters)(9); // 9 different filters + + std::vector reporters_currently_used; + + assert_handler ah = nullptr; + + Timer timer; + + std::vector stringifiedContexts; // logging from INFO() due to an exception + + // stuff for subcases + std::vector subcasesStack; + std::set subcasesPassed; + int subcasesCurrentMaxLevel; + bool should_reenter; + std::atomic shouldLogCurrentException; + + void resetRunData() { + numTestCases = 0; + numTestCasesPassingFilters = 0; + numTestSuitesPassingFilters = 0; + numTestCasesFailed = 0; + numAsserts = 0; + numAssertsFailed = 0; + numAssertsCurrentTest = 0; + numAssertsFailedCurrentTest = 0; + } + + void finalizeTestCaseData() { + seconds = timer.getElapsedSeconds(); + + // update the non-atomic counters + numAsserts += numAssertsCurrentTest_atomic; + numAssertsFailed += numAssertsFailedCurrentTest_atomic; + numAssertsCurrentTest = numAssertsCurrentTest_atomic; + numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; + + if(numAssertsFailedCurrentTest) + failure_flags |= TestCaseFailureReason::AssertFailure; + + if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && + Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) + failure_flags |= TestCaseFailureReason::Timeout; + + if(currentTest->m_should_fail) { + if(failure_flags) { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; + } else { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; + } + } else if(failure_flags && currentTest->m_may_fail) { + failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; + } else if(currentTest->m_expected_failures > 0) { + if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { + failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; + } else { + failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; + } + } + + bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); + + // if any subcase has failed - the whole test case has failed + testCaseSuccess = !(failure_flags && !ok_to_fail); + if(!testCaseSuccess) + numTestCasesFailed++; + } + }; + + ContextState* g_cs = nullptr; + + // used to avoid locks for the debug output + // TODO: figure out if this is indeed necessary/correct - seems like either there still + // could be a race or that there wouldn't be a race even if using the context directly + DOCTEST_THREAD_LOCAL bool g_no_colors; + +#endif // DOCTEST_CONFIG_DISABLE +} // namespace detail + +char* String::allocate(unsigned sz) { + if (sz <= last) { + buf[sz] = '\0'; + setLast(last - sz); + return buf; + } else { + setOnHeap(); + data.size = sz; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + data.ptr[sz] = '\0'; + return data.ptr; + } +} + +void String::setOnHeap() { *reinterpret_cast(&buf[last]) = 128; } +void String::setLast(unsigned in) { buf[last] = char(in); } + +void String::copy(const String& other) { + if(other.isOnStack()) { + memcpy(buf, other.buf, len); + } else { + memcpy(allocate(other.data.size), other.data.ptr, other.data.size); + } +} + +String::String() { + buf[0] = '\0'; + setLast(); +} + +String::~String() { + if(!isOnStack()) + delete[] data.ptr; + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +} + +String::String(const char* in) + : String(in, strlen(in)) {} + +String::String(const char* in, unsigned in_size) { + memcpy(allocate(in_size), in, in_size); +} + +String::String(std::istream& in, unsigned in_size) { + in.read(allocate(in_size), in_size); +} + +String::String(const String& other) { copy(other); } + +String& String::operator=(const String& other) { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + + copy(other); + } + + return *this; +} + +String& String::operator+=(const String& other) { + const unsigned my_old_size = size(); + const unsigned other_size = other.size(); + const unsigned total_size = my_old_size + other_size; + if(isOnStack()) { + if(total_size < len) { + // append to the current stack space + memcpy(buf + my_old_size, other.c_str(), other_size + 1); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + setLast(last - total_size); + } else { + // alloc new chunk + char* temp = new char[total_size + 1]; + // copy current data to new location before writing in the union + memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed + // update data in union + setOnHeap(); + data.size = total_size; + data.capacity = data.size + 1; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } else { + if(data.capacity > total_size) { + // append to the current heap block + data.size = total_size; + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } else { + // resize + data.capacity *= 2; + if(data.capacity <= total_size) + data.capacity = total_size + 1; + // alloc new chunk + char* temp = new char[data.capacity]; + // copy current data to new location before releasing it + memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed + // release old chunk + delete[] data.ptr; + // update the rest of the union members + data.size = total_size; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } + + return *this; +} + +String::String(String&& other) { + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); +} + +String& String::operator=(String&& other) { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); + } + return *this; +} + +char String::operator[](unsigned i) const { + return const_cast(this)->operator[](i); // NOLINT +} + +char& String::operator[](unsigned i) { + if(isOnStack()) + return reinterpret_cast(buf)[i]; + return data.ptr[i]; +} + +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") +unsigned String::size() const { + if(isOnStack()) + return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 + return data.size; +} +DOCTEST_GCC_SUPPRESS_WARNING_POP + +unsigned String::capacity() const { + if(isOnStack()) + return len; + return data.capacity; +} + +int String::compare(const char* other, bool no_case) const { + if(no_case) + return doctest::stricmp(c_str(), other); + return std::strcmp(c_str(), other); +} + +int String::compare(const String& other, bool no_case) const { + return compare(other.c_str(), no_case); +} + +// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; } + +// clang-format off +bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } +bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } +bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } +bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } +bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } +bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } +// clang-format on + +std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } + +namespace { + void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) +} // namespace + +namespace Color { + std::ostream& operator<<(std::ostream& s, Color::Enum code) { + color_to_stream(s, code); + return s; + } +} // namespace Color + +// clang-format off +const char* assertString(assertType::Enum at) { + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled + switch(at) { //!OCLINT missing default in switch statements + case assertType::DT_WARN : return "WARN"; + case assertType::DT_CHECK : return "CHECK"; + case assertType::DT_REQUIRE : return "REQUIRE"; + + case assertType::DT_WARN_FALSE : return "WARN_FALSE"; + case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; + case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; + + case assertType::DT_WARN_THROWS : return "WARN_THROWS"; + case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; + case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; + + case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; + case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; + case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; + + case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH"; + case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH"; + case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH"; + + case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS"; + case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS"; + case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS"; + + case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; + case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; + case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; + + case assertType::DT_WARN_EQ : return "WARN_EQ"; + case assertType::DT_CHECK_EQ : return "CHECK_EQ"; + case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; + case assertType::DT_WARN_NE : return "WARN_NE"; + case assertType::DT_CHECK_NE : return "CHECK_NE"; + case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; + case assertType::DT_WARN_GT : return "WARN_GT"; + case assertType::DT_CHECK_GT : return "CHECK_GT"; + case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; + case assertType::DT_WARN_LT : return "WARN_LT"; + case assertType::DT_CHECK_LT : return "CHECK_LT"; + case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; + case assertType::DT_WARN_GE : return "WARN_GE"; + case assertType::DT_CHECK_GE : return "CHECK_GE"; + case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; + case assertType::DT_WARN_LE : return "WARN_LE"; + case assertType::DT_CHECK_LE : return "CHECK_LE"; + case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; + + case assertType::DT_WARN_UNARY : return "WARN_UNARY"; + case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; + case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; + case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; + case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; + case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + return ""; +} +// clang-format on + +const char* failureString(assertType::Enum at) { + if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional + return "WARNING"; + if(at & assertType::is_check) //!OCLINT bitwise operator in conditional + return "ERROR"; + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return "FATAL ERROR"; + return ""; +} + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +// depending on the current options this will remove the path of filenames +const char* skipPathFromFilename(const char* file) { +#ifndef DOCTEST_CONFIG_DISABLE + if(getContextOptions()->no_path_in_filenames) { + auto back = std::strrchr(file, '\\'); + auto forward = std::strrchr(file, '/'); + if(back || forward) { + if(back > forward) + forward = back; + return forward + 1; + } + } +#endif // DOCTEST_CONFIG_DISABLE + return file; +} +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +bool SubcaseSignature::operator<(const SubcaseSignature& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + if(std::strcmp(m_file, other.m_file) != 0) + return std::strcmp(m_file, other.m_file) < 0; + return m_name.compare(other.m_name) < 0; +} + +IContextScope::IContextScope() = default; +IContextScope::~IContextScope() = default; + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(char* in) { return toString(static_cast(in)); } +// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(bool in) { return in ? "true" : "false"; } +String toString(float in) { return fpToString(in, 5) + "f"; } +String toString(double in) { return fpToString(in, 10); } +String toString(double long in) { return fpToString(in, 15); } + +#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \ + String toString(type in) { \ + char buf[64]; \ + std::sprintf(buf, fmt, in); \ + return buf; \ + } + +DOCTEST_TO_STRING_OVERLOAD(char, "%d") +DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") +DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int short, "%d") +DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int, "%d") +DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") +DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") +DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") +DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") + +String toString(std::nullptr_t) { return "NULL"; } + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +String toString(const std::string& in) { return in.c_str(); } +#endif // VS 2019 + +Approx::Approx(double value) + : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) + , m_scale(1.0) + , m_value(value) {} + +Approx Approx::operator()(double value) const { + Approx approx(value); + approx.epsilon(m_epsilon); + approx.scale(m_scale); + return approx; +} + +Approx& Approx::epsilon(double newEpsilon) { + m_epsilon = newEpsilon; + return *this; +} +Approx& Approx::scale(double newScale) { + m_scale = newScale; + return *this; +} + +bool operator==(double lhs, const Approx& rhs) { + // Thanks to Richard Harris for his help refining this formula + return std::fabs(lhs - rhs.m_value) < + rhs.m_epsilon * (rhs.m_scale + std::max(std::fabs(lhs), std::fabs(rhs.m_value))); +} +bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } +bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } +bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } +bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } +bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } +bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } +bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } +bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } +bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } +bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } +bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } + +String toString(const Approx& in) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + return "Approx( " + doctest::toString(in.m_value) + " )"; +} +const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } + +} // namespace doctest + +#ifdef DOCTEST_CONFIG_DISABLE +namespace doctest { +Context::Context(int, const char* const*) {} +Context::~Context() = default; +void Context::applyCommandLine(int, const char* const*) {} +void Context::addFilter(const char*, const char*) {} +void Context::clearFilters() {} +void Context::setOption(const char*, bool) {} +void Context::setOption(const char*, int) {} +void Context::setOption(const char*, const char*) {} +bool Context::shouldExit() { return false; } +void Context::setAsDefaultForAssertsOutOfTestCases() {} +void Context::setAssertHandler(detail::assert_handler) {} +void Context::setCout(std::ostream* out) {} +int Context::run() { return 0; } + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return 0; } +const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } +int IReporter::get_num_stringified_contexts() { return 0; } +const String* IReporter::get_stringified_contexts() { return nullptr; } + +int registerReporter(const char*, int, IReporter*) { return 0; } + +} // namespace doctest +#else // DOCTEST_CONFIG_DISABLE + +#if !defined(DOCTEST_CONFIG_COLORS_NONE) +#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_CONFIG_COLORS_WINDOWS +#else // linux +#define DOCTEST_CONFIG_COLORS_ANSI +#endif // platform +#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI +#endif // DOCTEST_CONFIG_COLORS_NONE + +namespace doctest_detail_test_suite_ns { +// holds the current test suite +doctest::detail::TestSuite& getCurrentTestSuite() { + static doctest::detail::TestSuite data{}; + return data; +} +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +namespace { + // the int (priority) is part of the key for automatic sorting - sadly one can register a + // reporter with a duplicate name and a different priority but hopefully that won't happen often :| + typedef std::map, reporterCreatorFunc> reporterMap; + + reporterMap& getReporters() { + static reporterMap data; + return data; + } + reporterMap& getListeners() { + static reporterMap data; + return data; + } +} // namespace +namespace detail { +#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ + for(auto& curr_rep : g_cs->reporters_currently_used) \ + curr_rep->function(__VA_ARGS__) + + bool checkIfShouldThrow(assertType::Enum at) { + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return true; + + if((at & assertType::is_check) //!OCLINT bitwise operator in conditional + && getContextOptions()->abort_after > 0 && + (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= + getContextOptions()->abort_after) + return true; + + return false; + } + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_NORETURN void throwException() { + g_cs->shouldLogCurrentException = false; + throw TestFailureException(); + } // NOLINT(cert-err60-cpp) +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + void throwException() {} +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +} // namespace detail + +namespace { + using namespace detail; + // matching of a string against a wildcard mask (case sensitivity configurable) taken from + // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing + int wildcmp(const char* str, const char* wild, bool caseSensitive) { + const char* cp = str; + const char* mp = wild; + + while((*str) && (*wild != '*')) { + if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && + (*wild != '?')) { + return 0; + } + wild++; + str++; + } + + while(*str) { + if(*wild == '*') { + if(!*++wild) { + return 1; + } + mp = wild; + cp = str + 1; + } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || + (*wild == '?')) { + wild++; + str++; + } else { + wild = mp; //!OCLINT parameter reassignment + str = cp++; //!OCLINT parameter reassignment + } + } + + while(*wild == '*') { + wild++; + } + return !*wild; + } + + //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html + //unsigned hashStr(unsigned const char* str) { + // unsigned long hash = 5381; + // char c; + // while((c = *str++)) + // hash = ((hash << 5) + hash) + c; // hash * 33 + c + // return hash; + //} + + // checks if the name matches any of the filters (and can be configured what to do when empty) + bool matchesAny(const char* name, const std::vector& filters, bool matchEmpty, + bool caseSensitive) { + if(filters.empty() && matchEmpty) + return true; + for(auto& curr : filters) + if(wildcmp(name, curr.c_str(), caseSensitive)) + return true; + return false; + } +} // namespace +namespace detail { + + Subcase::Subcase(const String& name, const char* file, int line) + : m_signature({name, file, line}) { + auto* s = g_cs; + + // check subcase filters + if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { + if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) + return; + if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) + return; + } + + // if a Subcase on the same level has already been entered + if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) { + s->should_reenter = true; + return; + } + + // push the current signature to the stack so we can check if the + // current stack + the current new subcase have been traversed + s->subcasesStack.push_back(m_signature); + if(s->subcasesPassed.count(s->subcasesStack) != 0) { + // pop - revert to previous stack since we've already passed this + s->subcasesStack.pop_back(); + return; + } + + s->subcasesCurrentMaxLevel = s->subcasesStack.size(); + m_entered = true; + + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + + Subcase::~Subcase() { + if(m_entered) { + // only mark the subcase stack as passed if no subcases have been skipped + if(g_cs->should_reenter == false) + g_cs->subcasesPassed.insert(g_cs->subcasesStack); + g_cs->subcasesStack.pop_back(); + +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0 +#else + if(std::uncaught_exception() +#endif + && g_cs->shouldLogCurrentException) { + DOCTEST_ITERATE_THROUGH_REPORTERS( + test_case_exception, {"exception thrown in subcase - will translate later " + "when the whole test case has been exited (cannot " + "translate while there is an active exception)", + false}); + g_cs->shouldLogCurrentException = false; + } + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + } + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + Subcase::operator bool() const { return m_entered; } + + Result::Result(bool passed, const String& decomposition) + : m_passed(passed) + , m_decomp(decomposition) {} + + ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) + : m_at(at) {} + + TestSuite& TestSuite::operator*(const char* in) { + m_test_suite = in; + return *this; + } + + TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type, int template_id) { + m_file = file; + m_line = line; + m_name = nullptr; // will be later overridden in operator* + m_test_suite = test_suite.m_test_suite; + m_description = test_suite.m_description; + m_skip = test_suite.m_skip; + m_no_breaks = test_suite.m_no_breaks; + m_no_output = test_suite.m_no_output; + m_may_fail = test_suite.m_may_fail; + m_should_fail = test_suite.m_should_fail; + m_expected_failures = test_suite.m_expected_failures; + m_timeout = test_suite.m_timeout; + + m_test = test; + m_type = type; + m_template_id = template_id; + } + + TestCase::TestCase(const TestCase& other) + : TestCaseData() { + *this = other; + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice + TestCase& TestCase::operator=(const TestCase& other) { + static_cast(*this) = static_cast(other); + + m_test = other.m_test; + m_type = other.m_type; + m_template_id = other.m_template_id; + m_full_name = other.m_full_name; + + if(m_template_id != -1) + m_name = m_full_name.c_str(); + return *this; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& TestCase::operator*(const char* in) { + m_name = in; + // make a new name with an appended type for templated test case + if(m_template_id != -1) { + m_full_name = String(m_name) + m_type; + // redirect the name to point to the newly constructed full name + m_name = m_full_name.c_str(); + } + return *this; + } + + bool TestCase::operator<(const TestCase& other) const { + // this will be used only to differentiate between test cases - not relevant for sorting + if(m_line != other.m_line) + return m_line < other.m_line; + const int name_cmp = strcmp(m_name, other.m_name); + if(name_cmp != 0) + return name_cmp < 0; + const int file_cmp = m_file.compare(other.m_file); + if(file_cmp != 0) + return file_cmp < 0; + return m_template_id < other.m_template_id; + } + + // all the registered tests + std::set& getRegisteredTests() { + static std::set data; + return data; + } +} // namespace detail +namespace { + using namespace detail; + // for sorting tests by file/line + bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { + // this is needed because MSVC gives different case for drive letters + // for __FILE__ when evaluated in a header and a source file + const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); + if(res != 0) + return res < 0; + if(lhs->m_line != rhs->m_line) + return lhs->m_line < rhs->m_line; + return lhs->m_template_id < rhs->m_template_id; + } + + // for sorting tests by suite/file/line + bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); + if(res != 0) + return res < 0; + return fileOrderComparator(lhs, rhs); + } + + // for sorting tests by name/suite/file/line + bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_name, rhs->m_name); + if(res != 0) + return res < 0; + return suiteOrderComparator(lhs, rhs); + } + + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + void color_to_stream(std::ostream& s, Color::Enum code) { + static_cast(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS + static_cast(code); // for DOCTEST_CONFIG_COLORS_NONE +#ifdef DOCTEST_CONFIG_COLORS_ANSI + if(g_no_colors || + (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) + return; + + auto col = ""; + // clang-format off + switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement + case Color::Red: col = "[0;31m"; break; + case Color::Green: col = "[0;32m"; break; + case Color::Blue: col = "[0;34m"; break; + case Color::Cyan: col = "[0;36m"; break; + case Color::Yellow: col = "[0;33m"; break; + case Color::Grey: col = "[1;30m"; break; + case Color::LightGrey: col = "[0;37m"; break; + case Color::BrightRed: col = "[1;31m"; break; + case Color::BrightGreen: col = "[1;32m"; break; + case Color::BrightWhite: col = "[1;37m"; break; + case Color::Bright: // invalid + case Color::None: + case Color::White: + default: col = "[0m"; + } + // clang-format on + s << "\033" << col; +#endif // DOCTEST_CONFIG_COLORS_ANSI + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + if(g_no_colors || + (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false)) + return; + + static struct ConsoleHelper { + HANDLE stdoutHandle; + WORD origFgAttrs; + WORD origBgAttrs; + + ConsoleHelper() { + stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); + origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + } ch; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs) + + // clang-format off + switch (code) { + case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; + case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; + case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; + case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; + case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; + case Color::Grey: DOCTEST_SET_ATTR(0); break; + case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; + case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; + case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; + case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::None: + case Color::Bright: // invalid + default: DOCTEST_SET_ATTR(ch.origFgAttrs); + } + // clang-format on +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + + std::vector& getExceptionTranslators() { + static std::vector data; + return data; + } + + String translateActiveException() { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + String res; + auto& translators = getExceptionTranslators(); + for(auto& curr : translators) + if(curr->translate(res)) + return res; + // clang-format off + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") + try { + throw; + } catch(std::exception& ex) { + return ex.what(); + } catch(std::string& msg) { + return msg.c_str(); + } catch(const char* msg) { + return msg; + } catch(...) { + return "unknown exception"; + } + DOCTEST_GCC_SUPPRESS_WARNING_POP +// clang-format on +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + return ""; +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } +} // namespace + +namespace detail { + // used by the macros for registering tests + int regTest(const TestCase& tc) { + getRegisteredTests().insert(tc); + return 0; + } + + // sets the current test suite + int setTestSuite(const TestSuite& ts) { + doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; + return 0; + } + +#ifdef DOCTEST_IS_DEBUGGER_ACTIVE + bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } +#else // DOCTEST_IS_DEBUGGER_ACTIVE +#ifdef DOCTEST_PLATFORM_LINUX + class ErrnoGuard { + public: + ErrnoGuard() : m_oldErrno(errno) {} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; + }; + // See the comments in Catch2 for the reasoning behind this implementation: + // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102 + bool isDebuggerActive() { + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for(std::string line; std::getline(in, line);) { + static const int PREFIX_LEN = 11; + if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) { + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + return false; + } +#elif defined(DOCTEST_PLATFORM_MAC) + // The following function is taken directly from the following technical note: + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive() { + int mib[4]; + kinfo_proc info; + size_t size; + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + info.kp_proc.p_flag = 0; + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + // Call sysctl. + size = sizeof(info); + if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { + std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; + return false; + } + // We're being debugged if the P_TRACED flag is set. + return ((info.kp_proc.p_flag & P_TRACED) != 0); + } +#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) + bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } +#else + bool isDebuggerActive() { return false; } +#endif // Platform +#endif // DOCTEST_IS_DEBUGGER_ACTIVE + + void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { + if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == + getExceptionTranslators().end()) + getExceptionTranslators().push_back(et); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, char* in) { *s << in; } + void toStream(std::ostream* s, const char* in) { *s << in; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } + void toStream(std::ostream* s, float in) { *s << in; } + void toStream(std::ostream* s, double in) { *s << in; } + void toStream(std::ostream* s, double long in) { *s << in; } + + void toStream(std::ostream* s, char in) { *s << in; } + void toStream(std::ostream* s, char signed in) { *s << in; } + void toStream(std::ostream* s, char unsigned in) { *s << in; } + void toStream(std::ostream* s, int short in) { *s << in; } + void toStream(std::ostream* s, int short unsigned in) { *s << in; } + void toStream(std::ostream* s, int in) { *s << in; } + void toStream(std::ostream* s, int unsigned in) { *s << in; } + void toStream(std::ostream* s, int long in) { *s << in; } + void toStream(std::ostream* s, int long unsigned in) { *s << in; } + void toStream(std::ostream* s, int long long in) { *s << in; } + void toStream(std::ostream* s, int long long unsigned in) { *s << in; } + + DOCTEST_THREAD_LOCAL std::vector g_infoContexts; // for logging with INFO() + + ContextScopeBase::ContextScopeBase() { + g_infoContexts.push_back(this); + } + + ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) { + if (other.need_to_destroy) { + other.destroy(); + } + other.need_to_destroy = false; + g_infoContexts.push_back(this); + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + + // destroy cannot be inlined into the destructor because that would mean calling stringify after + // ContextScope has been destroyed (base class destructors run after derived class destructors). + // Instead, ContextScope calls this method directly from its destructor. + void ContextScopeBase::destroy() { +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0) { +#else + if(std::uncaught_exception()) { +#endif + std::ostringstream s; + this->stringify(&s); + g_cs->stringifiedContexts.push_back(s.str().c_str()); + } + g_infoContexts.pop_back(); + } + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP +} // namespace detail +namespace { + using namespace detail; + +#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) + struct FatalConditionHandler + { + static void reset() {} + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + }; +#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + + void reportFatal(const std::string&); + +#ifdef DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + DWORD id; + const char* name; + }; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + {static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), + "SIGILL - Illegal instruction signal"}, + {static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"}, + {static_cast(EXCEPTION_ACCESS_VIOLATION), + "SIGSEGV - Segmentation violation signal"}, + {static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"}, + }; + + struct FatalConditionHandler + { + static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { + // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the + // console just once no matter how many threads have crashed. + static std::mutex mutex; + static bool execute = true; + { + std::lock_guard lock(mutex); + if(execute) { + bool reported = false; + for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + reported = true; + break; + } + } + if(reported == false) + reportFatal("Unhandled SEH exception caught"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + } + execute = false; + } + std::exit(EXIT_FAILURE); + } + + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for doctest to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + // Register an unhandled exception filter + previousTop = SetUnhandledExceptionFilter(handleException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + + // On Windows uncaught exceptions from another thread, exceptions from + // destructors, or calls to std::terminate are not a SEH exception + + // The terminal handler gets called when: + // - std::terminate is called FROM THE TEST RUNNER THREAD + // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD + original_terminate_handler = std::get_terminate(); + std::set_terminate([]() DOCTEST_NOEXCEPT { + reportFatal("Terminate handler called"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well + }); + + // SIGABRT is raised when: + // - std::terminate is called FROM A DIFFERENT THREAD + // - an exception is thrown from a destructor FROM A DIFFERENT THREAD + // - an uncaught exception is thrown FROM A DIFFERENT THREAD + prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT { + if(signal == SIGABRT) { + reportFatal("SIGABRT - Abort (abnormal termination) signal"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); + } + }); + + // The following settings are taken from google test, and more + // specifically from UnitTest::Run() inside of gtest.cc + + // the user does not want to see pop-up dialogs about crashes + prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + // This forces the abort message to go to stderr in all circumstances. + prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR); + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program - we want to disable that. + prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + // In debug mode, the Windows CRT can crash with an assertion over invalid + // input (e.g. passing an invalid file descriptor). The default handling + // for these assertions is to pop up a dialog and wait for user input. + // Instead ask the CRT to dump such assertions to stderr non-interactively. + prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + } + + static void reset() { + if(isSet) { + // Unregister handler and restore the old guarantee + SetUnhandledExceptionFilter(previousTop); + SetThreadStackGuarantee(&guaranteeSize); + std::set_terminate(original_terminate_handler); + std::signal(SIGABRT, prev_sigabrt_handler); + SetErrorMode(prev_error_mode_1); + _set_error_mode(prev_error_mode_2); + _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + static_cast(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode)); + static_cast(_CrtSetReportFile(_CRT_ASSERT, prev_report_file)); + isSet = false; + } + } + + ~FatalConditionHandler() { reset(); } + + private: + static UINT prev_error_mode_1; + static int prev_error_mode_2; + static unsigned int prev_abort_behavior; + static int prev_report_mode; + static _HFILE prev_report_file; + static void (DOCTEST_CDECL *prev_sigabrt_handler)(int); + static std::terminate_handler original_terminate_handler; + static bool isSet; + static ULONG guaranteeSize; + static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; + }; + + UINT FatalConditionHandler::prev_error_mode_1; + int FatalConditionHandler::prev_error_mode_2; + unsigned int FatalConditionHandler::prev_abort_behavior; + int FatalConditionHandler::prev_report_mode; + _HFILE FatalConditionHandler::prev_report_file; + void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int); + std::terminate_handler FatalConditionHandler::original_terminate_handler; + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; + +#else // DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + int id; + const char* name; + }; + SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, + {SIGILL, "SIGILL - Illegal instruction signal"}, + {SIGFPE, "SIGFPE - Floating point error signal"}, + {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, + {SIGTERM, "SIGTERM - Termination request signal"}, + {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; + + struct FatalConditionHandler + { + static bool isSet; + static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; + static stack_t oldSigStack; + static size_t altStackSize; + static char* altStackMem; + + static void handleSignal(int sig) { + const char* name = ""; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + SignalDefs& def = signalDefs[i]; + if(sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise(sig); + } + + static void allocateAltStackMem() { + altStackMem = new char[altStackSize]; + } + + static void freeAltStackMem() { + delete[] altStackMem; + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = altStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = {}; + sa.sa_handler = handleSignal; // NOLINT + sa.sa_flags = SA_ONSTACK; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { reset(); } + static void reset() { + if(isSet) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ; + char* FatalConditionHandler::altStackMem = nullptr; + +#endif // DOCTEST_PLATFORM_WINDOWS +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + +} // namespace + +namespace { + using namespace detail; + +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) +#else + // TODO: integration with XCode and other IDEs +#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros) +#endif // Platform + + void addAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsCurrentTest_atomic++; + } + + void addFailedAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsFailedCurrentTest_atomic++; + } + +#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) + void reportFatal(const std::string& message) { + g_cs->failure_flags |= TestCaseFailureReason::Crash; + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); + + while(g_cs->subcasesStack.size()) { + g_cs->subcasesStack.pop_back(); + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + + g_cs->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH +} // namespace +namespace detail { + + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const char* exception_string) { + m_test_case = g_cs->currentTest; + m_at = at; + m_file = file; + m_line = line; + m_expr = expr; + m_failed = true; + m_threw = false; + m_threw_as = false; + m_exception_type = exception_type; + m_exception_string = exception_string; +#if DOCTEST_MSVC + if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC + ++m_expr; +#endif // MSVC + } + + void ResultBuilder::setResult(const Result& res) { + m_decomp = res.m_decomp; + m_failed = !res.m_passed; + } + + void ResultBuilder::translateException() { + m_threw = true; + m_exception = translateActiveException(); + } + + bool ResultBuilder::log() { + if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw; + } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT + m_failed = !m_threw_as || (m_exception != m_exception_string); + } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw_as; + } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + m_failed = m_exception != m_exception_string; + } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + m_failed = m_threw; + } + + if(m_exception.size()) + m_exception = "\"" + m_exception + "\""; + + if(is_running_in_test) { + addAssert(m_at); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); + + if(m_failed) + addFailedAssert(m_at); + } else if(m_failed) { + failed_out_of_a_testing_context(*this); + } + + return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger + } + + void ResultBuilder::react() const { + if(m_failed && checkIfShouldThrow(m_at)) + throwException(); + } + + void failed_out_of_a_testing_context(const AssertData& ad) { + if(g_cs->ah) + g_cs->ah(ad); + else + std::abort(); + } + + bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + Result result) { + bool failed = !result.m_passed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); + DOCTEST_ASSERT_IN_TESTS(result.m_decomp); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + return !failed; + } + + MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { + m_stream = tlssPush(); + m_file = file; + m_line = line; + m_severity = severity; + } + + MessageBuilder::~MessageBuilder() { + if (!logged) + tlssPop(); + } + + IExceptionTranslator::IExceptionTranslator() = default; + IExceptionTranslator::~IExceptionTranslator() = default; + + bool MessageBuilder::log() { + if (!logged) { + m_string = tlssPop(); + logged = true; + } + + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); + + const bool isWarn = m_severity & assertType::is_warn; + + // warn is just a message in this context so we don't treat it as an assert + if(!isWarn) { + addAssert(m_severity); + addFailedAssert(m_severity); + } + + return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger + } + + void MessageBuilder::react() { + if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional + throwException(); + } +} // namespace detail +namespace { + using namespace detail; + + // clang-format off + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; + ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = std::cout ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, const char* attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::stringstream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + //XmlWriter& writeComment( std::string const& text ); + + //void writeStylesheetRef( std::string const& url ); + + //XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + +using uchar = unsigned char; + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + os.flags(f); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: https://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: https://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { + if( !name.empty() && attribute && attribute[0] != '\0' ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + //XmlWriter& XmlWriter::writeComment( std::string const& text ) { + // ensureTagClosed(); + // m_os << m_indent << ""; + // m_needsNewline = true; + // return *this; + //} + + //void XmlWriter::writeStylesheetRef( std::string const& url ) { + // m_os << "\n"; + //} + + //XmlWriter& XmlWriter::writeBlankLine() { + // ensureTagClosed(); + // m_os << '\n'; + // return *this; + //} + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + +// ================================================================================================= +// End of copy-pasted code from Catch +// ================================================================================================= + + // clang-format on + + struct XmlReporter : public IReporter + { + XmlWriter xml; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + XmlReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + std::stringstream ss; + for(int i = 0; i < num_contexts; ++i) { + contexts[i]->stringify(&ss); + xml.scopedElement("Info").writeText(ss.str()); + ss.str(""); + } + } + } + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + void test_case_start_impl(const TestCaseData& in) { + bool open_ts_tag = false; + if(tc != nullptr) { // we have already opened a test suite + if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { + xml.endElement(); + open_ts_tag = true; + } + } + else { + open_ts_tag = true; // first test case ==> first test suite + } + + if(open_ts_tag) { + xml.startElement("TestSuite"); + xml.writeAttribute("name", in.m_test_suite); + } + + tc = ∈ + xml.startElement("TestCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) + .writeAttribute("line", line(in.m_line)) + .writeAttribute("description", in.m_description); + + if(Approx(in.m_timeout) != 0) + xml.writeAttribute("timeout", in.m_timeout); + if(in.m_may_fail) + xml.writeAttribute("may_fail", true); + if(in.m_should_fail) + xml.writeAttribute("should_fail", true); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + test_run_start(); + if(opt.list_reporters) { + for(auto& curr : getListeners()) + xml.scopedElement("Listener") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + for(auto& curr : getReporters()) + xml.scopedElement("Reporter") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + } else if(opt.count || opt.list_test_cases) { + for(unsigned i = 0; i < in.num_data; ++i) { + xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) + .writeAttribute("testsuite", in.data[i]->m_test_suite) + .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) + .writeAttribute("line", line(in.data[i]->m_line)) + .writeAttribute("skipped", in.data[i]->m_skip); + } + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + } else if(opt.list_test_suites) { + for(unsigned i = 0; i < in.num_data; ++i) + xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + xml.scopedElement("OverallResultsTestSuites") + .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); + } + xml.endElement(); + } + + void test_run_start() override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + + xml.startElement("doctest").writeAttribute("binary", binary_name); + if(opt.no_version == false) + xml.writeAttribute("version", DOCTEST_VERSION_STR); + + // only the consequential ones (TODO: filters) + xml.scopedElement("Options") + .writeAttribute("order_by", opt.order_by.c_str()) + .writeAttribute("rand_seed", opt.rand_seed) + .writeAttribute("first", opt.first) + .writeAttribute("last", opt.last) + .writeAttribute("abort_after", opt.abort_after) + .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) + .writeAttribute("case_sensitive", opt.case_sensitive) + .writeAttribute("no_throw", opt.no_throw) + .writeAttribute("no_skip", opt.no_skip); + } + + void test_run_end(const TestRunStats& p) override { + if(tc) // the TestSuite tag - only if there has been at least 1 test case + xml.endElement(); + + xml.scopedElement("OverallResultsAsserts") + .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) + .writeAttribute("failures", p.numAssertsFailed); + + xml.startElement("OverallResultsTestCases") + .writeAttribute("successes", + p.numTestCasesPassingFilters - p.numTestCasesFailed) + .writeAttribute("failures", p.numTestCasesFailed); + if(opt.no_skipped_summary == false) + xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); + xml.endElement(); + + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + test_case_start_impl(in); + xml.ensureTagClosed(); + } + + void test_case_reenter(const TestCaseData&) override {} + + void test_case_end(const CurrentTestCaseStats& st) override { + xml.startElement("OverallResultsAsserts") + .writeAttribute("successes", + st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) + .writeAttribute("failures", st.numAssertsFailedCurrentTest) + .writeAttribute("test_case_success", st.testCaseSuccess); + if(opt.duration) + xml.writeAttribute("duration", st.seconds); + if(tc->m_expected_failures) + xml.writeAttribute("expected_failures", tc->m_expected_failures); + xml.endElement(); + + xml.endElement(); + } + + void test_case_exception(const TestCaseException& e) override { + std::lock_guard lock(mutex); + + xml.scopedElement("Exception") + .writeAttribute("crash", e.is_crash) + .writeText(e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + xml.startElement("SubCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)); + xml.ensureTagClosed(); + } + + void subcase_end() override { xml.endElement(); } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + std::lock_guard lock(mutex); + + xml.startElement("Expression") + .writeAttribute("success", !rb.m_failed) + .writeAttribute("type", assertString(rb.m_at)) + .writeAttribute("filename", skipPathFromFilename(rb.m_file)) + .writeAttribute("line", line(rb.m_line)); + + xml.scopedElement("Original").writeText(rb.m_expr); + + if(rb.m_threw) + xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); + + if(rb.m_at & assertType::is_throws_as) + xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); + if(rb.m_at & assertType::is_throws_with) + xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string); + if((rb.m_at & assertType::is_normal) && !rb.m_threw) + xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void log_message(const MessageData& mb) override { + std::lock_guard lock(mutex); + + xml.startElement("Message") + .writeAttribute("type", failureString(mb.m_severity)) + .writeAttribute("filename", skipPathFromFilename(mb.m_file)) + .writeAttribute("line", line(mb.m_line)); + + xml.scopedElement("Text").writeText(mb.m_string.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void test_case_skipped(const TestCaseData& in) override { + if(opt.no_skipped_summary == false) { + test_case_start_impl(in); + xml.writeAttribute("skipped", "true"); + xml.endElement(); + } + } + }; + + DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + + void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { + if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == + 0) //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " + << Color::None; + + if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; + } else if((rb.m_at & assertType::is_throws_as) && + (rb.m_at & assertType::is_throws_with)) { //!OCLINT + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; + if(rb.m_threw) { + if(!rb.m_failed) { + s << "threw as expected!\n"; + } else { + s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; + } + } else { + s << "did NOT throw at all!\n"; + } + } else if(rb.m_at & + assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " + << rb.m_exception_type << " ) " << Color::None + << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & + assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\" ) " << Color::None + << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan + << rb.m_exception << "\n"; + } else { + s << (rb.m_threw ? "THREW exception: " : + (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); + if(rb.m_threw) + s << rb.m_exception << "\n"; + else + s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; + } + } + + // TODO: + // - log_message() + // - respond to queries + // - honor remaining options + // - more attributes in tags + struct JUnitReporter : public IReporter + { + XmlWriter xml; + std::mutex mutex; + Timer timer; + std::vector deepestSubcaseStackNames; + + struct JUnitTestCaseData + { + static std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + + std::tm timeInfo; +#ifdef DOCTEST_PLATFORM_WINDOWS + gmtime_s(&timeInfo, &rawtime); +#else // DOCTEST_PLATFORM_WINDOWS + gmtime_r(&rawtime, &timeInfo); +#endif // DOCTEST_PLATFORM_WINDOWS + + char timeStamp[timeStampSize]; + const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; + + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); + return std::string(timeStamp); + } + + struct JUnitTestMessage + { + JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) + : message(_message), type(_type), details(_details) {} + + JUnitTestMessage(const std::string& _message, const std::string& _details) + : message(_message), type(), details(_details) {} + + std::string message, type, details; + }; + + struct JUnitTestCase + { + JUnitTestCase(const std::string& _classname, const std::string& _name) + : classname(_classname), name(_name), time(0), failures() {} + + std::string classname, name; + double time; + std::vector failures, errors; + }; + + void add(const std::string& classname, const std::string& name) { + testcases.emplace_back(classname, name); + } + + void appendSubcaseNamesToLastTestcase(std::vector nameStack) { + for(auto& curr: nameStack) + if(curr.size()) + testcases.back().name += std::string("/") + curr.c_str(); + } + + void addTime(double time) { + if(time < 1e-4) + time = 0; + testcases.back().time = time; + totalSeconds += time; + } + + void addFailure(const std::string& message, const std::string& type, const std::string& details) { + testcases.back().failures.emplace_back(message, type, details); + ++totalFailures; + } + + void addError(const std::string& message, const std::string& details) { + testcases.back().errors.emplace_back(message, details); + ++totalErrors; + } + + std::vector testcases; + double totalSeconds = 0; + int totalErrors = 0, totalFailures = 0; + }; + + JUnitTestCaseData testCaseData; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + JUnitReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData&) override {} + + void test_run_start() override {} + + void test_run_end(const TestRunStats& p) override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + xml.startElement("testsuites"); + xml.startElement("testsuite").writeAttribute("name", binary_name) + .writeAttribute("errors", testCaseData.totalErrors) + .writeAttribute("failures", testCaseData.totalFailures) + .writeAttribute("tests", p.numAsserts); + if(opt.no_time_in_output == false) { + xml.writeAttribute("time", testCaseData.totalSeconds); + xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); + } + if(opt.no_version == false) + xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); + + for(const auto& testCase : testCaseData.testcases) { + xml.startElement("testcase") + .writeAttribute("classname", testCase.classname) + .writeAttribute("name", testCase.name); + if(opt.no_time_in_output == false) + xml.writeAttribute("time", testCase.time); + // This is not ideal, but it should be enough to mimic gtest's junit output. + xml.writeAttribute("status", "run"); + + for(const auto& failure : testCase.failures) { + xml.scopedElement("failure") + .writeAttribute("message", failure.message) + .writeAttribute("type", failure.type) + .writeText(failure.details, false); + } + + for(const auto& error : testCase.errors) { + xml.scopedElement("error") + .writeAttribute("message", error.message) + .writeText(error.details); + } + + xml.endElement(); + } + xml.endElement(); + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + timer.start(); + } + + void test_case_reenter(const TestCaseData& in) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + + timer.start(); + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + } + + void test_case_end(const CurrentTestCaseStats&) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + } + + void test_case_exception(const TestCaseException& e) override { + std::lock_guard lock(mutex); + testCaseData.addError("exception", e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + deepestSubcaseStackNames.push_back(in.m_name); + } + + void subcase_end() override {} + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed) // report only failures & ignore the `success` option + return; + + std::lock_guard lock(mutex); + + std::ostringstream os; + os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + fulltext_log_assert_to_stream(os, rb); + log_contexts(os); + testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); + } + + void log_message(const MessageData&) override {} + + void test_case_skipped(const TestCaseData&) override {} + + void log_contexts(std::ostringstream& s) { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << std::endl; + } + } + } + }; + + DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); + + struct Whitespace + { + int nrSpaces; + explicit Whitespace(int nr) + : nrSpaces(nr) {} + }; + + std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { + if(ws.nrSpaces != 0) + out << std::setw(ws.nrSpaces) << ' '; + return out; + } + + struct ConsoleReporter : public IReporter + { + std::ostream& s; + bool hasLoggedCurrentTestStart; + std::vector subcasesStack; + size_t currentSubcaseLevel; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc; + + ConsoleReporter(const ContextOptions& co) + : s(*co.cout) + , opt(co) {} + + ConsoleReporter(const ContextOptions& co, std::ostream& ostr) + : s(ostr) + , opt(co) {} + + // ========================================================================================= + // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE + // ========================================================================================= + + void separator_to_stream() { + s << Color::Yellow + << "===============================================================================" + "\n"; + } + + const char* getSuccessOrFailString(bool success, assertType::Enum at, + const char* success_str) { + if(success) + return success_str; + return failureString(at); + } + + Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { + return success ? Color::BrightGreen : + (at & assertType::is_warn) ? Color::Yellow : Color::Red; + } + + void successOrFailColoredStringToStream(bool success, assertType::Enum at, + const char* success_str = "SUCCESS") { + s << getSuccessOrFailColor(success, at) + << getSuccessOrFailString(success, at, success_str) << ": "; + } + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << Color::None << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << "\n"; + } + } + + s << "\n"; + } + + // this was requested to be made virtual so users could override it + virtual void file_line_to_stream(const char* file, int line, + const char* tail = "") { + s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") + << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option + << (opt.gnu_file_line ? ":" : "):") << tail; + } + + void logTestStart() { + if(hasLoggedCurrentTestStart) + return; + + separator_to_stream(); + file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); + if(tc->m_description) + s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; + if(tc->m_test_suite && tc->m_test_suite[0] != '\0') + s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; + if(strncmp(tc->m_name, " Scenario:", 11) != 0) + s << Color::Yellow << "TEST CASE: "; + s << Color::None << tc->m_name << "\n"; + + for(size_t i = 0; i < currentSubcaseLevel; ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + + if(currentSubcaseLevel != subcasesStack.size()) { + s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; + for(size_t i = 0; i < subcasesStack.size(); ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + } + + s << "\n"; + + hasLoggedCurrentTestStart = true; + } + + void printVersion() { + if(opt.no_version == false) + s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" + << DOCTEST_VERSION_STR << "\"\n"; + } + + void printIntro() { + if(opt.no_intro == false) { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } + } + + void printHelp() { + int sizePrefixDisplay = static_cast(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); + printVersion(); + // clang-format off + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filters use wildcards for matching strings\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "something passes a filter if any of the strings in a filter matches\n"; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; +#endif + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "Query flags - the program quits after them. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " + << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " + << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " + << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " + << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " + << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " + << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; + // ================================================================================== << 79 + s << Color::Cyan << "[doctest] " << Color::None; + s << "The available / options/filters are:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase= " + << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters= " + << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " + << Whitespace(sizePrefixDisplay*1) << "output filename\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " + << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; + s << Whitespace(sizePrefixDisplay*3) << " - [file/suite/name/rand/none]\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " + << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " + << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last= " + << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after= " + << Whitespace(sizePrefixDisplay*1) << "stop after failed assertions\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels= " + << Whitespace(sizePrefixDisplay*1) << "apply filters for the first levels\n"; + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success= " + << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive= " + << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit= " + << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " + << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal= " + << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet= " + << Whitespace(sizePrefixDisplay*1) << "no console output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " + << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " + << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " + << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " + << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors= " + << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks= " + << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip= " + << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line= " + << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames= " + << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers= " + << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; + // ================================================================================== << 79 + // clang-format on + + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "for more information visit the project documentation\n\n"; + } + + void printRegisteredReporters() { + printVersion(); + auto printReporters = [this] (const reporterMap& reporters, const char* type) { + if(reporters.size()) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; + for(auto& curr : reporters) + s << "priority: " << std::setw(5) << curr.first.first + << " name: " << curr.first.second << "\n"; + } + }; + printReporters(getListeners(), "listeners"); + printReporters(getReporters(), "reporters"); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + if(opt.version) { + printVersion(); + } else if(opt.help) { + printHelp(); + } else if(opt.list_reporters) { + printRegisteredReporters(); + } else if(opt.count || opt.list_test_cases) { + if(opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "listing all test case names\n"; + separator_to_stream(); + } + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_name << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; + separator_to_stream(); + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_test_suite << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + void test_run_start() override { + if(!opt.minimal) + printIntro(); + } + + void test_run_end(const TestRunStats& p) override { + if(opt.minimal && p.numTestCasesFailed == 0) + return; + + separator_to_stream(); + s << std::dec; + + auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast(p.numAsserts))) + 1))); + auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast(p.numAsserts - p.numAssertsFailed))) + 1))); + auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast(p.numAssertsFailed))) + 1))); + const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; + s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) + << p.numTestCasesPassingFilters << " | " + << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : + Color::Green) + << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" + << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) + << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |"; + if(opt.no_skipped_summary == false) { + const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; + s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped + << " skipped" << Color::None; + } + s << "\n"; + s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth) + << p.numAsserts << " | " + << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) + << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None + << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) + << p.numAssertsFailed << " failed" << Color::None << " |\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) + << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; + } + + void test_case_start(const TestCaseData& in) override { + hasLoggedCurrentTestStart = false; + tc = ∈ + subcasesStack.clear(); + currentSubcaseLevel = 0; + } + + void test_case_reenter(const TestCaseData&) override { + subcasesStack.clear(); + } + + void test_case_end(const CurrentTestCaseStats& st) override { + if(tc->m_no_output) + return; + + // log the preamble of the test case only if there is something + // else to print - something other than that an assert has failed + if(opt.duration || + (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) + logTestStart(); + + if(opt.duration) + s << Color::None << std::setprecision(6) << std::fixed << st.seconds + << " s: " << tc->m_name << "\n"; + + if(st.failure_flags & TestCaseFailureReason::Timeout) + s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) + << std::fixed << tc->m_timeout << "!\n"; + + if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { + s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { + s << Color::Yellow << "Failed as expected so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { + s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { + s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures + << " times so marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { + s << Color::Yellow << "Failed exactly " << tc->m_expected_failures + << " times as expected so marking it as not failed!\n"; + } + if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { + s << Color::Red << "Aborting - too many failed asserts!\n"; + } + s << Color::None; // lgtm [cpp/useless-expression] + } + + void test_case_exception(const TestCaseException& e) override { + std::lock_guard lock(mutex); + if(tc->m_no_output) + return; + + logTestStart(); + + file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); + successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : + assertType::is_check); + s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") + << Color::Cyan << e.error_string << "\n"; + + int num_stringified_contexts = get_num_stringified_contexts(); + if(num_stringified_contexts) { + auto stringified_contexts = get_stringified_contexts(); + s << Color::None << " logged: "; + for(int i = num_stringified_contexts; i > 0; --i) { + s << (i == num_stringified_contexts ? "" : " ") + << stringified_contexts[i - 1] << "\n"; + } + } + s << "\n" << Color::None; + } + + void subcase_start(const SubcaseSignature& subc) override { + subcasesStack.push_back(subc); + ++currentSubcaseLevel; + hasLoggedCurrentTestStart = false; + } + + void subcase_end() override { + --currentSubcaseLevel; + hasLoggedCurrentTestStart = false; + } + + void log_assert(const AssertData& rb) override { + if((!rb.m_failed && !opt.success) || tc->m_no_output) + return; + + std::lock_guard lock(mutex); + + logTestStart(); + + file_line_to_stream(rb.m_file, rb.m_line, " "); + successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); + + fulltext_log_assert_to_stream(s, rb); + + log_contexts(); + } + + void log_message(const MessageData& mb) override { + if(tc->m_no_output) + return; + + std::lock_guard lock(mutex); + + logTestStart(); + + file_line_to_stream(mb.m_file, mb.m_line, " "); + s << getSuccessOrFailColor(false, mb.m_severity) + << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, + "MESSAGE") << ": "; + s << Color::None << mb.m_string << "\n"; + log_contexts(); + } + + void test_case_skipped(const TestCaseData&) override {} + }; + + DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); + +#ifdef DOCTEST_PLATFORM_WINDOWS + struct DebugOutputWindowReporter : public ConsoleReporter + { + DOCTEST_THREAD_LOCAL static std::ostringstream oss; + + DebugOutputWindowReporter(const ContextOptions& co) + : ConsoleReporter(co, oss) {} + +#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ + void func(type arg) override { \ + bool with_col = g_no_colors; \ + g_no_colors = false; \ + ConsoleReporter::func(arg); \ + if(oss.tellp() != std::streampos{}) { \ + DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ + oss.str(""); \ + } \ + g_no_colors = with_col; \ + } + + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) + }; + + DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; +#endif // DOCTEST_PLATFORM_WINDOWS + + // the implementation of parseOption() + bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { + // going from the end to the beginning and stopping on the first occurrence from the end + for(int i = argc; i > 0; --i) { + auto index = i - 1; + auto temp = std::strstr(argv[index], pattern); + if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue + // eliminate matches in which the chars before the option are not '-' + bool noBadCharsFound = true; + auto curr = argv[index]; + while(curr != temp) { + if(*curr++ != '-') { + noBadCharsFound = false; + break; + } + } + if(noBadCharsFound && argv[index][0] == '-') { + if(value) { + // parsing the value of an option + temp += strlen(pattern); + const unsigned len = strlen(temp); + if(len) { + *value = temp; + return true; + } + } else { + // just a flag - no value + return true; + } + } + } + } + return false; + } + + // parses an option and returns the string after the '=' character + bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, + const String& defaultVal = String()) { + if(value) + *value = defaultVal; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + // offset (normally 3 for "dt-") to skip prefix + if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) + return true; +#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + return parseOptionImpl(argc, argv, pattern, value); + } + + // locates a flag on the command line + bool parseFlag(int argc, const char* const* argv, const char* pattern) { + return parseOption(argc, argv, pattern); + } + + // parses a comma separated list of words after a pattern in one of the arguments in argv + bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, + std::vector& res) { + String filtersString; + if(parseOption(argc, argv, pattern, &filtersString)) { + // tokenize with "," as a separator, unless escaped with backslash + std::ostringstream s; + auto flush = [&s, &res]() { + auto string = s.str(); + if(string.size() > 0) { + res.push_back(string.c_str()); + } + s.str(""); + }; + + bool seenBackslash = false; + const char* current = filtersString.c_str(); + const char* end = current + strlen(current); + while(current != end) { + char character = *current++; + if(seenBackslash) { + seenBackslash = false; + if(character == ',') { + s.put(','); + continue; + } + s.put('\\'); + } + if(character == '\\') { + seenBackslash = true; + } else if(character == ',') { + flush(); + } else { + s.put(character); + } + } + + if(seenBackslash) { + s.put('\\'); + } + flush(); + return true; + } + return false; + } + + enum optionType + { + option_bool, + option_int + }; + + // parses an int/bool option from the command line + bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, + int& res) { + String parsedValue; + if(!parseOption(argc, argv, pattern, &parsedValue)) + return false; + + if(type == 0) { + // boolean + const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 + const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 + + // if the value matches any of the positive/negative possibilities + for(unsigned i = 0; i < 4; i++) { + if(parsedValue.compare(positive[i], true) == 0) { + res = 1; //!OCLINT parameter reassignment + return true; + } + if(parsedValue.compare(negative[i], true) == 0) { + res = 0; //!OCLINT parameter reassignment + return true; + } + } + } else { + // integer + // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... + int theInt = std::atoi(parsedValue.c_str()); // NOLINT + if(theInt != 0) { + res = theInt; //!OCLINT parameter reassignment + return true; + } + } + return false; + } +} // namespace + +Context::Context(int argc, const char* const* argv) + : p(new detail::ContextState) { + parseArgs(argc, argv, true); + if(argc) + p->binary_name = argv[0]; +} + +Context::~Context() { + if(g_cs == p) + g_cs = nullptr; + delete p; +} + +void Context::applyCommandLine(int argc, const char* const* argv) { + parseArgs(argc, argv); + if(argc) + p->binary_name = argv[0]; +} + +// parses args +void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { + using namespace detail; + + // clang-format off + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); + // clang-format on + + int intRes = 0; + String strRes; + +#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ + p->var = static_cast(intRes); \ + else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ + p->var = true; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ + p->var = intRes; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ + if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \ + parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \ + withDefaults) \ + p->var = strRes + + // clang-format off + DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); + DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); + DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); + + DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); + DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); + + DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); + DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); + + DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); + // clang-format on + + if(withDefaults) { + p->help = false; + p->version = false; + p->count = false; + p->list_test_cases = false; + p->list_test_suites = false; + p->list_reporters = false; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { + p->help = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { + p->version = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { + p->count = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { + p->list_test_cases = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { + p->list_test_suites = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { + p->list_reporters = true; + p->exit = true; + } +} + +// allows the user to add procedurally to the filters from the command line +void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } + +// allows the user to clear all filters from the command line +void Context::clearFilters() { + for(auto& curr : p->filters) + curr.clear(); +} + +// allows the user to override procedurally the bool options from the command line +void Context::setOption(const char* option, bool value) { + setOption(option, value ? "true" : "false"); +} + +// allows the user to override procedurally the int options from the command line +void Context::setOption(const char* option, int value) { + setOption(option, toString(value).c_str()); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +} + +// allows the user to override procedurally the string options from the command line +void Context::setOption(const char* option, const char* value) { + auto argv = String("-") + option + "=" + value; + auto lvalue = argv.c_str(); + parseArgs(1, &lvalue); +} + +// users should query this in their main() and exit the program if true +bool Context::shouldExit() { return p->exit; } + +void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } + +void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } + +void Context::setCout(std::ostream* out) { p->cout = out; } + +static class DiscardOStream : public std::ostream +{ +private: + class : public std::streambuf + { + private: + // allowing some buffering decreases the amount of calls to overflow + char buf[1024]; + + protected: + std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; } + + int_type overflow(int_type ch) override { + setp(std::begin(buf), std::end(buf)); + return traits_type::not_eof(ch); + } + } discardBuf; + +public: + DiscardOStream() + : std::ostream(&discardBuf) {} +} discardOut; + +// the main function that does all the filtering and test running +int Context::run() { + using namespace detail; + + // save the old context state in case such was setup - for using asserts out of a testing context + auto old_cs = g_cs; + // this is the current contest + g_cs = p; + is_running_in_test = true; + + g_no_colors = p->no_colors; + p->resetRunData(); + + std::fstream fstr; + if(p->cout == nullptr) { + if(p->quiet) { + p->cout = &discardOut; + } else if(p->out.size()) { + // to a file if specified + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } else { + // stdout by default + p->cout = &std::cout; + } + } + + FatalConditionHandler::allocateAltStackMem(); + + auto cleanup_and_return = [&]() { + FatalConditionHandler::freeAltStackMem(); + + if(fstr.is_open()) + fstr.close(); + + // restore context + g_cs = old_cs; + is_running_in_test = false; + + // we have to free the reporters which were allocated when the run started + for(auto& curr : p->reporters_currently_used) + delete curr; + p->reporters_currently_used.clear(); + + if(p->numTestCasesFailed && !p->no_exitcode) + return EXIT_FAILURE; + return EXIT_SUCCESS; + }; + + // setup default reporter if none is given through the command line + if(p->filters[8].empty()) + p->filters[8].push_back("console"); + + // check to see if any of the registered reporters has been selected + for(auto& curr : getReporters()) { + if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) + p->reporters_currently_used.push_back(curr.second(*g_cs)); + } + + // TODO: check if there is nothing in reporters_currently_used + + // prepend all listeners + for(auto& curr : getListeners()) + p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); + +#ifdef DOCTEST_PLATFORM_WINDOWS + if(isDebuggerActive() && p->no_debug_output == false) + p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); +#endif // DOCTEST_PLATFORM_WINDOWS + + // handle version, help and no_run + if(p->no_run || p->version || p->help || p->list_reporters) { + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); + + return cleanup_and_return(); + } + + std::vector testArray; + for(auto& curr : getRegisteredTests()) + testArray.push_back(&curr); + p->numTestCases = testArray.size(); + + // sort the collected records + if(!testArray.empty()) { + if(p->order_by.compare("file", true) == 0) { + std::sort(testArray.begin(), testArray.end(), fileOrderComparator); + } else if(p->order_by.compare("suite", true) == 0) { + std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); + } else if(p->order_by.compare("name", true) == 0) { + std::sort(testArray.begin(), testArray.end(), nameOrderComparator); + } else if(p->order_by.compare("rand", true) == 0) { + std::srand(p->rand_seed); + + // random_shuffle implementation + const auto first = &testArray[0]; + for(size_t i = testArray.size() - 1; i > 0; --i) { + int idxToSwap = std::rand() % (i + 1); // NOLINT + + const auto temp = first[i]; + + first[i] = first[idxToSwap]; + first[idxToSwap] = temp; + } + } else if(p->order_by.compare("none", true) == 0) { + // means no sorting - beneficial for death tests which call into the executable + // with a specific test case in mind - we don't want to slow down the startup times + } + } + + std::set testSuitesPassingFilt; + + bool query_mode = p->count || p->list_test_cases || p->list_test_suites; + std::vector queryResults; + + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); + + // invoke the registered functions if they match the filter criteria (or just count them) + for(auto& curr : testArray) { + const auto& tc = *curr; + + bool skip_me = false; + if(tc.m_skip && !p->no_skip) + skip_me = true; + + if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) + skip_me = true; + + if(!skip_me) + p->numTestCasesPassingFilters++; + + // skip the test if it is not in the execution range + if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || + (p->first > p->numTestCasesPassingFilters)) + skip_me = true; + + if(skip_me) { + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); + continue; + } + + // do not execute the test if we are to only count the number of filter passing tests + if(p->count) + continue; + + // print the name of the test and don't execute it + if(p->list_test_cases) { + queryResults.push_back(&tc); + continue; + } + + // print the name of the test suite if not done already and don't execute it + if(p->list_test_suites) { + if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { + queryResults.push_back(&tc); + testSuitesPassingFilt.insert(tc.m_test_suite); + p->numTestSuitesPassingFilters++; + } + continue; + } + + // execute the test if it passes all the filtering + { + p->currentTest = &tc; + + p->failure_flags = TestCaseFailureReason::None; + p->seconds = 0; + + // reset atomic counters + p->numAssertsFailedCurrentTest_atomic = 0; + p->numAssertsCurrentTest_atomic = 0; + + p->subcasesPassed.clear(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); + + p->timer.start(); + + bool run_test = true; + + do { + // reset some of the fields for subcases (except for the set of fully passed ones) + p->should_reenter = false; + p->subcasesCurrentMaxLevel = 0; + p->subcasesStack.clear(); + + p->shouldLogCurrentException = true; + + // reset stuff for logging with INFO() + p->stringifiedContexts.clear(); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method) +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable + FatalConditionHandler fatalConditionHandler; // Handle signals + // execute the test + tc.m_test(); + fatalConditionHandler.reset(); +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + } catch(const TestFailureException&) { + p->failure_flags |= TestCaseFailureReason::AssertFailure; + } catch(...) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, + {translateActiveException(), false}); + p->failure_flags |= TestCaseFailureReason::Exception; + } +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + + // exit this loop if enough assertions have failed - even if there are more subcases + if(p->abort_after > 0 && + p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { + run_test = false; + p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; + } + + if(p->should_reenter && run_test) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); + if(!p->should_reenter) + run_test = false; + } while(run_test); + + p->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + p->currentTest = nullptr; + + // stop executing tests if enough assertions have failed + if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) + break; + } + } + + if(!query_mode) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } else { + QueryData qdata; + qdata.run_stats = g_cs; + qdata.data = queryResults.data(); + qdata.num_data = unsigned(queryResults.size()); + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); + } + + return cleanup_and_return(); +} + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } +const IContextScope* const* IReporter::get_active_contexts() { + return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; +} + +int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } +const String* IReporter::get_stringified_contexts() { + return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; +} + +namespace detail { + void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { + if(isReporter) + getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + else + getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + } +} // namespace detail + +} // namespace doctest + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + +#endif // DOCTEST_LIBRARY_IMPLEMENTATION +#endif // DOCTEST_CONFIG_IMPLEMENT diff --git a/binding-tests/uuid.cpp b/binding-tests/uuid.cpp new file mode 100644 index 000000000..3c7979b2f --- /dev/null +++ b/binding-tests/uuid.cpp @@ -0,0 +1,7 @@ +#include "doctest.h" +#include "taskchampion.h" + +TEST_CASE("creating a UUID") { + StoragePtr *storage = storage_new_in_memory(); + storage_free(storage); +} diff --git a/lib/Cargo.toml b/lib/Cargo.toml new file mode 100644 index 000000000..9d941a6d7 --- /dev/null +++ b/lib/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "taskchampion-lib" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[lib] +name = "taskchampion" +crate-type = ["cdylib"] + +[dependencies] +taskchampion = { path = "../taskchampion" } + +[build-dependencies] +cbindgen = "0.20.0" diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 000000000..d2dbe101b --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,2 @@ +taskchampion.h: cbindgen.toml ../target/debug/libtaskchampion.so + cbindgen --config cbindgen.toml --crate taskchampion-lib --output $@ diff --git a/lib/build.rs b/lib/build.rs new file mode 100644 index 000000000..8d9db2f1f --- /dev/null +++ b/lib/build.rs @@ -0,0 +1,18 @@ +use cbindgen::*; + +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + Builder::new() + .with_crate(crate_dir) + .with_language(Language::C) + .with_config(Config { + cpp_compat: true, + ..Default::default() + }) + .generate() + .expect("Unable to generate bindings") + .write_to_file("taskchampion.h"); +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs new file mode 100644 index 000000000..30f61eb69 --- /dev/null +++ b/lib/src/lib.rs @@ -0,0 +1 @@ +pub mod storage; diff --git a/lib/src/storage.rs b/lib/src/storage.rs new file mode 100644 index 000000000..96950dcd9 --- /dev/null +++ b/lib/src/storage.rs @@ -0,0 +1,16 @@ +use taskchampion::{storage::Storage, StorageConfig}; + +pub struct StoragePtr(Box); + +#[no_mangle] +pub extern "C" fn storage_new_in_memory() -> *mut StoragePtr { + // TODO: this is a box containing a fat pointer + Box::into_raw(Box::new(StoragePtr( + StorageConfig::InMemory.into_storage().unwrap(), + ))) +} + +#[no_mangle] +pub extern "C" fn storage_free(storage: *mut StoragePtr) { + drop(unsafe { Box::from_raw(storage) }); +} diff --git a/lib/taskchampion.h b/lib/taskchampion.h new file mode 100644 index 000000000..dc8631325 --- /dev/null +++ b/lib/taskchampion.h @@ -0,0 +1,15 @@ +#include +#include +#include +#include +#include + +struct StoragePtr; + +extern "C" { + +StoragePtr *storage_new_in_memory(); + +void storage_free(StoragePtr *storage); + +} // extern "C" From ce56127bbfa3fe2b47942b97bd059b8deb629998 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 17:24:54 +0000 Subject: [PATCH 431/548] create / free replicas, plus error handling --- Cargo.lock | 5 ++- binding-tests/Makefile | 2 +- binding-tests/replica.cpp | 12 +++++ binding-tests/uuid.cpp | 7 --- lib/Cargo.toml | 1 + lib/src/lib.rs | 2 +- lib/src/replica.rs | 92 +++++++++++++++++++++++++++++++++++++++ lib/src/storage.rs | 16 ------- lib/taskchampion.h | 25 +++++++++-- 9 files changed, 132 insertions(+), 30 deletions(-) create mode 100644 binding-tests/replica.cpp delete mode 100644 binding-tests/uuid.cpp create mode 100644 lib/src/replica.rs delete mode 100644 lib/src/storage.rs diff --git a/Cargo.lock b/Cargo.lock index caa3684ab..91cc866d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,9 +1617,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.97" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9" [[package]] name = "libgit2-sys" @@ -3032,6 +3032,7 @@ name = "taskchampion-lib" version = "0.1.0" dependencies = [ "cbindgen", + "libc", "taskchampion", ] diff --git a/binding-tests/Makefile b/binding-tests/Makefile index e5f09d787..3599821f4 100644 --- a/binding-tests/Makefile +++ b/binding-tests/Makefile @@ -3,7 +3,7 @@ INC=-I ../lib LIB=-L ../target/debug RPATH=-Wl,-rpath,../target/debug -TESTS = uuid.cpp +TESTS = replica.cpp .PHONY: all test diff --git a/binding-tests/replica.cpp b/binding-tests/replica.cpp new file mode 100644 index 000000000..c8e11cedc --- /dev/null +++ b/binding-tests/replica.cpp @@ -0,0 +1,12 @@ +#include +#include "doctest.h" +#include "taskchampion.h" + +TEST_CASE("creating an in-memory Replica does not crash") { + Replica *rep = tc_replica_new(NULL); + CHECK(tc_replica_error(rep) == NULL); + uhoh(rep); + REQUIRE(tc_replica_error(rep) != NULL); + CHECK(strcmp(tc_replica_error(rep), "uhoh!") == 0); + tc_replica_free(rep); +} diff --git a/binding-tests/uuid.cpp b/binding-tests/uuid.cpp deleted file mode 100644 index 3c7979b2f..000000000 --- a/binding-tests/uuid.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "doctest.h" -#include "taskchampion.h" - -TEST_CASE("creating a UUID") { - StoragePtr *storage = storage_new_in_memory(); - storage_free(storage); -} diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 9d941a6d7..956c5cbef 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -9,6 +9,7 @@ name = "taskchampion" crate-type = ["cdylib"] [dependencies] +libc = "0.2.113" taskchampion = { path = "../taskchampion" } [build-dependencies] diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 30f61eb69..78004cf88 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1 +1 @@ -pub mod storage; +pub mod replica; diff --git a/lib/src/replica.rs b/lib/src/replica.rs new file mode 100644 index 000000000..81656519b --- /dev/null +++ b/lib/src/replica.rs @@ -0,0 +1,92 @@ +use libc::c_char; +use std::ffi::{CStr, CString, OsStr}; +use std::path::PathBuf; +use taskchampion::Replica as TCReplica; +use taskchampion::StorageConfig; + +// TODO: unix-only +use std::os::unix::ffi::OsStrExt; + +/// A replica represents an instance of a user's task data, providing an easy interface +/// for querying and modifying that data. +pub struct Replica { + inner: TCReplica, + error: Option, +} + +/// Create a new Replica. +/// +/// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the +/// on-disk storage for this replica. The path argument is no longer referenced after return. +/// +/// Returns NULL on error; see tc_replica_error. +/// +/// Replicas are not threadsafe. +#[no_mangle] +pub extern "C" fn tc_replica_new<'a>(path: *const c_char) -> *mut Replica { + let storage_res = if path.is_null() { + StorageConfig::InMemory.into_storage() + } else { + let path: &'a [u8] = unsafe { CStr::from_ptr(path) }.to_bytes(); + let path: &OsStr = OsStr::from_bytes(path); + let path: PathBuf = path.to_os_string().into(); + StorageConfig::OnDisk { taskdb_dir: path }.into_storage() + }; + + let storage = match storage_res { + Ok(storage) => storage, + // TODO: report errors somehow + Err(_) => return std::ptr::null_mut(), + }; + + Box::into_raw(Box::new(Replica { + inner: TCReplica::new(storage), + error: None, + })) +} + +/// Utility function to safely convert *mut Replica into &mut Replica +fn rep_ref(rep: *mut Replica) -> &'static mut Replica { + debug_assert!(!rep.is_null()); + unsafe { &mut *rep } +} + +fn wrap(rep: *mut Replica, f: F, err_value: T) -> T +where + F: FnOnce(&mut Replica) -> Result, +{ + debug_assert!(!rep.is_null()); + let rep = unsafe { &mut *rep }; + match f(rep) { + Ok(v) => v, + Err(e) => { + rep.error = Some(CString::new(e.as_bytes()).unwrap()); + err_value + } + } +} + +/// temporary (testing errors) +#[no_mangle] +pub extern "C" fn uhoh<'a>(rep: *mut Replica) -> u32 { + wrap(rep, |rep| Err("uhoh!"), 0) +} + +/// Get the latest error for a replica, or NULL if the last operation succeeded. +/// +/// The returned string is valid until the next replica operation. +#[no_mangle] +pub extern "C" fn tc_replica_error<'a>(rep: *mut Replica) -> *const c_char { + let rep: &'a Replica = rep_ref(rep); + if let Some(ref e) = rep.error { + e.as_ptr() + } else { + std::ptr::null() + } +} + +/// Free a Replica. +#[no_mangle] +pub extern "C" fn tc_replica_free(rep: *mut Replica) { + drop(unsafe { Box::from_raw(rep) }); +} diff --git a/lib/src/storage.rs b/lib/src/storage.rs deleted file mode 100644 index 96950dcd9..000000000 --- a/lib/src/storage.rs +++ /dev/null @@ -1,16 +0,0 @@ -use taskchampion::{storage::Storage, StorageConfig}; - -pub struct StoragePtr(Box); - -#[no_mangle] -pub extern "C" fn storage_new_in_memory() -> *mut StoragePtr { - // TODO: this is a box containing a fat pointer - Box::into_raw(Box::new(StoragePtr( - StorageConfig::InMemory.into_storage().unwrap(), - ))) -} - -#[no_mangle] -pub extern "C" fn storage_free(storage: *mut StoragePtr) { - drop(unsafe { Box::from_raw(storage) }); -} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index dc8631325..ac28be560 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -4,12 +4,31 @@ #include #include -struct StoragePtr; +/// A replica represents an instance of a user's task data, providing an easy interface +/// for querying and modifying that data. +struct Replica; extern "C" { -StoragePtr *storage_new_in_memory(); +/// Create a new Replica. +/// +/// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the +/// on-disk storage for this replica. The path argument is no longer referenced after return. +/// +/// Returns NULL on error; see tc_replica_error. +/// +/// Replicas are not threadsafe. +Replica *tc_replica_new(const char *path); -void storage_free(StoragePtr *storage); +/// temporary (testing errors) +uint32_t uhoh(Replica *rep); + +/// Get the latest error for a replica, or NULL if the last operation succeeded. +/// +/// The returned string is valid until the next replica operation. +const char *tc_replica_error(Replica *rep); + +/// Free a Replica. +void tc_replica_free(Replica *rep); } // extern "C" From e590dc7c9894f0fe642d1f4bd67f63b6c6137028 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 17:34:59 +0000 Subject: [PATCH 432/548] add tc_replica_undo --- Cargo.lock | 1 + binding-tests/replica.cpp | 12 +++++++++--- lib/Cargo.toml | 1 + lib/src/replica.rs | 26 +++++++++++++++++--------- lib/taskchampion.h | 7 +++++-- 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91cc866d9..b2f438354 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3031,6 +3031,7 @@ dependencies = [ name = "taskchampion-lib" version = "0.1.0" dependencies = [ + "anyhow", "cbindgen", "libc", "taskchampion", diff --git a/binding-tests/replica.cpp b/binding-tests/replica.cpp index c8e11cedc..6400b8002 100644 --- a/binding-tests/replica.cpp +++ b/binding-tests/replica.cpp @@ -5,8 +5,14 @@ TEST_CASE("creating an in-memory Replica does not crash") { Replica *rep = tc_replica_new(NULL); CHECK(tc_replica_error(rep) == NULL); - uhoh(rep); - REQUIRE(tc_replica_error(rep) != NULL); - CHECK(strcmp(tc_replica_error(rep), "uhoh!") == 0); + tc_replica_free(rep); +} + +TEST_CASE("undo on an empty in-memory Replica does nothing") { + Replica *rep = tc_replica_new(NULL); + CHECK(tc_replica_error(rep) == NULL); + int rv = tc_replica_undo(rep); + CHECK(rv == 0); + CHECK(tc_replica_error(rep) == NULL); tc_replica_free(rep); } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 956c5cbef..5d9b44fc6 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["cdylib"] [dependencies] libc = "0.2.113" taskchampion = { path = "../taskchampion" } +anyhow = "1.0" [build-dependencies] cbindgen = "0.20.0" diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 81656519b..98b38e389 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -10,6 +10,7 @@ use std::os::unix::ffi::OsStrExt; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. pub struct Replica { + // TODO: make this an option so that it can be take()n when holding a mut ref inner: TCReplica, error: Option, } @@ -51,25 +52,32 @@ fn rep_ref(rep: *mut Replica) -> &'static mut Replica { unsafe { &mut *rep } } -fn wrap(rep: *mut Replica, f: F, err_value: T) -> T +fn wrap<'a, T, F>(rep: *mut Replica, f: F, err_value: T) -> T where - F: FnOnce(&mut Replica) -> Result, + F: FnOnce(&mut TCReplica) -> anyhow::Result, { - debug_assert!(!rep.is_null()); - let rep = unsafe { &mut *rep }; - match f(rep) { + let rep: &'a mut Replica = rep_ref(rep); + match f(&mut rep.inner) { Ok(v) => v, Err(e) => { - rep.error = Some(CString::new(e.as_bytes()).unwrap()); + let error = e.to_string(); + let error = match CString::new(error.as_bytes()) { + Ok(e) => e, + Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(), + }; + rep.error = Some(error); err_value } } } -/// temporary (testing errors) +/// Undo local operations until the most recent UndoPoint. +/// +/// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were +/// undone. #[no_mangle] -pub extern "C" fn uhoh<'a>(rep: *mut Replica) -> u32 { - wrap(rep, |rep| Err("uhoh!"), 0) +pub extern "C" fn tc_replica_undo<'a>(rep: *mut Replica) -> i32 { + wrap(rep, |rep| Ok(if rep.undo()? { 1 } else { 0 }), -1) } /// Get the latest error for a replica, or NULL if the last operation succeeded. diff --git a/lib/taskchampion.h b/lib/taskchampion.h index ac28be560..081dd09cc 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -20,8 +20,11 @@ extern "C" { /// Replicas are not threadsafe. Replica *tc_replica_new(const char *path); -/// temporary (testing errors) -uint32_t uhoh(Replica *rep); +/// Undo local operations until the most recent UndoPoint. +/// +/// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were +/// undone. +int32_t tc_replica_undo(Replica *rep); /// Get the latest error for a replica, or NULL if the last operation succeeded. /// From 46e08bc04006117819cd7b5030327e6d52240f68 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 19:45:39 +0000 Subject: [PATCH 433/548] add some UUID support --- Cargo.lock | 1 + binding-tests/Makefile | 2 +- binding-tests/uuid.cpp | 38 +++++++++++++++++++++++++ lib/Cargo.toml | 1 + lib/build.rs | 10 +++++++ lib/src/lib.rs | 1 + lib/src/uuid.rs | 63 ++++++++++++++++++++++++++++++++++++++++++ lib/taskchampion.h | 21 ++++++++++++++ 8 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 binding-tests/uuid.cpp create mode 100644 lib/src/uuid.rs diff --git a/Cargo.lock b/Cargo.lock index b2f438354..4f577c437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3035,6 +3035,7 @@ dependencies = [ "cbindgen", "libc", "taskchampion", + "uuid", ] [[package]] diff --git a/binding-tests/Makefile b/binding-tests/Makefile index 3599821f4..620befab5 100644 --- a/binding-tests/Makefile +++ b/binding-tests/Makefile @@ -3,7 +3,7 @@ INC=-I ../lib LIB=-L ../target/debug RPATH=-Wl,-rpath,../target/debug -TESTS = replica.cpp +TESTS = replica.cpp uuid.cpp .PHONY: all test diff --git a/binding-tests/uuid.cpp b/binding-tests/uuid.cpp new file mode 100644 index 000000000..949d081f0 --- /dev/null +++ b/binding-tests/uuid.cpp @@ -0,0 +1,38 @@ +#include +#include "doctest.h" +#include "taskchampion.h" + +TEST_CASE("creating UUIDs does not crash") { + Uuid u1 = tc_uuid_new_v4(); + Uuid u2 = tc_uuid_nil(); +} + +TEST_CASE("converting UUIDs to string works") { + Uuid u2 = tc_uuid_nil(); + REQUIRE(TC_UUID_STRING_BYTES == 36); + + char u2str[TC_UUID_STRING_BYTES]; + tc_uuid_to_str(u2, u2str); + CHECK(strncmp(u2str, "00000000-0000-0000-0000-000000000000", TC_UUID_STRING_BYTES) == 0); +} + +TEST_CASE("converting UUIDs from string works") { + Uuid u; + char ustr[TC_UUID_STRING_BYTES] = "fdc314b7-f938-4845-b8d1-95716e4eb762"; + CHECK(tc_uuid_from_str(ustr, &u)); + CHECK(u._0[0] == 0xfd); + // .. if these two are correct, probably it worked :) + CHECK(u._0[15] == 0x62); +} + +TEST_CASE("converting invalid UUIDs from string fails as expected") { + Uuid u; + char ustr[TC_UUID_STRING_BYTES] = "not-a-valid-uuid"; + CHECK(!tc_uuid_from_str(ustr, &u)); +} + +TEST_CASE("converting invalid UTF-8 UUIDs from string fails as expected") { + Uuid u; + char ustr[TC_UUID_STRING_BYTES] = "\xf0\x28\x8c\xbc"; + CHECK(!tc_uuid_from_str(ustr, &u)); +} diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 5d9b44fc6..b01d1bcbd 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["cdylib"] [dependencies] libc = "0.2.113" taskchampion = { path = "../taskchampion" } +uuid = { version = "^0.8.2", features = ["serde", "v4"] } anyhow = "1.0" [build-dependencies] diff --git a/lib/build.rs b/lib/build.rs index 8d9db2f1f..74dfe0fc8 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -10,6 +10,16 @@ fn main() { .with_language(Language::C) .with_config(Config { cpp_compat: true, + export: ExportConfig { + item_types: vec![ + ItemType::Structs, + ItemType::Globals, + ItemType::Functions, + ItemType::Constants, + ItemType::OpaqueItems, + ], + ..Default::default() + }, ..Default::default() }) .generate() diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 78004cf88..98b7c0124 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1 +1,2 @@ pub mod replica; +pub mod uuid; diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs new file mode 100644 index 000000000..5fbbe8d7a --- /dev/null +++ b/lib/src/uuid.rs @@ -0,0 +1,63 @@ +use libc; +use taskchampion::Uuid as TcUuid; + +/// Uuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. +#[repr(C)] +pub struct Uuid([u8; 16]); + +impl From for Uuid { + fn from(uuid: TcUuid) -> Uuid { + // TODO: can we avoid clone here? + Uuid(uuid.as_bytes().clone()) + } +} + +impl From for TcUuid { + fn from(uuid: Uuid) -> TcUuid { + TcUuid::from_bytes(uuid.0) + } +} + +/// Create a new, randomly-generated UUID. +#[no_mangle] +pub extern "C" fn tc_uuid_new_v4() -> Uuid { + TcUuid::new_v4().into() +} + +/// Create a new UUID with the nil value. +#[no_mangle] +pub extern "C" fn tc_uuid_nil() -> Uuid { + TcUuid::nil().into() +} + +/// Length, in bytes, of a C string containing a Uuid. +#[no_mangle] +pub static TC_UUID_STRING_BYTES: usize = ::uuid::adapter::Hyphenated::LENGTH; + +/// Write the string representation of a Uuid into the given buffer, which must be +/// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. +#[no_mangle] +pub extern "C" fn tc_uuid_to_str<'a>(uuid: Uuid, out: *mut libc::c_char) { + debug_assert!(!out.is_null()); + let buf: &'a mut [u8] = unsafe { + std::slice::from_raw_parts_mut(out as *mut u8, ::uuid::adapter::Hyphenated::LENGTH) + }; + let uuid: TcUuid = uuid.into(); + uuid.to_hyphenated().encode_lower(buf); +} + +/// Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns +/// false on failure. +#[no_mangle] +pub extern "C" fn tc_uuid_from_str<'a>(val: *const libc::c_char, out: *mut Uuid) -> bool { + debug_assert!(!val.is_null()); + debug_assert!(!out.is_null()); + let slice = unsafe { std::slice::from_raw_parts(val as *const u8, TC_UUID_STRING_BYTES) }; + if let Ok(s) = std::str::from_utf8(slice) { + if let Ok(u) = TcUuid::parse_str(s) { + unsafe { *out = u.into() }; + return true; + } + } + false +} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 081dd09cc..e5ad1de99 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -8,8 +8,15 @@ /// for querying and modifying that data. struct Replica; +/// Uuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. +struct Uuid { + uint8_t _0[16]; +}; + extern "C" { +extern const uintptr_t TC_UUID_STRING_BYTES; + /// Create a new Replica. /// /// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the @@ -34,4 +41,18 @@ const char *tc_replica_error(Replica *rep); /// Free a Replica. void tc_replica_free(Replica *rep); +/// Create a new, randomly-generated UUID. +Uuid tc_uuid_new_v4(); + +/// Create a new UUID with the nil value. +Uuid tc_uuid_nil(); + +/// Write the string representation of a Uuid into the given buffer, which must be +/// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. +void tc_uuid_to_str(Uuid uuid, char *out); + +/// Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns +/// false on failure. +bool tc_uuid_from_str(const char *val, Uuid *out); + } // extern "C" From 821118106aae65e3ad8676ea2303ae8cbab7e658 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 19:57:42 +0000 Subject: [PATCH 434/548] add TC prefix to types, too --- binding-tests/replica.cpp | 8 ++++---- binding-tests/uuid.cpp | 18 ++++++++--------- lib/build.rs | 10 ---------- lib/src/replica.rs | 37 +++++++++++++++++------------------ lib/src/uuid.rs | 41 +++++++++++++++++++++------------------ lib/taskchampion.h | 32 +++++++++++++++--------------- 6 files changed, 69 insertions(+), 77 deletions(-) diff --git a/binding-tests/replica.cpp b/binding-tests/replica.cpp index 6400b8002..4e1053d8c 100644 --- a/binding-tests/replica.cpp +++ b/binding-tests/replica.cpp @@ -2,14 +2,14 @@ #include "doctest.h" #include "taskchampion.h" -TEST_CASE("creating an in-memory Replica does not crash") { - Replica *rep = tc_replica_new(NULL); +TEST_CASE("creating an in-memory TCReplica does not crash") { + TCReplica *rep = tc_replica_new(NULL); CHECK(tc_replica_error(rep) == NULL); tc_replica_free(rep); } -TEST_CASE("undo on an empty in-memory Replica does nothing") { - Replica *rep = tc_replica_new(NULL); +TEST_CASE("undo on an empty in-memory TCReplica does nothing") { + TCReplica *rep = tc_replica_new(NULL); CHECK(tc_replica_error(rep) == NULL); int rv = tc_replica_undo(rep); CHECK(rv == 0); diff --git a/binding-tests/uuid.cpp b/binding-tests/uuid.cpp index 949d081f0..aba07d867 100644 --- a/binding-tests/uuid.cpp +++ b/binding-tests/uuid.cpp @@ -3,12 +3,12 @@ #include "taskchampion.h" TEST_CASE("creating UUIDs does not crash") { - Uuid u1 = tc_uuid_new_v4(); - Uuid u2 = tc_uuid_nil(); + TCUuid u1 = tc_uuid_new_v4(); + TCUuid u2 = tc_uuid_nil(); } TEST_CASE("converting UUIDs to string works") { - Uuid u2 = tc_uuid_nil(); + TCUuid u2 = tc_uuid_nil(); REQUIRE(TC_UUID_STRING_BYTES == 36); char u2str[TC_UUID_STRING_BYTES]; @@ -17,22 +17,22 @@ TEST_CASE("converting UUIDs to string works") { } TEST_CASE("converting UUIDs from string works") { - Uuid u; + TCUuid u; char ustr[TC_UUID_STRING_BYTES] = "fdc314b7-f938-4845-b8d1-95716e4eb762"; CHECK(tc_uuid_from_str(ustr, &u)); - CHECK(u._0[0] == 0xfd); - // .. if these two are correct, probably it worked :) - CHECK(u._0[15] == 0x62); + CHECK(u.bytes[0] == 0xfd); + // .. if these two bytes are correct, then it probably worked :) + CHECK(u.bytes[15] == 0x62); } TEST_CASE("converting invalid UUIDs from string fails as expected") { - Uuid u; + TCUuid u; char ustr[TC_UUID_STRING_BYTES] = "not-a-valid-uuid"; CHECK(!tc_uuid_from_str(ustr, &u)); } TEST_CASE("converting invalid UTF-8 UUIDs from string fails as expected") { - Uuid u; + TCUuid u; char ustr[TC_UUID_STRING_BYTES] = "\xf0\x28\x8c\xbc"; CHECK(!tc_uuid_from_str(ustr, &u)); } diff --git a/lib/build.rs b/lib/build.rs index 74dfe0fc8..8d9db2f1f 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -10,16 +10,6 @@ fn main() { .with_language(Language::C) .with_config(Config { cpp_compat: true, - export: ExportConfig { - item_types: vec![ - ItemType::Structs, - ItemType::Globals, - ItemType::Functions, - ItemType::Constants, - ItemType::OpaqueItems, - ], - ..Default::default() - }, ..Default::default() }) .generate() diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 98b38e389..44066507e 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,30 +1,29 @@ use libc::c_char; use std::ffi::{CStr, CString, OsStr}; use std::path::PathBuf; -use taskchampion::Replica as TCReplica; -use taskchampion::StorageConfig; +use taskchampion::{Replica, StorageConfig}; // TODO: unix-only use std::os::unix::ffi::OsStrExt; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. -pub struct Replica { +pub struct TCReplica { // TODO: make this an option so that it can be take()n when holding a mut ref - inner: TCReplica, + inner: Replica, error: Option, } -/// Create a new Replica. +/// Create a new TCReplica. /// /// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the /// on-disk storage for this replica. The path argument is no longer referenced after return. /// /// Returns NULL on error; see tc_replica_error. /// -/// Replicas are not threadsafe. +/// TCReplicas are not threadsafe. #[no_mangle] -pub extern "C" fn tc_replica_new<'a>(path: *const c_char) -> *mut Replica { +pub extern "C" fn tc_replica_new<'a>(path: *const c_char) -> *mut TCReplica { let storage_res = if path.is_null() { StorageConfig::InMemory.into_storage() } else { @@ -40,23 +39,23 @@ pub extern "C" fn tc_replica_new<'a>(path: *const c_char) -> *mut Replica { Err(_) => return std::ptr::null_mut(), }; - Box::into_raw(Box::new(Replica { - inner: TCReplica::new(storage), + Box::into_raw(Box::new(TCReplica { + inner: Replica::new(storage), error: None, })) } -/// Utility function to safely convert *mut Replica into &mut Replica -fn rep_ref(rep: *mut Replica) -> &'static mut Replica { +/// Utility function to safely convert *mut TCReplica into &mut TCReplica +fn rep_ref(rep: *mut TCReplica) -> &'static mut TCReplica { debug_assert!(!rep.is_null()); unsafe { &mut *rep } } -fn wrap<'a, T, F>(rep: *mut Replica, f: F, err_value: T) -> T +fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T where - F: FnOnce(&mut TCReplica) -> anyhow::Result, + F: FnOnce(&mut Replica) -> anyhow::Result, { - let rep: &'a mut Replica = rep_ref(rep); + let rep: &'a mut TCReplica = rep_ref(rep); match f(&mut rep.inner) { Ok(v) => v, Err(e) => { @@ -76,7 +75,7 @@ where /// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were /// undone. #[no_mangle] -pub extern "C" fn tc_replica_undo<'a>(rep: *mut Replica) -> i32 { +pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica) -> i32 { wrap(rep, |rep| Ok(if rep.undo()? { 1 } else { 0 }), -1) } @@ -84,8 +83,8 @@ pub extern "C" fn tc_replica_undo<'a>(rep: *mut Replica) -> i32 { /// /// The returned string is valid until the next replica operation. #[no_mangle] -pub extern "C" fn tc_replica_error<'a>(rep: *mut Replica) -> *const c_char { - let rep: &'a Replica = rep_ref(rep); +pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *const c_char { + let rep: &'a TCReplica = rep_ref(rep); if let Some(ref e) = rep.error { e.as_ptr() } else { @@ -93,8 +92,8 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut Replica) -> *const c_char { } } -/// Free a Replica. +/// Free a TCReplica. #[no_mangle] -pub extern "C" fn tc_replica_free(rep: *mut Replica) { +pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { drop(unsafe { Box::from_raw(rep) }); } diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 5fbbe8d7a..8ce407afc 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -1,60 +1,63 @@ use libc; -use taskchampion::Uuid as TcUuid; +use taskchampion::Uuid; -/// Uuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. +/// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. +/// Uuids are typically treated as opaque, but the bytes are available in big-endian format. +/// +/// cbindgen:field-names=[bytes] #[repr(C)] -pub struct Uuid([u8; 16]); +pub struct TCUuid([u8; 16]); -impl From for Uuid { - fn from(uuid: TcUuid) -> Uuid { +impl From for TCUuid { + fn from(uuid: Uuid) -> TCUuid { // TODO: can we avoid clone here? - Uuid(uuid.as_bytes().clone()) + TCUuid(uuid.as_bytes().clone()) } } -impl From for TcUuid { - fn from(uuid: Uuid) -> TcUuid { - TcUuid::from_bytes(uuid.0) +impl From for Uuid { + fn from(uuid: TCUuid) -> Uuid { + Uuid::from_bytes(uuid.0) } } /// Create a new, randomly-generated UUID. #[no_mangle] -pub extern "C" fn tc_uuid_new_v4() -> Uuid { - TcUuid::new_v4().into() +pub extern "C" fn tc_uuid_new_v4() -> TCUuid { + Uuid::new_v4().into() } /// Create a new UUID with the nil value. #[no_mangle] -pub extern "C" fn tc_uuid_nil() -> Uuid { - TcUuid::nil().into() +pub extern "C" fn tc_uuid_nil() -> TCUuid { + Uuid::nil().into() } -/// Length, in bytes, of a C string containing a Uuid. +/// Length, in bytes, of a C string containing a TCUuid. #[no_mangle] pub static TC_UUID_STRING_BYTES: usize = ::uuid::adapter::Hyphenated::LENGTH; -/// Write the string representation of a Uuid into the given buffer, which must be +/// Write the string representation of a TCUuid into the given buffer, which must be /// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. #[no_mangle] -pub extern "C" fn tc_uuid_to_str<'a>(uuid: Uuid, out: *mut libc::c_char) { +pub extern "C" fn tc_uuid_to_str<'a>(uuid: TCUuid, out: *mut libc::c_char) { debug_assert!(!out.is_null()); let buf: &'a mut [u8] = unsafe { std::slice::from_raw_parts_mut(out as *mut u8, ::uuid::adapter::Hyphenated::LENGTH) }; - let uuid: TcUuid = uuid.into(); + let uuid: Uuid = uuid.into(); uuid.to_hyphenated().encode_lower(buf); } /// Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns /// false on failure. #[no_mangle] -pub extern "C" fn tc_uuid_from_str<'a>(val: *const libc::c_char, out: *mut Uuid) -> bool { +pub extern "C" fn tc_uuid_from_str<'a>(val: *const libc::c_char, out: *mut TCUuid) -> bool { debug_assert!(!val.is_null()); debug_assert!(!out.is_null()); let slice = unsafe { std::slice::from_raw_parts(val as *const u8, TC_UUID_STRING_BYTES) }; if let Ok(s) = std::str::from_utf8(slice) { - if let Ok(u) = TcUuid::parse_str(s) { + if let Ok(u) = Uuid::parse_str(s) { unsafe { *out = u.into() }; return true; } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index e5ad1de99..c018143d5 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -6,53 +6,53 @@ /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. -struct Replica; +struct TCReplica; -/// Uuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. -struct Uuid { - uint8_t _0[16]; +/// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. +struct TCUuid { + uint8_t bytes[16]; }; extern "C" { extern const uintptr_t TC_UUID_STRING_BYTES; -/// Create a new Replica. +/// Create a new TCReplica. /// /// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the /// on-disk storage for this replica. The path argument is no longer referenced after return. /// /// Returns NULL on error; see tc_replica_error. /// -/// Replicas are not threadsafe. -Replica *tc_replica_new(const char *path); +/// TCReplicas are not threadsafe. +TCReplica *tc_replica_new(const char *path); /// Undo local operations until the most recent UndoPoint. /// /// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were /// undone. -int32_t tc_replica_undo(Replica *rep); +int32_t tc_replica_undo(TCReplica *rep); /// Get the latest error for a replica, or NULL if the last operation succeeded. /// /// The returned string is valid until the next replica operation. -const char *tc_replica_error(Replica *rep); +const char *tc_replica_error(TCReplica *rep); -/// Free a Replica. -void tc_replica_free(Replica *rep); +/// Free a TCReplica. +void tc_replica_free(TCReplica *rep); /// Create a new, randomly-generated UUID. -Uuid tc_uuid_new_v4(); +TCUuid tc_uuid_new_v4(); /// Create a new UUID with the nil value. -Uuid tc_uuid_nil(); +TCUuid tc_uuid_nil(); -/// Write the string representation of a Uuid into the given buffer, which must be +/// Write the string representation of a TCUuid into the given buffer, which must be /// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. -void tc_uuid_to_str(Uuid uuid, char *out); +void tc_uuid_to_str(TCUuid uuid, char *out); /// Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns /// false on failure. -bool tc_uuid_from_str(const char *val, Uuid *out); +bool tc_uuid_from_str(const char *val, TCUuid *out); } // extern "C" From bb722325fe4a23aa24093fc25c0646a10586ad97 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 22:45:57 +0000 Subject: [PATCH 435/548] more task functionality --- binding-tests/Makefile | 2 +- binding-tests/task.cpp | 35 ++++++++++++ lib/build.rs | 5 ++ lib/src/lib.rs | 3 ++ lib/src/replica.rs | 86 +++++++++++++++++++++-------- lib/src/status.rs | 36 +++++++++++++ lib/src/string.rs | 48 +++++++++++++++++ lib/src/task.rs | 96 +++++++++++++++++++++++++++++++++ lib/taskchampion.h | 45 ++++++++++++++++ taskchampion/src/task/status.rs | 1 + 10 files changed, 333 insertions(+), 24 deletions(-) create mode 100644 binding-tests/task.cpp create mode 100644 lib/src/status.rs create mode 100644 lib/src/string.rs create mode 100644 lib/src/task.rs diff --git a/binding-tests/Makefile b/binding-tests/Makefile index 620befab5..121d942cc 100644 --- a/binding-tests/Makefile +++ b/binding-tests/Makefile @@ -3,7 +3,7 @@ INC=-I ../lib LIB=-L ../target/debug RPATH=-Wl,-rpath,../target/debug -TESTS = replica.cpp uuid.cpp +TESTS = replica.cpp uuid.cpp task.cpp .PHONY: all test diff --git a/binding-tests/task.cpp b/binding-tests/task.cpp new file mode 100644 index 000000000..0592b6b23 --- /dev/null +++ b/binding-tests/task.cpp @@ -0,0 +1,35 @@ +#include +#include "doctest.h" +#include "taskchampion.h" + +TEST_CASE("creating a Task does not crash") { + TCReplica *rep = tc_replica_new(NULL); + CHECK(tc_replica_error(rep) == NULL); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_new("my task")); + REQUIRE(task != NULL); + + CHECK(tc_task_get_status(task) == TC_STATUS_PENDING); + + TCString *desc = tc_task_get_description(task); + REQUIRE(desc != NULL); + CHECK(strcmp(tc_string_content(desc), "my task") == 0); + tc_string_free(desc); + + tc_task_free(task); + + tc_replica_free(rep); +} + +TEST_CASE("undo on an empty in-memory TCReplica does nothing") { + TCReplica *rep = tc_replica_new(NULL); + CHECK(tc_replica_error(rep) == NULL); + int rv = tc_replica_undo(rep); + CHECK(rv == 0); + CHECK(tc_replica_error(rep) == NULL); + tc_replica_free(rep); +} + diff --git a/lib/build.rs b/lib/build.rs index 8d9db2f1f..13eda1bc5 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -10,6 +10,11 @@ fn main() { .with_language(Language::C) .with_config(Config { cpp_compat: true, + enumeration: EnumConfig { + // this appears to still default to true for C + enum_class: false, + ..Default::default() + }, ..Default::default() }) .generate() diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 98b7c0124..a97d0f732 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,2 +1,5 @@ pub mod replica; +pub mod status; +pub mod string; +pub mod task; pub mod uuid; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 44066507e..81a387ca5 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,3 +1,4 @@ +use crate::{status::TCStatus, string::TCString, task::TCTask}; use libc::c_char; use std::ffi::{CStr, CString, OsStr}; use std::path::PathBuf; @@ -9,11 +10,37 @@ use std::os::unix::ffi::OsStrExt; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. pub struct TCReplica { - // TODO: make this an option so that it can be take()n when holding a mut ref + // TODO: make this a RefCell so that it can be take()n when holding a mut ref inner: Replica, error: Option, } +/// Utility function to safely convert *mut TCReplica into &mut TCReplica +fn rep_ref(rep: *mut TCReplica) -> &'static mut TCReplica { + debug_assert!(!rep.is_null()); + unsafe { &mut *rep } +} + +/// Utility function to allow using `?` notation to return an error value. +fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T +where + F: FnOnce(&mut Replica) -> anyhow::Result, +{ + let rep: &'a mut TCReplica = rep_ref(rep); + match f(&mut rep.inner) { + Ok(v) => v, + Err(e) => { + let error = e.to_string(); + let error = match CString::new(error.as_bytes()) { + Ok(e) => e, + Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(), + }; + rep.error = Some(error); + err_value + } + } +} + /// Create a new TCReplica. /// /// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the @@ -45,30 +72,37 @@ pub extern "C" fn tc_replica_new<'a>(path: *const c_char) -> *mut TCReplica { })) } -/// Utility function to safely convert *mut TCReplica into &mut TCReplica -fn rep_ref(rep: *mut TCReplica) -> &'static mut TCReplica { - debug_assert!(!rep.is_null()); - unsafe { &mut *rep } +/* + * TODO: + * - tc_replica_all_tasks + * - tc_replica_all_task_uuids + * - tc_replica_working_set + * - tc_replica_get_task + */ + +/// Create a new task. The task must not already exist. +/// +/// Returns the task, or NULL on error. +#[no_mangle] +pub extern "C" fn tc_replica_new_task<'a>( + rep: *mut TCReplica, + status: TCStatus, + description: *mut TCString, +) -> *mut TCTask { + wrap( + rep, + |rep| { + let description = TCString::from_arg(description); + let task = rep.new_task(status.into(), description.as_str()?.to_string())?; + Ok(TCTask::as_ptr(task)) + }, + std::ptr::null_mut(), + ) } -fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T -where - F: FnOnce(&mut Replica) -> anyhow::Result, -{ - let rep: &'a mut TCReplica = rep_ref(rep); - match f(&mut rep.inner) { - Ok(v) => v, - Err(e) => { - let error = e.to_string(); - let error = match CString::new(error.as_bytes()) { - Ok(e) => e, - Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(), - }; - rep.error = Some(error); - err_value - } - } -} +/* - tc_replica_import_task_with_uuid + * - tc_replica_sync + */ /// Undo local operations until the most recent UndoPoint. /// @@ -95,5 +129,11 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *const c_char { /// Free a TCReplica. #[no_mangle] pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { + debug_assert!(!rep.is_null()); drop(unsafe { Box::from_raw(rep) }); } + +/* + * - tc_replica_rebuild_working_set + * - tc_replica_add_undo_point + */ diff --git a/lib/src/status.rs b/lib/src/status.rs new file mode 100644 index 000000000..306f27630 --- /dev/null +++ b/lib/src/status.rs @@ -0,0 +1,36 @@ +pub use taskchampion::Status; + +/// The status of a task, as defined by the task data model. +/// cbindgen:prefix-with-name +/// cbindgen:rename-all=ScreamingSnakeCase +#[repr(C)] +pub enum TCStatus { + Pending, + Completed, + Deleted, + /// Unknown signifies a status in the task DB that was not + /// recognized. + Unknown, +} + +impl From for Status { + fn from(status: TCStatus) -> Status { + match status { + TCStatus::Pending => Status::Pending, + TCStatus::Completed => Status::Completed, + TCStatus::Deleted => Status::Deleted, + TCStatus::Unknown => Status::Unknown("unknown".to_string()), + } + } +} + +impl From for TCStatus { + fn from(status: Status) -> TCStatus { + match status { + Status::Pending => TCStatus::Pending, + Status::Completed => TCStatus::Completed, + Status::Deleted => TCStatus::Deleted, + Status::Unknown(_) => TCStatus::Unknown, + } + } +} diff --git a/lib/src/string.rs b/lib/src/string.rs new file mode 100644 index 000000000..39e17be13 --- /dev/null +++ b/lib/src/string.rs @@ -0,0 +1,48 @@ +use std::ffi::{CStr, CString, NulError}; + +// thinking: +// - TCString ownership always taken when passed in +// - TCString ownership always falls to C when passed out +// - accept that bytes must be copied to get owned string +// - Can we do this with an enum of some sort? + +/// TCString supports passing strings into and out of the TaskChampion API. +pub struct TCString(CString); + +impl TCString { + /// Take a TCString from C as an argument. + pub(crate) fn from_arg(tcstring: *mut TCString) -> Self { + debug_assert!(!tcstring.is_null()); + *(unsafe { Box::from_raw(tcstring) }) + } + + /// Get a regular Rust &str for this value. + pub(crate) fn as_str(&self) -> Result<&str, std::str::Utf8Error> { + self.0.as_c_str().to_str() + } + + /// Construct a *mut TCString from a string for returning to C. + pub(crate) fn return_string(string: impl Into>) -> Result<*mut TCString, NulError> { + let tcstring = TCString(CString::new(string)?); + Ok(Box::into_raw(Box::new(tcstring))) + } +} + +#[no_mangle] +pub extern "C" fn tc_string_new(cstr: *const libc::c_char) -> *mut TCString { + let cstring = unsafe { CStr::from_ptr(cstr) }.into(); + Box::into_raw(Box::new(TCString(cstring))) +} + +#[no_mangle] +pub extern "C" fn tc_string_content(string: *mut TCString) -> *const libc::c_char { + debug_assert!(!string.is_null()); + let string: &CString = unsafe { &(*string).0 }; + string.as_ptr() +} + +#[no_mangle] +pub extern "C" fn tc_string_free(string: *mut TCString) { + debug_assert!(!string.is_null()); + drop(unsafe { Box::from_raw(string) }); +} diff --git a/lib/src/task.rs b/lib/src/task.rs new file mode 100644 index 000000000..781b5d3d0 --- /dev/null +++ b/lib/src/task.rs @@ -0,0 +1,96 @@ +use crate::{status::TCStatus, string::TCString, uuid::TCUuid}; +use taskchampion::Task; + +/// A task, as publicly exposed by this library. +/// +/// A task carries no reference to the replica that created it, and can +/// be used until it is freed or converted to a TaskMut. +pub struct TCTask { + inner: Task, +} + +impl TCTask { + pub(crate) fn as_ptr(task: Task) -> *mut TCTask { + Box::into_raw(Box::new(TCTask { inner: task })) + } +} + +/// Utility function to allow using `?` notation to return an error value. +fn wrap<'a, T, F>(task: *const TCTask, f: F, err_value: T) -> T +where + F: FnOnce(&Task) -> anyhow::Result, +{ + let task: &'a Task = task_ref(task); + match f(task) { + Ok(v) => v, + Err(e) => { + /* + let error = e.to_string(); + let error = match CString::new(error.as_bytes()) { + Ok(e) => e, + Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(), + }; + */ + //task.error = Some(error); + err_value + } + } +} + +/// Utility function to safely convert *const TCTask into &Task +fn task_ref(task: *const TCTask) -> &'static Task { + debug_assert!(!task.is_null()); + unsafe { &(*task).inner } +} + +/// Get a task's UUID. +#[no_mangle] +pub extern "C" fn tc_task_get_uuid<'a>(task: *const TCTask) -> TCUuid { + let task: &'a Task = task_ref(task); + let uuid = task.get_uuid(); + uuid.into() +} + +/// Get a task's status. +#[no_mangle] +pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { + let task: &'a Task = task_ref(task); + task.get_status().into() +} + +/* TODO + * into_mut + * get_taskmap + */ + +/// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it +/// contains embedded NUL characters). +#[no_mangle] +pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCString { + wrap( + task, + |task| Ok(TCString::return_string(task.get_description())?), + std::ptr::null_mut(), + ) +} + +/* TODO + * get_wait + * is_waiting + * is_active + * has_tag + * get_tags + * get_annotations + * get_uda + * get_udas + * get_legacy_uda + * get_legacy_udas + * get_modified + */ + +/// Free a task. +#[no_mangle] +pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) { + debug_assert!(!task.is_null()); + drop(unsafe { Box::from_raw(task) }); +} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index c018143d5..5d9691d70 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -4,11 +4,32 @@ #include #include +/// The status of a task, as defined by the task data model. +enum TCStatus { + TC_STATUS_PENDING, + TC_STATUS_COMPLETED, + TC_STATUS_DELETED, + /// Unknown signifies a status in the task DB that was not + /// recognized. + TC_STATUS_UNKNOWN, +}; + /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. struct TCReplica; +/// TCString supports passing strings into and out of the TaskChampion API. +struct TCString; + +/// A task, as publicly exposed by this library. +/// +/// A task carries no reference to the replica that created it, and can +/// be used until it is freed or converted to a TaskMut. +struct TCTask; + /// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. +/// Uuids are typically treated as opaque, but the bytes are available in big-endian format. +/// struct TCUuid { uint8_t bytes[16]; }; @@ -27,6 +48,11 @@ extern const uintptr_t TC_UUID_STRING_BYTES; /// TCReplicas are not threadsafe. TCReplica *tc_replica_new(const char *path); +/// Create a new task. The task must not already exist. +/// +/// Returns the task, or NULL on error. +TCTask *tc_replica_new_task(TCReplica *rep, TCStatus status, TCString *description); + /// Undo local operations until the most recent UndoPoint. /// /// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were @@ -41,6 +67,25 @@ const char *tc_replica_error(TCReplica *rep); /// Free a TCReplica. void tc_replica_free(TCReplica *rep); +TCString *tc_string_new(const char *cstr); + +const char *tc_string_content(TCString *string); + +void tc_string_free(TCString *string); + +/// Get a task's UUID. +TCUuid tc_task_get_uuid(const TCTask *task); + +/// Get a task's status. +TCStatus tc_task_get_status(const TCTask *task); + +/// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it +/// contains embedded NUL characters). +TCString *tc_task_get_description(const TCTask *task); + +/// Free a task. +void tc_task_free(TCTask *task); + /// Create a new, randomly-generated UUID. TCUuid tc_uuid_new_v4(); diff --git a/taskchampion/src/task/status.rs b/taskchampion/src/task/status.rs index 2b2afb6ba..31fee9cf7 100644 --- a/taskchampion/src/task/status.rs +++ b/taskchampion/src/task/status.rs @@ -1,5 +1,6 @@ /// The status of a task, as defined by the task data model. #[derive(Debug, PartialEq, Clone, strum_macros::Display)] +#[repr(C)] pub enum Status { Pending, Completed, From 65082c26e7314a2d91dbb0dc9dd25a00570cec73 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 23:58:47 +0000 Subject: [PATCH 436/548] improved TCString support --- binding-tests/.gitignore | 1 + binding-tests/Makefile | 4 +- binding-tests/replica.cpp | 6 ++ binding-tests/string.cpp | 29 ++++++++ binding-tests/task.cpp | 10 --- lib/src/replica.rs | 17 ++--- lib/src/string.rs | 138 +++++++++++++++++++++++++++++++------- lib/src/task.rs | 32 ++------- lib/taskchampion.h | 22 +++++- 9 files changed, 183 insertions(+), 76 deletions(-) create mode 100644 binding-tests/string.cpp diff --git a/binding-tests/.gitignore b/binding-tests/.gitignore index fb93ddffb..aa82f31b4 100644 --- a/binding-tests/.gitignore +++ b/binding-tests/.gitignore @@ -1,2 +1,3 @@ *.o doctest +test-db diff --git a/binding-tests/Makefile b/binding-tests/Makefile index 121d942cc..26f621596 100644 --- a/binding-tests/Makefile +++ b/binding-tests/Makefile @@ -3,14 +3,16 @@ INC=-I ../lib LIB=-L ../target/debug RPATH=-Wl,-rpath,../target/debug -TESTS = replica.cpp uuid.cpp task.cpp +TESTS = replica.cpp string.cpp uuid.cpp task.cpp .PHONY: all test all: test test: doctest + @rm -rf test-db @./doctest --no-version --no-intro + @rm -rf test-db %.o: %.cpp ../lib/taskchampion.h $(CXX) $(INC) -c $< -o $@ diff --git a/binding-tests/replica.cpp b/binding-tests/replica.cpp index 4e1053d8c..18364a653 100644 --- a/binding-tests/replica.cpp +++ b/binding-tests/replica.cpp @@ -8,6 +8,12 @@ TEST_CASE("creating an in-memory TCReplica does not crash") { tc_replica_free(rep); } +TEST_CASE("creating an on-disk TCReplica does not crash") { + TCReplica *rep = tc_replica_new(tc_string_new("test-db")); + CHECK(tc_replica_error(rep) == NULL); + tc_replica_free(rep); +} + TEST_CASE("undo on an empty in-memory TCReplica does nothing") { TCReplica *rep = tc_replica_new(NULL); CHECK(tc_replica_error(rep) == NULL); diff --git a/binding-tests/string.cpp b/binding-tests/string.cpp new file mode 100644 index 000000000..dd726e862 --- /dev/null +++ b/binding-tests/string.cpp @@ -0,0 +1,29 @@ +#include +#include "doctest.h" +#include "taskchampion.h" + +TEST_CASE("creating borrowed strings does not crash") { + TCString *s = tc_string_new("abcdef"); + tc_string_free(s); +} + +TEST_CASE("creating cloned strings does not crash") { + char *abcdef = strdup("abcdef"); + TCString *s = tc_string_clone(abcdef); + free(abcdef); + CHECK(strcmp(tc_string_content(s), "abcdef") == 0); + tc_string_free(s); +} + +TEST_CASE("strings echo back their content") { + TCString *s = tc_string_new("abcdef"); + CHECK(strcmp(tc_string_content(s), "abcdef") == 0); + tc_string_free(s); +} + +TEST_CASE("tc_string_content returns NULL for strings containing embedded NULs") { + TCString *s = tc_string_clone_with_len("ab\0de", 5); + REQUIRE(s != NULL); + CHECK(tc_string_content(s) == NULL); + tc_string_free(s); +} diff --git a/binding-tests/task.cpp b/binding-tests/task.cpp index 0592b6b23..9d82767ca 100644 --- a/binding-tests/task.cpp +++ b/binding-tests/task.cpp @@ -23,13 +23,3 @@ TEST_CASE("creating a Task does not crash") { tc_replica_free(rep); } - -TEST_CASE("undo on an empty in-memory TCReplica does nothing") { - TCReplica *rep = tc_replica_new(NULL); - CHECK(tc_replica_error(rep) == NULL); - int rv = tc_replica_undo(rep); - CHECK(rv == 0); - CHECK(tc_replica_error(rep) == NULL); - tc_replica_free(rep); -} - diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 81a387ca5..c137b105a 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,12 +1,8 @@ use crate::{status::TCStatus, string::TCString, task::TCTask}; use libc::c_char; -use std::ffi::{CStr, CString, OsStr}; -use std::path::PathBuf; +use std::ffi::CString; use taskchampion::{Replica, StorageConfig}; -// TODO: unix-only -use std::os::unix::ffi::OsStrExt; - /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. pub struct TCReplica { @@ -50,14 +46,15 @@ where /// /// TCReplicas are not threadsafe. #[no_mangle] -pub extern "C" fn tc_replica_new<'a>(path: *const c_char) -> *mut TCReplica { +pub extern "C" fn tc_replica_new<'a>(path: *mut TCString) -> *mut TCReplica { let storage_res = if path.is_null() { StorageConfig::InMemory.into_storage() } else { - let path: &'a [u8] = unsafe { CStr::from_ptr(path) }.to_bytes(); - let path: &OsStr = OsStr::from_bytes(path); - let path: PathBuf = path.to_os_string().into(); - StorageConfig::OnDisk { taskdb_dir: path }.into_storage() + let path = TCString::from_arg(path); + StorageConfig::OnDisk { + taskdb_dir: path.to_path_buf(), + } + .into_storage() }; let storage = match storage_res { diff --git a/lib/src/string.rs b/lib/src/string.rs index 39e17be13..3f9086d0e 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -1,46 +1,134 @@ -use std::ffi::{CStr, CString, NulError}; - -// thinking: -// - TCString ownership always taken when passed in -// - TCString ownership always falls to C when passed out -// - accept that bytes must be copied to get owned string -// - Can we do this with an enum of some sort? +use std::ffi::{CStr, CString, OsStr}; +use std::os::unix::ffi::OsStrExt; +use std::path::PathBuf; /// TCString supports passing strings into and out of the TaskChampion API. -pub struct TCString(CString); +/// +/// Unless specified otherwise, functions in this API take ownership of a TCString when it appears +/// as a function argument, and transfer ownership to the caller when the TCString appears as a +/// return value or otput argument. +pub enum TCString<'a> { + CString(CString), + CStr(&'a CStr), + String(String), +} -impl TCString { - /// Take a TCString from C as an argument. - pub(crate) fn from_arg(tcstring: *mut TCString) -> Self { +impl<'a> TCString<'a> { + /// Take a TCString from C as an argument. C callers generally expect TC functions to take + /// ownership of a string, which is what this function does. + pub(crate) fn from_arg(tcstring: *mut TCString<'a>) -> Self { debug_assert!(!tcstring.is_null()); *(unsafe { Box::from_raw(tcstring) }) } + /// Borrow a TCString from C as an argument. + pub(crate) fn from_arg_ref(tcstring: *mut TCString<'a>) -> &'a mut Self { + debug_assert!(!tcstring.is_null()); + unsafe { &mut *tcstring } + } + /// Get a regular Rust &str for this value. pub(crate) fn as_str(&self) -> Result<&str, std::str::Utf8Error> { - self.0.as_c_str().to_str() + match self { + TCString::CString(cstring) => cstring.as_c_str().to_str(), + TCString::CStr(cstr) => cstr.to_str(), + TCString::String(string) => Ok(string.as_ref()), + } } - /// Construct a *mut TCString from a string for returning to C. - pub(crate) fn return_string(string: impl Into>) -> Result<*mut TCString, NulError> { - let tcstring = TCString(CString::new(string)?); - Ok(Box::into_raw(Box::new(tcstring))) + pub(crate) fn as_bytes(&self) -> &[u8] { + match self { + TCString::CString(cstring) => cstring.as_bytes(), + TCString::CStr(cstr) => cstr.to_bytes(), + TCString::String(string) => string.as_bytes(), + } + } + + pub(crate) fn to_path_buf(&self) -> PathBuf { + // TODO: this is UNIX-specific. + let path: &OsStr = OsStr::from_bytes(self.as_bytes()); + path.to_os_string().into() + } + + /// Convert this to a return value for handing off to C. + pub(crate) fn return_val(self) -> *mut TCString<'a> { + Box::into_raw(Box::new(self)) } } -#[no_mangle] -pub extern "C" fn tc_string_new(cstr: *const libc::c_char) -> *mut TCString { - let cstring = unsafe { CStr::from_ptr(cstr) }.into(); - Box::into_raw(Box::new(TCString(cstring))) +impl<'a> From for TCString<'a> { + fn from(string: String) -> TCString<'a> { + TCString::String(string) + } } -#[no_mangle] -pub extern "C" fn tc_string_content(string: *mut TCString) -> *const libc::c_char { - debug_assert!(!string.is_null()); - let string: &CString = unsafe { &(*string).0 }; - string.as_ptr() +impl<'a> From<&str> for TCString<'a> { + fn from(string: &str) -> TCString<'a> { + TCString::String(string.to_string()) + } } +/// Create a new TCString referencing the given C string. The C string must remain valid until +/// after the TCString is freed. It's typically easiest to ensure this by using a static string. +#[no_mangle] +pub extern "C" fn tc_string_new(cstr: *const libc::c_char) -> *mut TCString<'static> { + let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; + TCString::CStr(cstr).return_val() +} + +/// Create a new TCString by cloning the content of the given C string. +#[no_mangle] +pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'static> { + let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; + TCString::CString(cstr.into()).return_val() +} + +/// Create a new TCString containing the given string with the given length. This allows creation +/// of strings containing embedded NUL characters. If the given string is not valid UTF-8, this +/// function will return NULL. +#[no_mangle] +pub extern "C" fn tc_string_clone_with_len( + buf: *const libc::c_char, + len: usize, +) -> *mut TCString<'static> { + let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) }; + let vec = slice.to_vec(); + if let Ok(string) = String::from_utf8(vec) { + TCString::String(string).return_val() + } else { + std::ptr::null_mut() + } +} + +/// Get the content of the string as a regular C string. The given string must not be NULL. The +/// returned value may be NULL if the string contains NUL bytes. +/// This function does _not_ take ownership of the TCString. +#[no_mangle] +pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { + let tcstring = TCString::from_arg_ref(tcstring); + // if we have a String, we need to consume it and turn it into + // a CString. + if let TCString::String(string) = tcstring { + // TODO: get rid of this clone + match CString::new(string.clone()) { + Ok(cstring) => { + *tcstring = TCString::CString(cstring); + } + Err(_) => { + // TODO: could recover the underlying String + return std::ptr::null(); + } + } + } + + match tcstring { + TCString::CString(cstring) => cstring.as_ptr(), + TCString::String(_) => unreachable!(), // just converted this + TCString::CStr(cstr) => cstr.as_ptr(), + } +} + +/// Free a TCString. #[no_mangle] pub extern "C" fn tc_string_free(string: *mut TCString) { debug_assert!(!string.is_null()); diff --git a/lib/src/task.rs b/lib/src/task.rs index 781b5d3d0..ba129caa4 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -15,28 +15,6 @@ impl TCTask { } } -/// Utility function to allow using `?` notation to return an error value. -fn wrap<'a, T, F>(task: *const TCTask, f: F, err_value: T) -> T -where - F: FnOnce(&Task) -> anyhow::Result, -{ - let task: &'a Task = task_ref(task); - match f(task) { - Ok(v) => v, - Err(e) => { - /* - let error = e.to_string(); - let error = match CString::new(error.as_bytes()) { - Ok(e) => e, - Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(), - }; - */ - //task.error = Some(error); - err_value - } - } -} - /// Utility function to safely convert *const TCTask into &Task fn task_ref(task: *const TCTask) -> &'static Task { debug_assert!(!task.is_null()); @@ -66,12 +44,10 @@ pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). #[no_mangle] -pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCString { - wrap( - task, - |task| Ok(TCString::return_string(task.get_description())?), - std::ptr::null_mut(), - ) +pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCString<'static> { + let task = task_ref(task); + let descr: TCString = task.get_description().into(); + descr.return_val() } /* TODO diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 5d9691d70..445916baf 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -19,6 +19,10 @@ enum TCStatus { struct TCReplica; /// TCString supports passing strings into and out of the TaskChampion API. +/// +/// Unless specified otherwise, functions in this API take ownership of a TCString when it appears +/// as a function argument, and transfer ownership to the caller when the TCString appears as a +/// return value or otput argument. struct TCString; /// A task, as publicly exposed by this library. @@ -46,7 +50,7 @@ extern const uintptr_t TC_UUID_STRING_BYTES; /// Returns NULL on error; see tc_replica_error. /// /// TCReplicas are not threadsafe. -TCReplica *tc_replica_new(const char *path); +TCReplica *tc_replica_new(TCString *path); /// Create a new task. The task must not already exist. /// @@ -67,10 +71,24 @@ const char *tc_replica_error(TCReplica *rep); /// Free a TCReplica. void tc_replica_free(TCReplica *rep); +/// Create a new TCString referencing the given C string. The C string must remain valid until +/// after the TCString is freed. It's typically easiest to ensure this by using a static string. TCString *tc_string_new(const char *cstr); -const char *tc_string_content(TCString *string); +/// Create a new TCString by cloning the content of the given C string. +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. If the given string is not valid UTF-8, this +/// function will return NULL. +TCString *tc_string_clone_with_len(const char *buf, uintptr_t len); + +/// Get the content of the string as a regular C string. The given string must not be NULL. The +/// returned value may be NULL if the string contains NUL bytes. +/// This function does _not_ take ownership of the TCString. +const char *tc_string_content(TCString *tcstring); + +/// Free a TCString. void tc_string_free(TCString *string); /// Get a task's UUID. From 40f30c6d894358084bd4ce2df68ef88d169cced4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 24 Jan 2022 04:12:58 +0000 Subject: [PATCH 437/548] remove unnecessary string clone --- binding-tests/string.cpp | 34 +++++++++++++++++++- lib/build.rs | 1 + lib/src/string.rs | 69 +++++++++++++++++++++++++++++++++------- lib/taskchampion.h | 16 ++++++++-- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/binding-tests/string.cpp b/binding-tests/string.cpp index dd726e862..8b743b266 100644 --- a/binding-tests/string.cpp +++ b/binding-tests/string.cpp @@ -10,20 +10,52 @@ TEST_CASE("creating borrowed strings does not crash") { TEST_CASE("creating cloned strings does not crash") { char *abcdef = strdup("abcdef"); TCString *s = tc_string_clone(abcdef); + REQUIRE(s != NULL); free(abcdef); + CHECK(strcmp(tc_string_content(s), "abcdef") == 0); tc_string_free(s); } -TEST_CASE("strings echo back their content") { +TEST_CASE("borrowed strings echo back their content") { TCString *s = tc_string_new("abcdef"); + REQUIRE(s != NULL); + CHECK(strcmp(tc_string_content(s), "abcdef") == 0); + size_t len; + const char *buf = tc_string_content_with_len(s, &len); + REQUIRE(buf != NULL); + CHECK(len == 6); + CHECK(strncmp(buf, "abcdef", len) == 0); + tc_string_free(s); +} + +TEST_CASE("cloned strings echo back their content") { + char *orig = strdup("abcdef"); + TCString *s = tc_string_clone(orig); + REQUIRE(s != NULL); + free(orig); + + CHECK(strcmp(tc_string_content(s), "abcdef") == 0); + + size_t len; + const char *buf = tc_string_content_with_len(s, &len); + REQUIRE(buf != NULL); + CHECK(len == 6); + CHECK(strncmp(buf, "abcdef", len) == 0); tc_string_free(s); } TEST_CASE("tc_string_content returns NULL for strings containing embedded NULs") { TCString *s = tc_string_clone_with_len("ab\0de", 5); REQUIRE(s != NULL); + CHECK(tc_string_content(s) == NULL); + + size_t len; + const char *buf = tc_string_content_with_len(s, &len); + REQUIRE(buf != NULL); + CHECK(len == 5); + CHECK(strncmp(buf, "ab\0de", len) == 0); tc_string_free(s); } diff --git a/lib/build.rs b/lib/build.rs index 13eda1bc5..d1ebc7bf1 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -10,6 +10,7 @@ fn main() { .with_language(Language::C) .with_config(Config { cpp_compat: true, + usize_is_size_t: true, enumeration: EnumConfig { // this appears to still default to true for C enum_class: false, diff --git a/lib/src/string.rs b/lib/src/string.rs index 3f9086d0e..1816d2a7d 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -6,11 +6,21 @@ use std::path::PathBuf; /// /// Unless specified otherwise, functions in this API take ownership of a TCString when it appears /// as a function argument, and transfer ownership to the caller when the TCString appears as a -/// return value or otput argument. +/// return value or output argument. pub enum TCString<'a> { CString(CString), CStr(&'a CStr), String(String), + + /// None is the default value for TCString, but this variant is never seen by C code or by Rust + /// code outside of this module. + None, +} + +impl<'a> Default for TCString<'a> { + fn default() -> Self { + TCString::None + } } impl<'a> TCString<'a> { @@ -33,6 +43,7 @@ impl<'a> TCString<'a> { TCString::CString(cstring) => cstring.as_c_str().to_str(), TCString::CStr(cstr) => cstr.to_str(), TCString::String(string) => Ok(string.as_ref()), + TCString::None => unreachable!(), } } @@ -41,6 +52,7 @@ impl<'a> TCString<'a> { TCString::CString(cstring) => cstring.as_bytes(), TCString::CStr(cstr) => cstr.to_bytes(), TCString::String(string) => string.as_bytes(), + TCString::None => unreachable!(), } } @@ -101,33 +113,66 @@ pub extern "C" fn tc_string_clone_with_len( } /// Get the content of the string as a regular C string. The given string must not be NULL. The -/// returned value may be NULL if the string contains NUL bytes. +/// returned value is NULL if the string contains NUL bytes. The returned string is valid until +/// the TCString is freed or passed to another TC API function. +/// /// This function does _not_ take ownership of the TCString. #[no_mangle] pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { let tcstring = TCString::from_arg_ref(tcstring); // if we have a String, we need to consume it and turn it into // a CString. - if let TCString::String(string) = tcstring { - // TODO: get rid of this clone - match CString::new(string.clone()) { - Ok(cstring) => { - *tcstring = TCString::CString(cstring); - } - Err(_) => { - // TODO: could recover the underlying String - return std::ptr::null(); + if matches!(tcstring, TCString::String(_)) { + if let TCString::String(string) = std::mem::take(tcstring) { + match CString::new(string) { + Ok(cstring) => { + *tcstring = TCString::CString(cstring); + } + Err(nul_err) => { + // recover the underlying String from the NulError + let original_bytes = nul_err.into_vec(); + // SAFETY: original_bytes just came from a String, so must be valid utf8 + let string = unsafe { String::from_utf8_unchecked(original_bytes) }; + *tcstring = TCString::String(string); + + // and return NULL as advertized + return std::ptr::null(); + } } + } else { + unreachable!() } } match tcstring { TCString::CString(cstring) => cstring.as_ptr(), - TCString::String(_) => unreachable!(), // just converted this + TCString::String(_) => unreachable!(), // just converted to CString TCString::CStr(cstr) => cstr.as_ptr(), + TCString::None => unreachable!(), } } +/// 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. The returned string is +/// valid until the TCString is freed or passed to another TC API function. +/// +/// This function does _not_ take ownership of the TCString. +#[no_mangle] +pub extern "C" fn tc_string_content_with_len( + tcstring: *mut TCString, + len_out: *mut usize, +) -> *const libc::c_char { + let tcstring = TCString::from_arg_ref(tcstring); + let bytes = match tcstring { + TCString::CString(cstring) => cstring.as_bytes(), + TCString::String(string) => string.as_bytes(), + TCString::CStr(cstr) => cstr.to_bytes(), + TCString::None => unreachable!(), + }; + unsafe { *len_out = bytes.len() }; + bytes.as_ptr() as *const libc::c_char +} + /// Free a TCString. #[no_mangle] pub extern "C" fn tc_string_free(string: *mut TCString) { diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 445916baf..ad42de148 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -40,7 +41,7 @@ struct TCUuid { extern "C" { -extern const uintptr_t TC_UUID_STRING_BYTES; +extern const size_t TC_UUID_STRING_BYTES; /// Create a new TCReplica. /// @@ -81,13 +82,22 @@ 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. If the given string is not valid UTF-8, this /// function will return NULL. -TCString *tc_string_clone_with_len(const char *buf, uintptr_t len); +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 not be NULL. The -/// returned value may be NULL if the string contains NUL bytes. +/// returned value is NULL if the string contains NUL bytes. The returned string is valid until +/// the TCString is freed or passed to another TC API function. +/// /// This function does _not_ take ownership of the TCString. const char *tc_string_content(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. The returned string is +/// valid until the TCString is freed or passed to another TC API function. +/// +/// This function does _not_ take ownership of the TCString. +const char *tc_string_content_with_len(TCString *tcstring, size_t *len_out); + /// Free a TCString. void tc_string_free(TCString *string); From 017fb398beea0a3ae66b07e2023be05af7921b13 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 24 Jan 2022 04:14:10 +0000 Subject: [PATCH 438/548] replace a clone with a copy --- lib/src/uuid.rs | 3 +-- lib/taskchampion.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 8ce407afc..5e3492bea 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -10,8 +10,7 @@ pub struct TCUuid([u8; 16]); impl From for TCUuid { fn from(uuid: Uuid) -> TCUuid { - // TODO: can we avoid clone here? - TCUuid(uuid.as_bytes().clone()) + TCUuid(*uuid.as_bytes()) } } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index ad42de148..560f23eb1 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -23,7 +23,7 @@ struct TCReplica; /// /// Unless specified otherwise, functions in this API take ownership of a TCString when it appears /// as a function argument, and transfer ownership to the caller when the TCString appears as a -/// return value or otput argument. +/// return value or output argument. struct TCString; /// A task, as publicly exposed by this library. From 56a805151d9c87c00b623b82b99df5f225da518d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 24 Jan 2022 15:44:51 +0000 Subject: [PATCH 439/548] use 2018 edition like the other crates --- lib/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index b01d1bcbd..b8545df90 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "taskchampion-lib" version = "0.1.0" -edition = "2021" +edition = "2018" build = "build.rs" [lib] From c006cbe8e5f5934fd81824df89eadc6ec409f82f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 25 Jan 2022 01:27:24 +0000 Subject: [PATCH 440/548] test bindings in an integration-tests crate --- Cargo.lock | 9 + Cargo.toml | 3 +- binding-tests/.gitignore | 3 - binding-tests/Makefile | 21 - binding-tests/doctest.cpp | 2 - binding-tests/doctest.h | 6816 ----------------- binding-tests/replica.cpp | 24 - binding-tests/string.cpp | 61 - binding-tests/task.cpp | 25 - binding-tests/uuid.cpp | 38 - integration-tests/Cargo.toml | 14 + integration-tests/README.md | 32 + integration-tests/build.rs | 36 + integration-tests/src/bindings_tests/mod.rs | 20 + .../src/bindings_tests/replica.c | 39 + integration-tests/src/bindings_tests/string.c | 81 + integration-tests/src/bindings_tests/task.c | 34 + integration-tests/src/bindings_tests/test.c | 6 + .../src/bindings_tests/unity/LICENSE.txt | 21 + .../src/bindings_tests/unity/README.md | 3 + .../src/bindings_tests/unity/unity.c | 2119 +++++ .../src/bindings_tests/unity/unity.h | 661 ++ .../bindings_tests/unity/unity_internals.h | 1053 +++ integration-tests/src/bindings_tests/uuid.c | 44 + integration-tests/src/lib.rs | 1 + integration-tests/tests/bindings.rs | 15 + lib/build.rs | 4 +- lib/src/replica.rs | 81 +- lib/taskchampion.h | 246 +- 29 files changed, 4389 insertions(+), 7123 deletions(-) delete mode 100644 binding-tests/.gitignore delete mode 100644 binding-tests/Makefile delete mode 100644 binding-tests/doctest.cpp delete mode 100644 binding-tests/doctest.h delete mode 100644 binding-tests/replica.cpp delete mode 100644 binding-tests/string.cpp delete mode 100644 binding-tests/task.cpp delete mode 100644 binding-tests/uuid.cpp create mode 100644 integration-tests/Cargo.toml create mode 100644 integration-tests/README.md create mode 100644 integration-tests/build.rs create mode 100644 integration-tests/src/bindings_tests/mod.rs create mode 100644 integration-tests/src/bindings_tests/replica.c create mode 100644 integration-tests/src/bindings_tests/string.c create mode 100644 integration-tests/src/bindings_tests/task.c create mode 100644 integration-tests/src/bindings_tests/test.c create mode 100644 integration-tests/src/bindings_tests/unity/LICENSE.txt create mode 100644 integration-tests/src/bindings_tests/unity/README.md create mode 100644 integration-tests/src/bindings_tests/unity/unity.c create mode 100644 integration-tests/src/bindings_tests/unity/unity.h create mode 100644 integration-tests/src/bindings_tests/unity/unity_internals.h create mode 100644 integration-tests/src/bindings_tests/uuid.c create mode 100644 integration-tests/src/lib.rs create mode 100644 integration-tests/tests/bindings.rs diff --git a/Cargo.lock b/Cargo.lock index 4f577c437..7924f76e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1520,6 +1520,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "integration-tests" +version = "0.4.1" +dependencies = [ + "cc", + "taskchampion", + "taskchampion-lib", +] + [[package]] name = "iovec" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index a552a0dda..09c0a9677 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,6 @@ members = [ "cli", "sync-server", "replica-server-tests", - "lib" + "lib", + "integration-tests", ] diff --git a/binding-tests/.gitignore b/binding-tests/.gitignore deleted file mode 100644 index aa82f31b4..000000000 --- a/binding-tests/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.o -doctest -test-db diff --git a/binding-tests/Makefile b/binding-tests/Makefile deleted file mode 100644 index 26f621596..000000000 --- a/binding-tests/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -CXX=g++ -INC=-I ../lib -LIB=-L ../target/debug -RPATH=-Wl,-rpath,../target/debug - -TESTS = replica.cpp string.cpp uuid.cpp task.cpp - -.PHONY: all test - -all: test - -test: doctest - @rm -rf test-db - @./doctest --no-version --no-intro - @rm -rf test-db - -%.o: %.cpp ../lib/taskchampion.h - $(CXX) $(INC) -c $< -o $@ - -doctest: doctest.o $(subst .cpp,.o,$(TESTS)) - $(CXX) $(LIB) $(RPATH) $< $(subst .cpp,.o,$(TESTS)) -ltaskchampion -o $@ diff --git a/binding-tests/doctest.cpp b/binding-tests/doctest.cpp deleted file mode 100644 index a3f832e49..000000000 --- a/binding-tests/doctest.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include "doctest.h" diff --git a/binding-tests/doctest.h b/binding-tests/doctest.h deleted file mode 100644 index d25f52682..000000000 --- a/binding-tests/doctest.h +++ /dev/null @@ -1,6816 +0,0 @@ -// ====================================================================== lgtm [cpp/missing-header-guard] -// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == -// ====================================================================== -// -// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD -// -// Copyright (c) 2016-2021 Viktor Kirilov -// -// Distributed under the MIT Software License -// See accompanying file LICENSE.txt or copy at -// https://opensource.org/licenses/MIT -// -// The documentation can be found at the library's page: -// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md -// -// ================================================================================================= -// ================================================================================================= -// ================================================================================================= -// -// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 -// which uses the Boost Software License - Version 1.0 -// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt -// -// The concept of subcases (sections in Catch) and expression decomposition are from there. -// Some parts of the code are taken directly: -// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> -// - the Approx() helper class for floating point comparison -// - colors in the console -// - breaking into a debugger -// - signal / SEH handling -// - timer -// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) -// -// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest -// which uses the Boost Software License - Version 1.0 -// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt -// -// ================================================================================================= -// ================================================================================================= -// ================================================================================================= - -#ifndef DOCTEST_LIBRARY_INCLUDED -#define DOCTEST_LIBRARY_INCLUDED - -// ================================================================================================= -// == VERSION ====================================================================================== -// ================================================================================================= - -#define DOCTEST_VERSION_MAJOR 2 -#define DOCTEST_VERSION_MINOR 4 -#define DOCTEST_VERSION_PATCH 8 - -// util we need here -#define DOCTEST_TOSTR_IMPL(x) #x -#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x) - -#define DOCTEST_VERSION_STR \ - DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \ - DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \ - DOCTEST_TOSTR(DOCTEST_VERSION_PATCH) - -#define DOCTEST_VERSION \ - (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) - -// ================================================================================================= -// == COMPILER VERSION ============================================================================= -// ================================================================================================= - -// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect - -#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) - -// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... -#if defined(_MSC_VER) && defined(_MSC_FULL_VER) -#if _MSC_VER == _MSC_FULL_VER / 10000 -#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) -#else // MSVC -#define DOCTEST_MSVC \ - DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) -#endif // MSVC -#endif // MSVC -#if defined(__clang__) && defined(__clang_minor__) -#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) -#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ - !defined(__INTEL_COMPILER) -#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#endif // GCC - -#ifndef DOCTEST_MSVC -#define DOCTEST_MSVC 0 -#endif // DOCTEST_MSVC -#ifndef DOCTEST_CLANG -#define DOCTEST_CLANG 0 -#endif // DOCTEST_CLANG -#ifndef DOCTEST_GCC -#define DOCTEST_GCC 0 -#endif // DOCTEST_GCC - -// ================================================================================================= -// == COMPILER WARNINGS HELPERS ==================================================================== -// ================================================================================================= - -#if DOCTEST_CLANG -#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) -#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") -#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) -#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") -#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ - DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) -#else // DOCTEST_CLANG -#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -#define DOCTEST_CLANG_SUPPRESS_WARNING(w) -#define DOCTEST_CLANG_SUPPRESS_WARNING_POP -#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) -#endif // DOCTEST_CLANG - -#if DOCTEST_GCC -#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) -#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") -#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) -#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") -#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ - DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) -#else // DOCTEST_GCC -#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH -#define DOCTEST_GCC_SUPPRESS_WARNING(w) -#define DOCTEST_GCC_SUPPRESS_WARNING_POP -#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) -#endif // DOCTEST_GCC - -#if DOCTEST_MSVC -#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) -#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) -#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) -#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ - DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) -#else // DOCTEST_MSVC -#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -#define DOCTEST_MSVC_SUPPRESS_WARNING(w) -#define DOCTEST_MSVC_SUPPRESS_WARNING_POP -#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) -#endif // DOCTEST_MSVC - -// ================================================================================================= -// == COMPILER WARNINGS ============================================================================ -// ================================================================================================= - -// both the header and the implementation suppress all of these, -// so it only makes sense to aggregrate them like so -#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \ - DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \ - \ - DOCTEST_GCC_SUPPRESS_WARNING_PUSH \ - DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \ - DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \ - DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \ - DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \ - DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \ - DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \ - DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") \ - DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \ - DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \ - \ - DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ - /* these 4 also disabled globally via cmake: */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \ - /* */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ - /* static analysis */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */ - -#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \ - DOCTEST_CLANG_SUPPRESS_WARNING_POP \ - DOCTEST_GCC_SUPPRESS_WARNING_POP \ - DOCTEST_MSVC_SUPPRESS_WARNING_POP - -DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH - -DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") - -DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") - -DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted - -#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ - DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ - DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ - DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ - -#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP - -// ================================================================================================= -// == FEATURE DETECTION ============================================================================ -// ================================================================================================= - -// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support -// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx -// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html -// MSVC version table: -// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering -// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022) -// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) -// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) -// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) -// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) -// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) -// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) -// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) -// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) - -// Universal Windows Platform support -#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -#define DOCTEST_CONFIG_NO_WINDOWS_SEH -#endif // WINAPI_FAMILY -#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) -#define DOCTEST_CONFIG_WINDOWS_SEH -#endif // MSVC -#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) -#undef DOCTEST_CONFIG_WINDOWS_SEH -#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH - -#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ - !defined(__EMSCRIPTEN__) -#define DOCTEST_CONFIG_POSIX_SIGNALS -#endif // _WIN32 -#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) -#undef DOCTEST_CONFIG_POSIX_SIGNALS -#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS - -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS -#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) -#define DOCTEST_CONFIG_NO_EXCEPTIONS -#endif // no exceptions -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS -#define DOCTEST_CONFIG_NO_EXCEPTIONS -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) -#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS - -#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) -#define DOCTEST_CONFIG_IMPLEMENT -#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN - -#if defined(_WIN32) || defined(__CYGWIN__) -#if DOCTEST_MSVC -#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) -#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) -#else // MSVC -#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) -#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) -#endif // MSVC -#else // _WIN32 -#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) -#define DOCTEST_SYMBOL_IMPORT -#endif // _WIN32 - -#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL -#ifdef DOCTEST_CONFIG_IMPLEMENT -#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT -#else // DOCTEST_CONFIG_IMPLEMENT -#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT -#endif // DOCTEST_CONFIG_IMPLEMENT -#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL -#define DOCTEST_INTERFACE -#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL - -#define DOCTEST_EMPTY - -#if DOCTEST_MSVC -#define DOCTEST_NOINLINE __declspec(noinline) -#define DOCTEST_UNUSED -#define DOCTEST_ALIGNMENT(x) -#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0) -#define DOCTEST_NOINLINE -#define DOCTEST_UNUSED -#define DOCTEST_ALIGNMENT(x) -#else -#define DOCTEST_NOINLINE __attribute__((noinline)) -#define DOCTEST_UNUSED __attribute__((unused)) -#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) -#endif - -#ifndef DOCTEST_NORETURN -#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) -#define DOCTEST_NORETURN -#else // DOCTEST_MSVC -#define DOCTEST_NORETURN [[noreturn]] -#endif // DOCTEST_MSVC -#endif // DOCTEST_NORETURN - -#ifndef DOCTEST_NOEXCEPT -#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) -#define DOCTEST_NOEXCEPT -#else // DOCTEST_MSVC -#define DOCTEST_NOEXCEPT noexcept -#endif // DOCTEST_MSVC -#endif // DOCTEST_NOEXCEPT - -#ifndef DOCTEST_CONSTEXPR -#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) -#define DOCTEST_CONSTEXPR const -#else // DOCTEST_MSVC -#define DOCTEST_CONSTEXPR constexpr -#endif // DOCTEST_MSVC -#endif // DOCTEST_CONSTEXPR - -// ================================================================================================= -// == FEATURE DETECTION END ======================================================================== -// ================================================================================================= - -// internal macros for string concatenation and anonymous variable name generation -#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 -#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) -#ifdef __COUNTER__ // not standard and may be missing for some compilers -#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) -#else // __COUNTER__ -#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) -#endif // __COUNTER__ - -#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE -#define DOCTEST_REF_WRAP(x) x& -#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE -#define DOCTEST_REF_WRAP(x) x -#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE - -// not using __APPLE__ because... this is how Catch does it -#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED -#define DOCTEST_PLATFORM_MAC -#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -#define DOCTEST_PLATFORM_IPHONE -#elif defined(_WIN32) -#define DOCTEST_PLATFORM_WINDOWS -#else // DOCTEST_PLATFORM -#define DOCTEST_PLATFORM_LINUX -#endif // DOCTEST_PLATFORM - -namespace doctest { namespace detail { - static DOCTEST_CONSTEXPR int consume(const int*, int) { return 0; } -}} - -#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ - static const int var = doctest::detail::consume(&var, __VA_ARGS__); \ - DOCTEST_CLANG_SUPPRESS_WARNING_POP - -#ifndef DOCTEST_BREAK_INTO_DEBUGGER -// should probably take a look at https://github.com/scottt/debugbreak -#ifdef DOCTEST_PLATFORM_LINUX -#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) -// Break at the location of the failing check if possible -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) -#else -#include -#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) -#endif -#elif defined(DOCTEST_PLATFORM_MAC) -#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) -#else -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler) -#endif -#elif DOCTEST_MSVC -#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() -#elif defined(__MINGW32__) -DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") -extern "C" __declspec(dllimport) void __stdcall DebugBreak(); -DOCTEST_GCC_SUPPRESS_WARNING_POP -#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() -#else // linux -#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast(0)) -#endif // linux -#endif // DOCTEST_BREAK_INTO_DEBUGGER - -// this is kept here for backwards compatibility since the config option was changed -#ifdef DOCTEST_CONFIG_USE_IOSFWD -#define DOCTEST_CONFIG_USE_STD_HEADERS -#endif // DOCTEST_CONFIG_USE_IOSFWD - -// for clang - always include ciso646 (which drags some std stuff) because -// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in -// which case we don't want to forward declare stuff from std - for reference: -// https://github.com/doctest/doctest/issues/126 -// https://github.com/doctest/doctest/issues/356 -#if DOCTEST_CLANG -#include -#ifdef _LIBCPP_VERSION -#define DOCTEST_CONFIG_USE_STD_HEADERS -#endif // _LIBCPP_VERSION -#endif // clang - -#ifdef DOCTEST_CONFIG_USE_STD_HEADERS -#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS -#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS -#include -#include -#include -#else // DOCTEST_CONFIG_USE_STD_HEADERS - -// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. -DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) - -namespace std { // NOLINT (cert-dcl58-cpp) -typedef decltype(nullptr) nullptr_t; -template -struct char_traits; -template <> -struct char_traits; -template -class basic_ostream; -typedef basic_ostream> ostream; -template -class basic_istream; -typedef basic_istream> istream; -template -class tuple; -#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 -template -class allocator; -template -class basic_string; -using string = basic_string, allocator>; -#endif // VS 2019 -} // namespace std - -DOCTEST_MSVC_SUPPRESS_WARNING_POP - -#endif // DOCTEST_CONFIG_USE_STD_HEADERS - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS -#include -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - -namespace doctest { - -DOCTEST_INTERFACE extern bool is_running_in_test; - -// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length -// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: -// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) -// - if small - capacity left before going on the heap - using the lowest 5 bits -// - if small - 2 bits are left unused - the second and third highest ones -// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) -// and the "is small" bit remains "0" ("as well as the capacity left") so its OK -// Idea taken from this lecture about the string implementation of facebook/folly - fbstring -// https://www.youtube.com/watch?v=kPR8h4-qZdk -// TODO: -// - optimizations - like not deleting memory unnecessarily in operator= and etc. -// - resize/reserve/clear -// - substr -// - replace -// - back/front -// - iterator stuff -// - find & friends -// - push_back/pop_back -// - assign/insert/erase -// - relational operators as free functions - taking const char* as one of the params -class DOCTEST_INTERFACE String -{ - static const unsigned len = 24; //!OCLINT avoid private static members - static const unsigned last = len - 1; //!OCLINT avoid private static members - - struct view // len should be more than sizeof(view) - because of the final byte for flags - { - char* ptr; - unsigned size; - unsigned capacity; - }; - - union - { - char buf[len]; - view data; - }; - - char* allocate(unsigned sz); - - bool isOnStack() const { return (buf[last] & 128) == 0; } - void setOnHeap(); - void setLast(unsigned in = last); - - void copy(const String& other); - -public: - String(); - ~String(); - - // cppcheck-suppress noExplicitConstructor - String(const char* in); - String(const char* in, unsigned in_size); - - String(std::istream& in, unsigned in_size); - - String(const String& other); - String& operator=(const String& other); - - String& operator+=(const String& other); - - String(String&& other); - String& operator=(String&& other); - - char operator[](unsigned i) const; - char& operator[](unsigned i); - - // the only functions I'm willing to leave in the interface - available for inlining - const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT - char* c_str() { - if(isOnStack()) - return reinterpret_cast(buf); - return data.ptr; - } - - unsigned size() const; - unsigned capacity() const; - - int compare(const char* other, bool no_case = false) const; - int compare(const String& other, bool no_case = false) const; -}; - -DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs); - -DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); - -DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); - -namespace Color { - enum Enum - { - None = 0, - White, - Red, - Green, - Blue, - Cyan, - Yellow, - Grey, - - Bright = 0x10, - - BrightRed = Bright | Red, - BrightGreen = Bright | Green, - LightGrey = Bright | Grey, - BrightWhite = Bright | White - }; - - DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); -} // namespace Color - -namespace assertType { - enum Enum - { - // macro traits - - is_warn = 1, - is_check = 2 * is_warn, - is_require = 2 * is_check, - - is_normal = 2 * is_require, - is_throws = 2 * is_normal, - is_throws_as = 2 * is_throws, - is_throws_with = 2 * is_throws_as, - is_nothrow = 2 * is_throws_with, - - is_false = 2 * is_nothrow, - is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types - - is_eq = 2 * is_unary, - is_ne = 2 * is_eq, - - is_lt = 2 * is_ne, - is_gt = 2 * is_lt, - - is_ge = 2 * is_gt, - is_le = 2 * is_ge, - - // macro types - - DT_WARN = is_normal | is_warn, - DT_CHECK = is_normal | is_check, - DT_REQUIRE = is_normal | is_require, - - DT_WARN_FALSE = is_normal | is_false | is_warn, - DT_CHECK_FALSE = is_normal | is_false | is_check, - DT_REQUIRE_FALSE = is_normal | is_false | is_require, - - DT_WARN_THROWS = is_throws | is_warn, - DT_CHECK_THROWS = is_throws | is_check, - DT_REQUIRE_THROWS = is_throws | is_require, - - DT_WARN_THROWS_AS = is_throws_as | is_warn, - DT_CHECK_THROWS_AS = is_throws_as | is_check, - DT_REQUIRE_THROWS_AS = is_throws_as | is_require, - - DT_WARN_THROWS_WITH = is_throws_with | is_warn, - DT_CHECK_THROWS_WITH = is_throws_with | is_check, - DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, - - DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, - DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, - DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, - - DT_WARN_NOTHROW = is_nothrow | is_warn, - DT_CHECK_NOTHROW = is_nothrow | is_check, - DT_REQUIRE_NOTHROW = is_nothrow | is_require, - - DT_WARN_EQ = is_normal | is_eq | is_warn, - DT_CHECK_EQ = is_normal | is_eq | is_check, - DT_REQUIRE_EQ = is_normal | is_eq | is_require, - - DT_WARN_NE = is_normal | is_ne | is_warn, - DT_CHECK_NE = is_normal | is_ne | is_check, - DT_REQUIRE_NE = is_normal | is_ne | is_require, - - DT_WARN_GT = is_normal | is_gt | is_warn, - DT_CHECK_GT = is_normal | is_gt | is_check, - DT_REQUIRE_GT = is_normal | is_gt | is_require, - - DT_WARN_LT = is_normal | is_lt | is_warn, - DT_CHECK_LT = is_normal | is_lt | is_check, - DT_REQUIRE_LT = is_normal | is_lt | is_require, - - DT_WARN_GE = is_normal | is_ge | is_warn, - DT_CHECK_GE = is_normal | is_ge | is_check, - DT_REQUIRE_GE = is_normal | is_ge | is_require, - - DT_WARN_LE = is_normal | is_le | is_warn, - DT_CHECK_LE = is_normal | is_le | is_check, - DT_REQUIRE_LE = is_normal | is_le | is_require, - - DT_WARN_UNARY = is_normal | is_unary | is_warn, - DT_CHECK_UNARY = is_normal | is_unary | is_check, - DT_REQUIRE_UNARY = is_normal | is_unary | is_require, - - DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, - DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, - DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, - }; -} // namespace assertType - -DOCTEST_INTERFACE const char* assertString(assertType::Enum at); -DOCTEST_INTERFACE const char* failureString(assertType::Enum at); -DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); - -struct DOCTEST_INTERFACE TestCaseData -{ - String m_file; // the file in which the test was registered (using String - see #350) - unsigned m_line; // the line where the test was registered - const char* m_name; // name of the test case - const char* m_test_suite; // the test suite in which the test was added - const char* m_description; - bool m_skip; - bool m_no_breaks; - bool m_no_output; - bool m_may_fail; - bool m_should_fail; - int m_expected_failures; - double m_timeout; -}; - -struct DOCTEST_INTERFACE AssertData -{ - // common - for all asserts - const TestCaseData* m_test_case; - assertType::Enum m_at; - const char* m_file; - int m_line; - const char* m_expr; - bool m_failed; - - // exception-related - for all asserts - bool m_threw; - String m_exception; - - // for normal asserts - String m_decomp; - - // for specific exception-related asserts - bool m_threw_as; - const char* m_exception_type; - const char* m_exception_string; -}; - -struct DOCTEST_INTERFACE MessageData -{ - String m_string; - const char* m_file; - int m_line; - assertType::Enum m_severity; -}; - -struct DOCTEST_INTERFACE SubcaseSignature -{ - String m_name; - const char* m_file; - int m_line; - - bool operator<(const SubcaseSignature& other) const; -}; - -struct DOCTEST_INTERFACE IContextScope -{ - IContextScope(); - virtual ~IContextScope(); - virtual void stringify(std::ostream*) const = 0; -}; - -namespace detail { - struct DOCTEST_INTERFACE TestCase; -} // namespace detail - -struct ContextOptions //!OCLINT too many fields -{ - std::ostream* cout = nullptr; // stdout stream - String binary_name; // the test binary name - - const detail::TestCase* currentTest = nullptr; - - // == parameters from the command line - String out; // output filename - String order_by; // how tests should be ordered - unsigned rand_seed; // the seed for rand ordering - - unsigned first; // the first (matching) test to be executed - unsigned last; // the last (matching) test to be executed - - int abort_after; // stop tests after this many failed assertions - int subcase_filter_levels; // apply the subcase filters for the first N levels - - bool success; // include successful assertions in output - bool case_sensitive; // if filtering should be case sensitive - bool exit; // if the program should be exited after the tests are ran/whatever - bool duration; // print the time duration of each test case - bool minimal; // minimal console output (only test failures) - bool quiet; // no console output - bool no_throw; // to skip exceptions-related assertion macros - bool no_exitcode; // if the framework should return 0 as the exitcode - bool no_run; // to not run the tests at all (can be done with an "*" exclude) - bool no_intro; // to not print the intro of the framework - bool no_version; // to not print the version of the framework - bool no_colors; // if output to the console should be colorized - bool force_colors; // forces the use of colors even when a tty cannot be detected - bool no_breaks; // to not break into the debugger - bool no_skip; // don't skip test cases which are marked to be skipped - bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): - bool no_path_in_filenames; // if the path to files should be removed from the output - bool no_line_numbers; // if source code line numbers should be omitted from the output - bool no_debug_output; // no output in the debug console when a debugger is attached - bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! - bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!! - - bool help; // to print the help - bool version; // to print the version - bool count; // if only the count of matching tests is to be retrieved - bool list_test_cases; // to list all tests matching the filters - bool list_test_suites; // to list all suites matching the filters - bool list_reporters; // lists all registered reporters -}; - -namespace detail { - template - struct enable_if - {}; - - template - struct enable_if - { typedef TYPE type; }; - - // clang-format off - template struct remove_reference { typedef T type; }; - template struct remove_reference { typedef T type; }; - template struct remove_reference { typedef T type; }; - - template U declval(int); - - template T declval(long); - - template auto declval() DOCTEST_NOEXCEPT -> decltype(declval(0)) ; - - template struct is_lvalue_reference { const static bool value=false; }; - template struct is_lvalue_reference { const static bool value=true; }; - - template struct is_rvalue_reference { const static bool value=false; }; - template struct is_rvalue_reference { const static bool value=true; }; - - template - inline T&& forward(typename remove_reference::type& t) DOCTEST_NOEXCEPT - { - return static_cast(t); - } - - template - inline T&& forward(typename remove_reference::type&& t) DOCTEST_NOEXCEPT - { - static_assert(!is_lvalue_reference::value, - "Can not forward an rvalue as an lvalue."); - return static_cast(t); - } - - template struct remove_const { typedef T type; }; - template struct remove_const { typedef T type; }; -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - template struct is_enum : public std::is_enum {}; - template struct underlying_type : public std::underlying_type {}; -#else - // Use compiler intrinsics - template struct is_enum { DOCTEST_CONSTEXPR static bool value = __is_enum(T); }; - template struct underlying_type { typedef __underlying_type(T) type; }; -#endif - // clang-format on - - template - struct deferred_false - // cppcheck-suppress unusedStructMember - { static const bool value = false; }; - - namespace has_insertion_operator_impl { - std::ostream &os(); - template - DOCTEST_REF_WRAP(T) val(); - - template - struct check { - static DOCTEST_CONSTEXPR bool value = false; - }; - - template - struct check(), void())> { - static DOCTEST_CONSTEXPR bool value = true; - }; - } // namespace has_insertion_operator_impl - - template - using has_insertion_operator = has_insertion_operator_impl::check; - - DOCTEST_INTERFACE std::ostream* tlssPush(); - DOCTEST_INTERFACE String tlssPop(); - - - template - struct StringMakerBase - { - template - static String convert(const DOCTEST_REF_WRAP(T)) { - return "{?}"; - } - }; - - // Vector and various type other than pointer or array. - template - struct filldata - { - static void fill(std::ostream* stream, const T &in) { - *stream << in; - } - }; - - template - struct filldata - { - static void fill(std::ostream* stream, const T (&in)[N]) { - for (unsigned long i = 0; i < N; i++) { - *stream << in[i]; - } - } - }; - - // Specialized since we don't want the terminating null byte! - template - struct filldata - { - static void fill(std::ostream* stream, const char(&in)[N]) { - *stream << in; - } - }; - - template - void filloss(std::ostream* stream, const T& in) { - filldata::fill(stream, in); - } - - template - void filloss(std::ostream* stream, const T (&in)[N]) { - // T[N], T(&)[N], T(&&)[N] have same behaviour. - // Hence remove reference. - filldata::type>::fill(stream, in); - } - - template <> - struct StringMakerBase - { - template - static String convert(const DOCTEST_REF_WRAP(T) in) { - /* When parameter "in" is a null terminated const char* it works. - * When parameter "in" is a T arr[N] without '\0' we can fill the - * stringstream with N objects (T=char).If in is char pointer * - * without '\0' , it would cause segfault - * stepping over unaccessible memory. - */ - - std::ostream* stream = tlssPush(); - filloss(stream, in); - return tlssPop(); - } - }; - - DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); - - template - String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { - return rawMemoryToString(&object, sizeof(object)); - } - - template - const char* type_to_string() { - return "<>"; - } -} // namespace detail - -template -struct StringMaker : public detail::StringMakerBase::value> -{}; - -template -struct StringMaker -{ - template - static String convert(U* p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; - -template -struct StringMaker -{ - static String convert(R C::*p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; - -template ::value, bool>::type = true> -String toString(const DOCTEST_REF_WRAP(T) value) { - return StringMaker::convert(value); -} - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -DOCTEST_INTERFACE String toString(char* in); -DOCTEST_INTERFACE String toString(const char* in); -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -DOCTEST_INTERFACE String toString(bool in); -DOCTEST_INTERFACE String toString(float in); -DOCTEST_INTERFACE String toString(double in); -DOCTEST_INTERFACE String toString(double long in); - -DOCTEST_INTERFACE String toString(char in); -DOCTEST_INTERFACE String toString(char signed in); -DOCTEST_INTERFACE String toString(char unsigned in); -DOCTEST_INTERFACE String toString(int short in); -DOCTEST_INTERFACE String toString(int short unsigned in); -DOCTEST_INTERFACE String toString(int in); -DOCTEST_INTERFACE String toString(int unsigned in); -DOCTEST_INTERFACE String toString(int long in); -DOCTEST_INTERFACE String toString(int long unsigned in); -DOCTEST_INTERFACE String toString(int long long in); -DOCTEST_INTERFACE String toString(int long long unsigned in); -DOCTEST_INTERFACE String toString(std::nullptr_t in); - -template ::value, bool>::type = true> -String toString(const DOCTEST_REF_WRAP(T) value) { - typedef typename detail::underlying_type::type UT; - return toString(static_cast(value)); -} - -#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 -DOCTEST_INTERFACE String toString(const std::string& in); -#endif // VS 2019 - -class DOCTEST_INTERFACE Approx -{ -public: - explicit Approx(double value); - - Approx operator()(double value) const; - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - template - explicit Approx(const T& value, - typename detail::enable_if::value>::type* = - static_cast(nullptr)) { - *this = Approx(static_cast(value)); - } -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - - Approx& epsilon(double newEpsilon); - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - template - typename detail::enable_if::value, Approx&>::type epsilon( - const T& newEpsilon) { - m_epsilon = static_cast(newEpsilon); - return *this; - } -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - - Approx& scale(double newScale); - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - template - typename detail::enable_if::value, Approx&>::type scale( - const T& newScale) { - m_scale = static_cast(newScale); - return *this; - } -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - - // clang-format off - DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); - - DOCTEST_INTERFACE friend String toString(const Approx& in); - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS -#define DOCTEST_APPROX_PREFIX \ - template friend typename detail::enable_if::value, bool>::type - - DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } - DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } - DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } - DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } - DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } -#undef DOCTEST_APPROX_PREFIX -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - - // clang-format on - -private: - double m_epsilon; - double m_scale; - double m_value; -}; - -DOCTEST_INTERFACE String toString(const Approx& in); - -DOCTEST_INTERFACE const ContextOptions* getContextOptions(); - -#if !defined(DOCTEST_CONFIG_DISABLE) - -namespace detail { - // clang-format off -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - template struct decay_array { typedef T type; }; - template struct decay_array { typedef T* type; }; - template struct decay_array { typedef T* type; }; - - template struct not_char_pointer { enum { value = 1 }; }; - template<> struct not_char_pointer { enum { value = 0 }; }; - template<> struct not_char_pointer { enum { value = 0 }; }; - - template struct can_use_op : public not_char_pointer::type> {}; -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - // clang-format on - - struct DOCTEST_INTERFACE TestFailureException - { - }; - - DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); - -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - DOCTEST_NORETURN -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - DOCTEST_INTERFACE void throwException(); - - struct DOCTEST_INTERFACE Subcase - { - SubcaseSignature m_signature; - bool m_entered = false; - - Subcase(const String& name, const char* file, int line); - ~Subcase(); - - operator bool() const; - }; - - template - String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, - const DOCTEST_REF_WRAP(R) rhs) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - return toString(lhs) + op + toString(rhs); - } - -#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) -DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") -#endif - -// This will check if there is any way it could find a operator like member or friend and uses it. -// If not it doesn't find the operator or if the operator at global scope is defined after -// this template, the template won't be instantiated due to SFINAE. Once the template is not -// instantiated it can look for global operator using normal conversions. -#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval() op doctest::detail::declval()),ret{}) - -#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ - template \ - DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(const R&& rhs) { \ - bool res = op_macro(doctest::detail::forward(lhs), doctest::detail::forward(rhs)); \ - if(m_at & assertType::is_false) \ - res = !res; \ - if(!res || doctest::getContextOptions()->success) \ - return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ - return Result(res); \ - } \ - template ::value, void >::type* = nullptr> \ - DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(const R& rhs) { \ - bool res = op_macro(doctest::detail::forward(lhs), rhs); \ - if(m_at & assertType::is_false) \ - res = !res; \ - if(!res || doctest::getContextOptions()->success) \ - return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ - return Result(res); \ - } - - // more checks could be added - like in Catch: - // https://github.com/catchorg/Catch2/pull/1480/files - // https://github.com/catchorg/Catch2/pull/1481/files -#define DOCTEST_FORBIT_EXPRESSION(rt, op) \ - template \ - rt& operator op(const R&) { \ - static_assert(deferred_false::value, \ - "Expression Too Complex Please Rewrite As Binary Comparison!"); \ - return *this; \ - } - - struct DOCTEST_INTERFACE Result - { - bool m_passed; - String m_decomp; - - Result() = default; - Result(bool passed, const String& decomposition = String()); - - // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence - DOCTEST_FORBIT_EXPRESSION(Result, &) - DOCTEST_FORBIT_EXPRESSION(Result, ^) - DOCTEST_FORBIT_EXPRESSION(Result, |) - DOCTEST_FORBIT_EXPRESSION(Result, &&) - DOCTEST_FORBIT_EXPRESSION(Result, ||) - DOCTEST_FORBIT_EXPRESSION(Result, ==) - DOCTEST_FORBIT_EXPRESSION(Result, !=) - DOCTEST_FORBIT_EXPRESSION(Result, <) - DOCTEST_FORBIT_EXPRESSION(Result, >) - DOCTEST_FORBIT_EXPRESSION(Result, <=) - DOCTEST_FORBIT_EXPRESSION(Result, >=) - DOCTEST_FORBIT_EXPRESSION(Result, =) - DOCTEST_FORBIT_EXPRESSION(Result, +=) - DOCTEST_FORBIT_EXPRESSION(Result, -=) - DOCTEST_FORBIT_EXPRESSION(Result, *=) - DOCTEST_FORBIT_EXPRESSION(Result, /=) - DOCTEST_FORBIT_EXPRESSION(Result, %=) - DOCTEST_FORBIT_EXPRESSION(Result, <<=) - DOCTEST_FORBIT_EXPRESSION(Result, >>=) - DOCTEST_FORBIT_EXPRESSION(Result, &=) - DOCTEST_FORBIT_EXPRESSION(Result, ^=) - DOCTEST_FORBIT_EXPRESSION(Result, |=) - }; - -#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - - DOCTEST_CLANG_SUPPRESS_WARNING_PUSH - DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") - DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") - //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") - //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") - //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") - - DOCTEST_GCC_SUPPRESS_WARNING_PUSH - DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") - DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") - //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") - //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") - //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") - - DOCTEST_MSVC_SUPPRESS_WARNING_PUSH - // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 - DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch - DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch - DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch - //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation - -#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - - // clang-format off -#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_COMPARISON_RETURN_TYPE bool -#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if::value || can_use_op::value, bool>::type - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } - inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } - inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } - inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } - inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } - inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - // clang-format on - -#define DOCTEST_RELATIONAL_OP(name, op) \ - template \ - DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ - const DOCTEST_REF_WRAP(R) rhs) { \ - return lhs op rhs; \ - } - - DOCTEST_RELATIONAL_OP(eq, ==) - DOCTEST_RELATIONAL_OP(ne, !=) - DOCTEST_RELATIONAL_OP(lt, <) - DOCTEST_RELATIONAL_OP(gt, >) - DOCTEST_RELATIONAL_OP(le, <=) - DOCTEST_RELATIONAL_OP(ge, >=) - -#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_CMP_EQ(l, r) l == r -#define DOCTEST_CMP_NE(l, r) l != r -#define DOCTEST_CMP_GT(l, r) l > r -#define DOCTEST_CMP_LT(l, r) l < r -#define DOCTEST_CMP_GE(l, r) l >= r -#define DOCTEST_CMP_LE(l, r) l <= r -#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_CMP_EQ(l, r) eq(l, r) -#define DOCTEST_CMP_NE(l, r) ne(l, r) -#define DOCTEST_CMP_GT(l, r) gt(l, r) -#define DOCTEST_CMP_LT(l, r) lt(l, r) -#define DOCTEST_CMP_GE(l, r) ge(l, r) -#define DOCTEST_CMP_LE(l, r) le(l, r) -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - - template - // cppcheck-suppress copyCtorAndEqOperator - struct Expression_lhs - { - L lhs; - assertType::Enum m_at; - - explicit Expression_lhs(L&& in, assertType::Enum at) - : lhs(doctest::detail::forward(in)) - , m_at(at) {} - - DOCTEST_NOINLINE operator Result() { -// this is needed only for MSVC 2015 -DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool - bool res = static_cast(lhs); -DOCTEST_MSVC_SUPPRESS_WARNING_POP - if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional - res = !res; - - if(!res || getContextOptions()->success) - return Result(res, toString(lhs)); - return Result(res); - } - - /* This is required for user-defined conversions from Expression_lhs to L */ - operator L() const { return lhs; } - - // clang-format off - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional - // clang-format on - - // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) - // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the - // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) - }; - -#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_MSVC_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP - -#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - -#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) -DOCTEST_CLANG_SUPPRESS_WARNING_POP -#endif - - struct DOCTEST_INTERFACE ExpressionDecomposer - { - assertType::Enum m_at; - - ExpressionDecomposer(assertType::Enum at); - - // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) - // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... - // https://github.com/catchorg/Catch2/issues/870 - // https://github.com/catchorg/Catch2/issues/565 - template - Expression_lhs operator<<(const L &&operand) { - return Expression_lhs(doctest::detail::forward(operand), m_at); - } - - template ::value,void >::type* = nullptr> - Expression_lhs operator<<(const L &operand) { - return Expression_lhs(operand, m_at); - } - }; - - struct DOCTEST_INTERFACE TestSuite - { - const char* m_test_suite = nullptr; - const char* m_description = nullptr; - bool m_skip = false; - bool m_no_breaks = false; - bool m_no_output = false; - bool m_may_fail = false; - bool m_should_fail = false; - int m_expected_failures = 0; - double m_timeout = 0; - - TestSuite& operator*(const char* in); - - template - TestSuite& operator*(const T& in) { - in.fill(*this); - return *this; - } - }; - - typedef void (*funcType)(); - - struct DOCTEST_INTERFACE TestCase : public TestCaseData - { - funcType m_test; // a function pointer to the test case - - const char* m_type; // for templated test cases - gets appended to the real name - int m_template_id; // an ID used to distinguish between the different versions of a templated test case - String m_full_name; // contains the name (only for templated test cases!) + the template type - - TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type = "", int template_id = -1); - - TestCase(const TestCase& other); - - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function - TestCase& operator=(const TestCase& other); - DOCTEST_MSVC_SUPPRESS_WARNING_POP - - TestCase& operator*(const char* in); - - template - TestCase& operator*(const T& in) { - in.fill(*this); - return *this; - } - - bool operator<(const TestCase& other) const; - }; - - // forward declarations of functions used by the macros - DOCTEST_INTERFACE int regTest(const TestCase& tc); - DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); - DOCTEST_INTERFACE bool isDebuggerActive(); - - template - int instantiationHelper(const T&) { return 0; } - - namespace binaryAssertComparison { - enum Enum - { - eq = 0, - ne, - gt, - lt, - ge, - le - }; - } // namespace binaryAssertComparison - - // clang-format off - template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; - -#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ - template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; - // clang-format on - - DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq) - DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne) - DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt) - DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt) - DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge) - DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le) - - struct DOCTEST_INTERFACE ResultBuilder : public AssertData - { - ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, - const char* exception_type = "", const char* exception_string = ""); - - void setResult(const Result& res); - - template - DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs, - const DOCTEST_REF_WRAP(R) rhs) { - m_failed = !RelationalComparator()(lhs, rhs); - if(m_failed || getContextOptions()->success) - m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); - return !m_failed; - } - - template - DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) { - m_failed = !val; - - if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional - m_failed = !m_failed; - - if(m_failed || getContextOptions()->success) - m_decomp = toString(val); - - return !m_failed; - } - - void translateException(); - - bool log(); - void react() const; - }; - - namespace assertAction { - enum Enum - { - nothing = 0, - dbgbreak = 1, - shouldthrow = 2 - }; - } // namespace assertAction - - DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); - - DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line, - const char* expr, Result result); - -#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ - do { \ - if(!is_running_in_test) { \ - if(failed) { \ - ResultBuilder rb(at, file, line, expr); \ - rb.m_failed = failed; \ - rb.m_decomp = decomp; \ - failed_out_of_a_testing_context(rb); \ - if(isDebuggerActive() && !getContextOptions()->no_breaks) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - if(checkIfShouldThrow(at)) \ - throwException(); \ - } \ - return !failed; \ - } \ - } while(false) - -#define DOCTEST_ASSERT_IN_TESTS(decomp) \ - ResultBuilder rb(at, file, line, expr); \ - rb.m_failed = failed; \ - if(rb.m_failed || getContextOptions()->success) \ - rb.m_decomp = decomp; \ - if(rb.log()) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - if(rb.m_failed && checkIfShouldThrow(at)) \ - throwException() - - template - DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line, - const char* expr, const DOCTEST_REF_WRAP(L) lhs, - const DOCTEST_REF_WRAP(R) rhs) { - bool failed = !RelationalComparator()(lhs, rhs); - - // ################################################################################### - // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT - // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED - // ################################################################################### - DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); - DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); - return !failed; - } - - template - DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line, - const char* expr, const DOCTEST_REF_WRAP(L) val) { - bool failed = !val; - - if(at & assertType::is_false) //!OCLINT bitwise operator in conditional - failed = !failed; - - // ################################################################################### - // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT - // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED - // ################################################################################### - DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); - DOCTEST_ASSERT_IN_TESTS(toString(val)); - return !failed; - } - - struct DOCTEST_INTERFACE IExceptionTranslator - { - IExceptionTranslator(); - virtual ~IExceptionTranslator(); - virtual bool translate(String&) const = 0; - }; - - template - class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class - { - public: - explicit ExceptionTranslator(String (*translateFunction)(T)) - : m_translateFunction(translateFunction) {} - - bool translate(String& res) const override { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - try { - throw; // lgtm [cpp/rethrow-no-exception] - // cppcheck-suppress catchExceptionByValue - } catch(T ex) { // NOLINT - res = m_translateFunction(ex); //!OCLINT parameter reassignment - return true; - } catch(...) {} //!OCLINT - empty catch statement -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - static_cast(res); // to silence -Wunused-parameter - return false; - } - - private: - String (*m_translateFunction)(T); - }; - - DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); - - template - struct StringStreamBase - { - template - static void convert(std::ostream* s, const T& in) { - *s << toString(in); - } - - // always treat char* as a string in this context - no matter - // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined - static void convert(std::ostream* s, const char* in) { *s << String(in); } - }; - - template <> - struct StringStreamBase - { - template - static void convert(std::ostream* s, const T& in) { - *s << in; - } - }; - - template - struct StringStream : public StringStreamBase::value> - {}; - - template - void toStream(std::ostream* s, const T& value) { - StringStream::convert(s, value); - } - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); - DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); - DOCTEST_INTERFACE void toStream(std::ostream* s, float in); - DOCTEST_INTERFACE void toStream(std::ostream* s, double in); - DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); - - DOCTEST_INTERFACE void toStream(std::ostream* s, char in); - DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); - DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); - - // ContextScope base class used to allow implementing methods of ContextScope - // that don't depend on the template parameter in doctest.cpp. - class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { - protected: - ContextScopeBase(); - ContextScopeBase(ContextScopeBase&& other); - - void destroy(); - bool need_to_destroy{true}; - }; - - template class ContextScope : public ContextScopeBase - { - const L lambda_; - - public: - explicit ContextScope(const L &lambda) : lambda_(lambda) {} - - ContextScope(ContextScope &&other) : ContextScopeBase(static_cast(other)), lambda_(other.lambda_) {} - - void stringify(std::ostream* s) const override { lambda_(s); } - - ~ContextScope() override { - if (need_to_destroy) { - destroy(); - } - } - }; - - struct DOCTEST_INTERFACE MessageBuilder : public MessageData - { - std::ostream* m_stream; - bool logged = false; - - MessageBuilder(const char* file, int line, assertType::Enum severity); - MessageBuilder() = delete; - ~MessageBuilder(); - - // the preferred way of chaining parameters for stringification - template - MessageBuilder& operator,(const T& in) { - toStream(m_stream, in); - return *this; - } - - // kept here just for backwards-compatibility - the comma operator should be preferred now - template - MessageBuilder& operator<<(const T& in) { return this->operator,(in); } - - // the `,` operator has the lowest operator precedence - if `<<` is used by the user then - // the `,` operator will be called last which is not what we want and thus the `*` operator - // is used first (has higher operator precedence compared to `<<`) so that we guarantee that - // an operator of the MessageBuilder class is called first before the rest of the parameters - template - MessageBuilder& operator*(const T& in) { return this->operator,(in); } - - bool log(); - void react(); - }; - - template - ContextScope MakeContextScope(const L &lambda) { - return ContextScope(lambda); - } -} // namespace detail - -#define DOCTEST_DEFINE_DECORATOR(name, type, def) \ - struct name \ - { \ - type data; \ - name(type in = def) \ - : data(in) {} \ - void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ - void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ - } - -DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); -DOCTEST_DEFINE_DECORATOR(description, const char*, ""); -DOCTEST_DEFINE_DECORATOR(skip, bool, true); -DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true); -DOCTEST_DEFINE_DECORATOR(no_output, bool, true); -DOCTEST_DEFINE_DECORATOR(timeout, double, 0); -DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); -DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); -DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); - -template -int registerExceptionTranslator(String (*translateFunction)(T)) { - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") - static detail::ExceptionTranslator exceptionTranslator(translateFunction); - DOCTEST_CLANG_SUPPRESS_WARNING_POP - detail::registerExceptionTranslatorImpl(&exceptionTranslator); - return 0; -} - -} // namespace doctest - -// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro -// introduces an anonymous namespace in which getCurrentTestSuite gets overridden -namespace doctest_detail_test_suite_ns { -DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); -} // namespace doctest_detail_test_suite_ns - -namespace doctest { -#else // DOCTEST_CONFIG_DISABLE -template -int registerExceptionTranslator(String (*)(T)) { - return 0; -} -#endif // DOCTEST_CONFIG_DISABLE - -namespace detail { - typedef void (*assert_handler)(const AssertData&); - struct ContextState; -} // namespace detail - -class DOCTEST_INTERFACE Context -{ - detail::ContextState* p; - - void parseArgs(int argc, const char* const* argv, bool withDefaults = false); - -public: - explicit Context(int argc = 0, const char* const* argv = nullptr); - - ~Context(); - - void applyCommandLine(int argc, const char* const* argv); - - void addFilter(const char* filter, const char* value); - void clearFilters(); - void setOption(const char* option, bool value); - void setOption(const char* option, int value); - void setOption(const char* option, const char* value); - - bool shouldExit(); - - void setAsDefaultForAssertsOutOfTestCases(); - - void setAssertHandler(detail::assert_handler ah); - - void setCout(std::ostream* out); - - int run(); -}; - -namespace TestCaseFailureReason { - enum Enum - { - None = 0, - AssertFailure = 1, // an assertion has failed in the test case - Exception = 2, // test case threw an exception - Crash = 4, // a crash... - TooManyFailedAsserts = 8, // the abort-after option - Timeout = 16, // see the timeout decorator - ShouldHaveFailedButDidnt = 32, // see the should_fail decorator - ShouldHaveFailedAndDid = 64, // see the should_fail decorator - DidntFailExactlyNumTimes = 128, // see the expected_failures decorator - FailedExactlyNumTimes = 256, // see the expected_failures decorator - CouldHaveFailedAndDid = 512 // see the may_fail decorator - }; -} // namespace TestCaseFailureReason - -struct DOCTEST_INTERFACE CurrentTestCaseStats -{ - int numAssertsCurrentTest; - int numAssertsFailedCurrentTest; - double seconds; - int failure_flags; // use TestCaseFailureReason::Enum - bool testCaseSuccess; -}; - -struct DOCTEST_INTERFACE TestCaseException -{ - String error_string; - bool is_crash; -}; - -struct DOCTEST_INTERFACE TestRunStats -{ - unsigned numTestCases; - unsigned numTestCasesPassingFilters; - unsigned numTestSuitesPassingFilters; - unsigned numTestCasesFailed; - int numAsserts; - int numAssertsFailed; -}; - -struct QueryData -{ - const TestRunStats* run_stats = nullptr; - const TestCaseData** data = nullptr; - unsigned num_data = 0; -}; - -struct DOCTEST_INTERFACE IReporter -{ - // The constructor has to accept "const ContextOptions&" as a single argument - // which has most of the options for the run + a pointer to the stdout stream - // Reporter(const ContextOptions& in) - - // called when a query should be reported (listing test cases, printing the version, etc.) - virtual void report_query(const QueryData&) = 0; - - // called when the whole test run starts - virtual void test_run_start() = 0; - // called when the whole test run ends (caching a pointer to the input doesn't make sense here) - virtual void test_run_end(const TestRunStats&) = 0; - - // called when a test case is started (safe to cache a pointer to the input) - virtual void test_case_start(const TestCaseData&) = 0; - // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) - virtual void test_case_reenter(const TestCaseData&) = 0; - // called when a test case has ended - virtual void test_case_end(const CurrentTestCaseStats&) = 0; - - // called when an exception is thrown from the test case (or it crashes) - virtual void test_case_exception(const TestCaseException&) = 0; - - // called whenever a subcase is entered (don't cache pointers to the input) - virtual void subcase_start(const SubcaseSignature&) = 0; - // called whenever a subcase is exited (don't cache pointers to the input) - virtual void subcase_end() = 0; - - // called for each assert (don't cache pointers to the input) - virtual void log_assert(const AssertData&) = 0; - // called for each message (don't cache pointers to the input) - virtual void log_message(const MessageData&) = 0; - - // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator - // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) - virtual void test_case_skipped(const TestCaseData&) = 0; - - // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have - virtual ~IReporter(); - - // can obtain all currently active contexts and stringify them if one wishes to do so - static int get_num_active_contexts(); - static const IContextScope* const* get_active_contexts(); - - // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown - static int get_num_stringified_contexts(); - static const String* get_stringified_contexts(); -}; - -namespace detail { - typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); - - DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); - - template - IReporter* reporterCreator(const ContextOptions& o) { - return new Reporter(o); - } -} // namespace detail - -template -int registerReporter(const char* name, int priority, bool isReporter) { - detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); - return 0; -} -} // namespace doctest - -// if registering is not disabled -#if !defined(DOCTEST_CONFIG_DISABLE) - -// common code in asserts - for convenience -#define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \ - if(b.log()) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - b.react(); \ - return !b.m_failed - -#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS -#define DOCTEST_WRAP_IN_TRY(x) x; -#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS -#define DOCTEST_WRAP_IN_TRY(x) \ - try { \ - x; \ - } catch(...) { DOCTEST_RB.translateException(); } -#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS - -#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS -#define DOCTEST_CAST_TO_VOID(...) \ - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ - static_cast(__VA_ARGS__); \ - DOCTEST_GCC_SUPPRESS_WARNING_POP -#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS -#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; -#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS - -// registers the test by initializing a dummy var with a function -#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ - global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ - doctest::detail::regTest( \ - doctest::detail::TestCase( \ - f, __FILE__, __LINE__, \ - doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ - decorators)) - -#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ - namespace { \ - struct der : public base \ - { \ - void f(); \ - }; \ - static void func() { \ - der v; \ - v.f(); \ - } \ - DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ - } \ - inline DOCTEST_NOINLINE void der::f() - -#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ - static void f(); \ - DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ - static void f() - -#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ - static doctest::detail::funcType proxy() { return f; } \ - DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \ - static void f() - -// for registering tests -#define DOCTEST_TEST_CASE(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) - -// for registering tests in classes - requires C++17 for inline variables! -#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) -#define DOCTEST_TEST_CASE_CLASS(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \ - DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \ - decorators) -#else // DOCTEST_TEST_CASE_CLASS -#define DOCTEST_TEST_CASE_CLASS(...) \ - TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER -#endif // DOCTEST_TEST_CASE_CLASS - -// for registering tests with a fixture -#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \ - DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) - -// for converting types to strings without the header and demangling -#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ - template <> \ - inline const char* type_to_string<__VA_ARGS__>() { \ - return "<" #__VA_ARGS__ ">"; \ - } -#define DOCTEST_TYPE_TO_STRING(...) \ - namespace doctest { namespace detail { \ - DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ - } \ - } \ - static_assert(true, "") - -#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ - template \ - static void func(); \ - namespace { \ - template \ - struct iter; \ - template \ - struct iter> \ - { \ - iter(const char* file, unsigned line, int index) { \ - doctest::detail::regTest(doctest::detail::TestCase(func, file, line, \ - doctest_detail_test_suite_ns::getCurrentTestSuite(), \ - doctest::detail::type_to_string(), \ - int(line) * 1000 + index) \ - * dec); \ - iter>(file, line, index + 1); \ - } \ - }; \ - template <> \ - struct iter> \ - { \ - iter(const char*, unsigned, int) {} \ - }; \ - } \ - template \ - static void func() - -#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ - DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ - DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)) - -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), \ - doctest::detail::instantiationHelper( \ - DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0))) - -#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ - static_assert(true, "") - -#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \ - static_assert(true, "") - -#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \ - template \ - static void anon() - -#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) - -// for subcases -#define DOCTEST_SUBCASE(name) \ - if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ - doctest::detail::Subcase(name, __FILE__, __LINE__)) - -// for grouping tests in test suites by using code blocks -#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ - namespace ns_name { namespace doctest_detail_test_suite_ns { \ - static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ - static doctest::detail::TestSuite data{}; \ - static bool inited = false; \ - DOCTEST_MSVC_SUPPRESS_WARNING_POP \ - DOCTEST_CLANG_SUPPRESS_WARNING_POP \ - DOCTEST_GCC_SUPPRESS_WARNING_POP \ - if(!inited) { \ - data* decorators; \ - inited = true; \ - } \ - return data; \ - } \ - } \ - } \ - namespace ns_name - -#define DOCTEST_TEST_SUITE(decorators) \ - DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_)) - -// for starting a testsuite block -#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \ - static_assert(true, "") - -// for ending a testsuite block -#define DOCTEST_TEST_SUITE_END \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \ - typedef int DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) - -// for registering exception translators -#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ - inline doctest::String translatorName(signature); \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ - doctest::registerExceptionTranslator(translatorName)) \ - doctest::String translatorName(signature) - -#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ - DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ - signature) - -// for registering reporters -#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), \ - doctest::registerReporter(name, priority, true)) \ - static_assert(true, "") - -// for registering listeners -#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), \ - doctest::registerReporter(name, priority, false)) \ - static_assert(true, "") - -// clang-format off -// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557 -#define DOCTEST_INFO(...) \ - DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \ - DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \ - __VA_ARGS__) -// clang-format on - -#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ - auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ - [&](std::ostream* s_name) { \ - doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ - mb_name.m_stream = s_name; \ - mb_name * __VA_ARGS__; \ - }) - -#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) - -#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ - [&] { \ - doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ - mb * __VA_ARGS__; \ - if(mb.log()) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - mb.react(); \ - }() - -// clang-format off -#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) -#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) -// clang-format on - -#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) -#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__) -#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__) - -#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. - -#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ - doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \ - doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ - << __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \ - DOCTEST_CLANG_SUPPRESS_WARNING_POP - -#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ - [&] { \ - DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ - }() - -#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -// necessary for _MESSAGE -#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 - -#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ - doctest::detail::decomp_assert( \ - doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ - doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ - << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP - -#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) -#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) -#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) -#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) -#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) -#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) - -// clang-format off -#define DOCTEST_WARN_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); }() -#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); }() -#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); }() -#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); }() -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); }() -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); }() -// clang-format on - -#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ - [&] { \ - if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #expr, #__VA_ARGS__, message); \ - try { \ - DOCTEST_CAST_TO_VOID(expr) \ - } catch(const typename doctest::detail::remove_const< \ - typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ - DOCTEST_RB.translateException(); \ - DOCTEST_RB.m_threw_as = true; \ - } catch(...) { DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ - } else { \ - return false; \ - } \ - }() - -#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ - [&] { \ - if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, expr_str, "", __VA_ARGS__); \ - try { \ - DOCTEST_CAST_TO_VOID(expr) \ - } catch(...) { DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ - } else { \ - return false; \ - } \ - }() - -#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ - [&] { \ - doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - try { \ - DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ - } catch(...) { DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ - }() - -// clang-format off -#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") -#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") -#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") - -#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) - -#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) - -#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) - -#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) -#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) -#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); }() -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); }() -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); }() -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); }() -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); }() -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); }() -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); }() -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); }() -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); }() -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); }() -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); }() -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); }() -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); }() -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); }() -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); }() -// clang-format on - -#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ - [&] { \ - doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY( \ - DOCTEST_RB.binary_assert( \ - __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ - }() - -#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ - [&] { \ - doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ - }() - -#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ - doctest::detail::binary_assert( \ - doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) - -#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ - doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ - #__VA_ARGS__, __VA_ARGS__) - -#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) -#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) -#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) -#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) -#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) -#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) -#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) -#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) -#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) -#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) -#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) -#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) -#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) -#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) -#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) -#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) -#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) -#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) - -#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) -#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) -#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) -#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) -#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS - -#undef DOCTEST_WARN_THROWS -#undef DOCTEST_CHECK_THROWS -#undef DOCTEST_REQUIRE_THROWS -#undef DOCTEST_WARN_THROWS_AS -#undef DOCTEST_CHECK_THROWS_AS -#undef DOCTEST_REQUIRE_THROWS_AS -#undef DOCTEST_WARN_THROWS_WITH -#undef DOCTEST_CHECK_THROWS_WITH -#undef DOCTEST_REQUIRE_THROWS_WITH -#undef DOCTEST_WARN_THROWS_WITH_AS -#undef DOCTEST_CHECK_THROWS_WITH_AS -#undef DOCTEST_REQUIRE_THROWS_WITH_AS -#undef DOCTEST_WARN_NOTHROW -#undef DOCTEST_CHECK_NOTHROW -#undef DOCTEST_REQUIRE_NOTHROW - -#undef DOCTEST_WARN_THROWS_MESSAGE -#undef DOCTEST_CHECK_THROWS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_MESSAGE -#undef DOCTEST_WARN_THROWS_AS_MESSAGE -#undef DOCTEST_CHECK_THROWS_AS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE -#undef DOCTEST_WARN_THROWS_WITH_MESSAGE -#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE -#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_WARN_NOTHROW_MESSAGE -#undef DOCTEST_CHECK_NOTHROW_MESSAGE -#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#define DOCTEST_WARN_THROWS(...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS(...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS(...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_AS(expr, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) -#define DOCTEST_WARN_NOTHROW(...) ([] { return false; }) -#define DOCTEST_CHECK_NOTHROW(...) ([] { return false; }) -#define DOCTEST_REQUIRE_NOTHROW(...) ([] { return false; }) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) - -#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#undef DOCTEST_REQUIRE -#undef DOCTEST_REQUIRE_FALSE -#undef DOCTEST_REQUIRE_MESSAGE -#undef DOCTEST_REQUIRE_FALSE_MESSAGE -#undef DOCTEST_REQUIRE_EQ -#undef DOCTEST_REQUIRE_NE -#undef DOCTEST_REQUIRE_GT -#undef DOCTEST_REQUIRE_LT -#undef DOCTEST_REQUIRE_GE -#undef DOCTEST_REQUIRE_LE -#undef DOCTEST_REQUIRE_UNARY -#undef DOCTEST_REQUIRE_UNARY_FALSE - -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - -// ================================================================================================= -// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == -// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == -// ================================================================================================= -#else // DOCTEST_CONFIG_DISABLE - -#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ - namespace { \ - template \ - struct der : public base \ - { void f(); }; \ - } \ - template \ - inline void der::f() - -#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ - template \ - static inline void f() - -// for registering tests -#define DOCTEST_TEST_CASE(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) - -// for registering tests in classes -#define DOCTEST_TEST_CASE_CLASS(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) - -// for registering tests with a fixture -#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \ - DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) - -// for converting types to strings without the header and demangling -#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "") -#define DOCTEST_TYPE_TO_STRING_IMPL(...) - -// for typed tests -#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ - template \ - inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() - -#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ - template \ - inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() - -#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "") -#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "") - -// for subcases -#define DOCTEST_SUBCASE(name) - -// for a testsuite block -#define DOCTEST_TEST_SUITE(name) namespace - -// for starting a testsuite block -#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "") - -// for ending a testsuite block -#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ - template \ - static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature) - -#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) -#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) - -#define DOCTEST_INFO(...) (static_cast(0)) -#define DOCTEST_CAPTURE(x) (static_cast(0)) -#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast(0)) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast(0)) -#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast(0)) -#define DOCTEST_MESSAGE(...) (static_cast(0)) -#define DOCTEST_FAIL_CHECK(...) (static_cast(0)) -#define DOCTEST_FAIL(...) (static_cast(0)) - -#ifdef DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED - -#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }() -#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }() -#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }() -#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }() -#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }() -#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }() - -#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }() -#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }() -#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }() -#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() - -namespace doctest { -namespace detail { -#define DOCTEST_RELATIONAL_OP(name, op) \ - template \ - bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; } - - DOCTEST_RELATIONAL_OP(eq, ==) - DOCTEST_RELATIONAL_OP(ne, !=) - DOCTEST_RELATIONAL_OP(lt, <) - DOCTEST_RELATIONAL_OP(gt, >) - DOCTEST_RELATIONAL_OP(le, <=) - DOCTEST_RELATIONAL_OP(ge, >=) -} // namespace detail -} // namespace doctest - -#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() -#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() -#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() -#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() -#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() -#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() -#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() -#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() -#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() -#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() -#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() -#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() -#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() -#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() -#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() -#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() -#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() -#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() -#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }() -#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }() -#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }() -#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() -#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() -#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() - -#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED - -#define DOCTEST_WARN(...) ([] { return false; }) -#define DOCTEST_CHECK(...) ([] { return false; }) -#define DOCTEST_REQUIRE(...) ([] { return false; }) -#define DOCTEST_WARN_FALSE(...) ([] { return false; }) -#define DOCTEST_CHECK_FALSE(...) ([] { return false; }) -#define DOCTEST_REQUIRE_FALSE(...) ([] { return false; }) - -#define DOCTEST_WARN_MESSAGE(cond, ...) ([] { return false; }) -#define DOCTEST_CHECK_MESSAGE(cond, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_MESSAGE(cond, ...) ([] { return false; }) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) ([] { return false; }) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) ([] { return false; }) - -#define DOCTEST_WARN_EQ(...) ([] { return false; }) -#define DOCTEST_CHECK_EQ(...) ([] { return false; }) -#define DOCTEST_REQUIRE_EQ(...) ([] { return false; }) -#define DOCTEST_WARN_NE(...) ([] { return false; }) -#define DOCTEST_CHECK_NE(...) ([] { return false; }) -#define DOCTEST_REQUIRE_NE(...) ([] { return false; }) -#define DOCTEST_WARN_GT(...) ([] { return false; }) -#define DOCTEST_CHECK_GT(...) ([] { return false; }) -#define DOCTEST_REQUIRE_GT(...) ([] { return false; }) -#define DOCTEST_WARN_LT(...) ([] { return false; }) -#define DOCTEST_CHECK_LT(...) ([] { return false; }) -#define DOCTEST_REQUIRE_LT(...) ([] { return false; }) -#define DOCTEST_WARN_GE(...) ([] { return false; }) -#define DOCTEST_CHECK_GE(...) ([] { return false; }) -#define DOCTEST_REQUIRE_GE(...) ([] { return false; }) -#define DOCTEST_WARN_LE(...) ([] { return false; }) -#define DOCTEST_CHECK_LE(...) ([] { return false; }) -#define DOCTEST_REQUIRE_LE(...) ([] { return false; }) - -#define DOCTEST_WARN_UNARY(...) ([] { return false; }) -#define DOCTEST_CHECK_UNARY(...) ([] { return false; }) -#define DOCTEST_REQUIRE_UNARY(...) ([] { return false; }) -#define DOCTEST_WARN_UNARY_FALSE(...) ([] { return false; }) -#define DOCTEST_CHECK_UNARY_FALSE(...) ([] { return false; }) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) ([] { return false; }) - -#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED - -// TODO: think about if these also need to work properly even when doctest is disabled -#define DOCTEST_WARN_THROWS(...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS(...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS(...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_AS(expr, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) -#define DOCTEST_WARN_NOTHROW(...) ([] { return false; }) -#define DOCTEST_CHECK_NOTHROW(...) ([] { return false; }) -#define DOCTEST_REQUIRE_NOTHROW(...) ([] { return false; }) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) - -#endif // DOCTEST_CONFIG_DISABLE - -// clang-format off -// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS -#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ -#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ -#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ -#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE -#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE -#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE -#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT -#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT -#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT -#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT -#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT -#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT -#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE -#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE -#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE -#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE -#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE -#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE - -#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY -#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY -#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY -#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE -#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE -#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE - -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__) -// clang-format on - -// BDD style macros -// clang-format off -#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) -#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) -#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) -#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) - -#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name) -#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name) -#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name) -#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name) -#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name) -// clang-format on - -// == SHORT VERSIONS OF THE MACROS -#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) - -#define TEST_CASE(name) DOCTEST_TEST_CASE(name) -#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) -#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) -#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) -#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) -#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) -#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__) -#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__) -#define SUBCASE(name) DOCTEST_SUBCASE(name) -#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators) -#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name) -#define TEST_SUITE_END DOCTEST_TEST_SUITE_END -#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) -#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter) -#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter) -#define INFO(...) DOCTEST_INFO(__VA_ARGS__) -#define CAPTURE(x) DOCTEST_CAPTURE(x) -#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__) -#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__) -#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__) -#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__) -#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__) -#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__) -#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__) - -#define WARN(...) DOCTEST_WARN(__VA_ARGS__) -#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__) -#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__) -#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__) -#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__) -#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__) -#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__) -#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__) -#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__) -#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__) -#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__) -#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__) -#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__) -#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__) -#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__) -#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__) -#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__) -#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__) -#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__) -#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__) -#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__) - -#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__) -#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__) -#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__) -#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) -#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) -#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) -#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__) -#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__) -#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__) -#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__) -#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) -#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) -#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) -#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__) -#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__) -#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__) -#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__) -#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) -#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) -#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) -#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__) - -#define SCENARIO(name) DOCTEST_SCENARIO(name) -#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name) -#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__) -#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) -#define GIVEN(name) DOCTEST_GIVEN(name) -#define WHEN(name) DOCTEST_WHEN(name) -#define AND_WHEN(name) DOCTEST_AND_WHEN(name) -#define THEN(name) DOCTEST_THEN(name) -#define AND_THEN(name) DOCTEST_AND_THEN(name) - -#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__) -#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__) -#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__) -#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__) -#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__) -#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__) -#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__) -#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__) -#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__) -#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__) -#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__) -#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__) -#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__) -#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__) -#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__) -#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__) -#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__) -#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__) -#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__) -#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__) -#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__) -#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__) -#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__) -#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__) - -// KEPT FOR BACKWARDS COMPATIBILITY -#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__) -#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__) -#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__) -#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__) -#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__) -#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__) -#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__) -#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__) -#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__) -#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__) -#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__) -#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__) -#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__) -#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__) -#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__) -#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__) -#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__) -#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__) - -#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__) -#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__) -#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__) -#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__) -#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__) -#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__) - -#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__) - -#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES - -#if !defined(DOCTEST_CONFIG_DISABLE) - -// this is here to clear the 'current test suite' for the current translation unit - at the top -DOCTEST_TEST_SUITE_END(); - -// add stringification for primitive/fundamental types -namespace doctest { namespace detail { - DOCTEST_TYPE_TO_STRING_IMPL(bool) - DOCTEST_TYPE_TO_STRING_IMPL(float) - DOCTEST_TYPE_TO_STRING_IMPL(double) - DOCTEST_TYPE_TO_STRING_IMPL(long double) - DOCTEST_TYPE_TO_STRING_IMPL(char) - DOCTEST_TYPE_TO_STRING_IMPL(signed char) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) -#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) - DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) -#endif // not MSVC or wchar_t support enabled - DOCTEST_TYPE_TO_STRING_IMPL(short int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) - DOCTEST_TYPE_TO_STRING_IMPL(int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) - DOCTEST_TYPE_TO_STRING_IMPL(long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) - DOCTEST_TYPE_TO_STRING_IMPL(long long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) -}} // namespace doctest::detail - -#endif // DOCTEST_CONFIG_DISABLE - -DOCTEST_CLANG_SUPPRESS_WARNING_POP -DOCTEST_MSVC_SUPPRESS_WARNING_POP -DOCTEST_GCC_SUPPRESS_WARNING_POP - -DOCTEST_SUPPRESS_COMMON_WARNINGS_POP - -#endif // DOCTEST_LIBRARY_INCLUDED - -#ifndef DOCTEST_SINGLE_HEADER -#define DOCTEST_SINGLE_HEADER -#endif // DOCTEST_SINGLE_HEADER - -#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) - -#ifndef DOCTEST_SINGLE_HEADER -#include "doctest_fwd.h" -#endif // DOCTEST_SINGLE_HEADER - -DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") - -#ifndef DOCTEST_LIBRARY_IMPLEMENTATION -#define DOCTEST_LIBRARY_IMPLEMENTATION - -DOCTEST_CLANG_SUPPRESS_WARNING_POP - -DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH - -DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") - -DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") -DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") -DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") -DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") -DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") -DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") - -DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data -DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled -DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified -DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal -DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch -DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C -DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) -DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed - -DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN - -// required includes - will go only in one translation unit! -#include -#include -#include -// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37 -#ifdef __BORLANDC__ -#include -#endif // __BORLANDC__ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef DOCTEST_PLATFORM_MAC -#include -#include -#include -#endif // DOCTEST_PLATFORM_MAC - -#ifdef DOCTEST_PLATFORM_WINDOWS - -// defines for a leaner windows.h -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif // WIN32_LEAN_AND_MEAN -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - -// not sure what AfxWin.h is for - here I do what Catch does -#ifdef __AFXDLL -#include -#else -#include -#endif -#include - -#else // DOCTEST_PLATFORM_WINDOWS - -#include -#include - -#endif // DOCTEST_PLATFORM_WINDOWS - -// this is a fix for https://github.com/doctest/doctest/issues/348 -// https://mail.gnome.org/archives/xml/2012-January/msg00000.html -#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) -#define STDOUT_FILENO fileno(stdout) -#endif // HAVE_UNISTD_H - -DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END - -// counts the number of elements in a C array -#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) - -#ifdef DOCTEST_CONFIG_DISABLE -#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled -#else // DOCTEST_CONFIG_DISABLE -#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled -#endif // DOCTEST_CONFIG_DISABLE - -#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX -#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" -#endif - -#ifndef DOCTEST_THREAD_LOCAL -#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) -#define DOCTEST_THREAD_LOCAL -#else // DOCTEST_MSVC -#define DOCTEST_THREAD_LOCAL thread_local -#endif // DOCTEST_MSVC -#endif // DOCTEST_THREAD_LOCAL - -#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES -#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 -#endif - -#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE -#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64 -#endif - -#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS -#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX -#else -#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" -#endif - -#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS -#endif - -#ifndef DOCTEST_CDECL -#define DOCTEST_CDECL __cdecl -#endif - -namespace doctest { - -bool is_running_in_test = false; - -namespace { - using namespace detail; - - template - DOCTEST_NORETURN void throw_exception(Ex const& e) { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - throw e; -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - std::cerr << "doctest will terminate because it needed to throw an exception.\n" - << "The message was: " << e.what() << '\n'; - std::terminate(); -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - } - -#ifndef DOCTEST_INTERNAL_ERROR -#define DOCTEST_INTERNAL_ERROR(msg) \ - throw_exception(std::logic_error( \ - __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) -#endif // DOCTEST_INTERNAL_ERROR - - // case insensitive strcmp - int stricmp(const char* a, const char* b) { - for(;; a++, b++) { - const int d = tolower(*a) - tolower(*b); - if(d != 0 || !*a) - return d; - } - } - - template - String fpToString(T value, int precision) { - std::ostringstream oss; - oss << std::setprecision(precision) << std::fixed << value; - std::string d = oss.str(); - size_t i = d.find_last_not_of('0'); - if(i != std::string::npos && i != d.size() - 1) { - if(d[i] == '.') - i++; - d = d.substr(0, i + 1); - } - return d.c_str(); - } - - struct Endianness - { - enum Arch - { - Big, - Little - }; - - static Arch which() { - int x = 1; - // casting any data pointer to char* is allowed - auto ptr = reinterpret_cast(&x); - if(*ptr) - return Little; - return Big; - } - }; -} // namespace - -namespace detail { - String rawMemoryToString(const void* object, unsigned size) { - // Reverse order for little endian architectures - int i = 0, end = static_cast(size), inc = 1; - if(Endianness::which() == Endianness::Little) { - i = end - 1; - end = inc = -1; - } - - unsigned const char* bytes = static_cast(object); - std::ostream* oss = tlssPush(); - *oss << "0x" << std::setfill('0') << std::hex; - for(; i != end; i += inc) - *oss << std::setw(2) << static_cast(bytes[i]); - return tlssPop(); - } - - DOCTEST_THREAD_LOCAL class - { - std::vector stack; - std::stringstream ss; - - public: - std::ostream* push() { - stack.push_back(ss.tellp()); - return &ss; - } - - String pop() { - if (stack.empty()) - DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!"); - - std::streampos pos = stack.back(); - stack.pop_back(); - unsigned sz = static_cast(ss.tellp() - pos); - ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out); - return String(ss, sz); - } - } g_oss; - - std::ostream* tlssPush() { - return g_oss.push(); - } - - String tlssPop() { - return g_oss.pop(); - } - -#ifndef DOCTEST_CONFIG_DISABLE - -namespace timer_large_integer -{ - -#if defined(DOCTEST_PLATFORM_WINDOWS) - typedef ULONGLONG type; -#else // DOCTEST_PLATFORM_WINDOWS - typedef std::uint64_t type; -#endif // DOCTEST_PLATFORM_WINDOWS -} - -typedef timer_large_integer::type ticks_t; - -#ifdef DOCTEST_CONFIG_GETCURRENTTICKS - ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } -#elif defined(DOCTEST_PLATFORM_WINDOWS) - ticks_t getCurrentTicks() { - static LARGE_INTEGER hz = {0}, hzo = {0}; - if(!hz.QuadPart) { - QueryPerformanceFrequency(&hz); - QueryPerformanceCounter(&hzo); - } - LARGE_INTEGER t; - QueryPerformanceCounter(&t); - return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; - } -#else // DOCTEST_PLATFORM_WINDOWS - ticks_t getCurrentTicks() { - timeval t; - gettimeofday(&t, nullptr); - return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); - } -#endif // DOCTEST_PLATFORM_WINDOWS - - struct Timer - { - void start() { m_ticks = getCurrentTicks(); } - unsigned int getElapsedMicroseconds() const { - return static_cast(getCurrentTicks() - m_ticks); - } - //unsigned int getElapsedMilliseconds() const { - // return static_cast(getElapsedMicroseconds() / 1000); - //} - double getElapsedSeconds() const { return static_cast(getCurrentTicks() - m_ticks) / 1000000.0; } - - private: - ticks_t m_ticks = 0; - }; - -#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS - template - using AtomicOrMultiLaneAtomic = std::atomic; -#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS - // Provides a multilane implementation of an atomic variable that supports add, sub, load, - // store. Instead of using a single atomic variable, this splits up into multiple ones, - // each sitting on a separate cache line. The goal is to provide a speedup when most - // operations are modifying. It achieves this with two properties: - // - // * Multiple atomics are used, so chance of congestion from the same atomic is reduced. - // * Each atomic sits on a separate cache line, so false sharing is reduced. - // - // The disadvantage is that there is a small overhead due to the use of TLS, and load/store - // is slower because all atomics have to be accessed. - template - class MultiLaneAtomic - { - struct CacheLineAlignedAtomic - { - std::atomic atomic{}; - char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic)]; - }; - CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; - - static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE, - "guarantee one atomic takes exactly one cache line"); - - public: - T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; } - - T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); } - - T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { - return myAtomic().fetch_add(arg, order); - } - - T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { - return myAtomic().fetch_sub(arg, order); - } - - operator T() const DOCTEST_NOEXCEPT { return load(); } - - T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT { - auto result = T(); - for(auto const& c : m_atomics) { - result += c.atomic.load(order); - } - return result; - } - - T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this] - store(desired); - return desired; - } - - void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { - // first value becomes desired", all others become 0. - for(auto& c : m_atomics) { - c.atomic.store(desired, order); - desired = {}; - } - } - - private: - // Each thread has a different atomic that it operates on. If more than NumLanes threads - // use this, some will use the same atomic. So performance will degrade a bit, but still - // everything will work. - // - // The logic here is a bit tricky. The call should be as fast as possible, so that there - // is minimal to no overhead in determining the correct atomic for the current thread. - // - // 1. A global static counter laneCounter counts continuously up. - // 2. Each successive thread will use modulo operation of that counter so it gets an atomic - // assigned in a round-robin fashion. - // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with - // little overhead. - std::atomic& myAtomic() DOCTEST_NOEXCEPT { - static std::atomic laneCounter; - DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = - laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; - - return m_atomics[tlsLaneIdx].atomic; - } - }; - - template - using AtomicOrMultiLaneAtomic = MultiLaneAtomic; -#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS - - // this holds both parameters from the command line and runtime data for tests - struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats - { - AtomicOrMultiLaneAtomic numAssertsCurrentTest_atomic; - AtomicOrMultiLaneAtomic numAssertsFailedCurrentTest_atomic; - - std::vector> filters = decltype(filters)(9); // 9 different filters - - std::vector reporters_currently_used; - - assert_handler ah = nullptr; - - Timer timer; - - std::vector stringifiedContexts; // logging from INFO() due to an exception - - // stuff for subcases - std::vector subcasesStack; - std::set subcasesPassed; - int subcasesCurrentMaxLevel; - bool should_reenter; - std::atomic shouldLogCurrentException; - - void resetRunData() { - numTestCases = 0; - numTestCasesPassingFilters = 0; - numTestSuitesPassingFilters = 0; - numTestCasesFailed = 0; - numAsserts = 0; - numAssertsFailed = 0; - numAssertsCurrentTest = 0; - numAssertsFailedCurrentTest = 0; - } - - void finalizeTestCaseData() { - seconds = timer.getElapsedSeconds(); - - // update the non-atomic counters - numAsserts += numAssertsCurrentTest_atomic; - numAssertsFailed += numAssertsFailedCurrentTest_atomic; - numAssertsCurrentTest = numAssertsCurrentTest_atomic; - numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; - - if(numAssertsFailedCurrentTest) - failure_flags |= TestCaseFailureReason::AssertFailure; - - if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && - Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) - failure_flags |= TestCaseFailureReason::Timeout; - - if(currentTest->m_should_fail) { - if(failure_flags) { - failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; - } else { - failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; - } - } else if(failure_flags && currentTest->m_may_fail) { - failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; - } else if(currentTest->m_expected_failures > 0) { - if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { - failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; - } else { - failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; - } - } - - bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || - (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || - (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); - - // if any subcase has failed - the whole test case has failed - testCaseSuccess = !(failure_flags && !ok_to_fail); - if(!testCaseSuccess) - numTestCasesFailed++; - } - }; - - ContextState* g_cs = nullptr; - - // used to avoid locks for the debug output - // TODO: figure out if this is indeed necessary/correct - seems like either there still - // could be a race or that there wouldn't be a race even if using the context directly - DOCTEST_THREAD_LOCAL bool g_no_colors; - -#endif // DOCTEST_CONFIG_DISABLE -} // namespace detail - -char* String::allocate(unsigned sz) { - if (sz <= last) { - buf[sz] = '\0'; - setLast(last - sz); - return buf; - } else { - setOnHeap(); - data.size = sz; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - data.ptr[sz] = '\0'; - return data.ptr; - } -} - -void String::setOnHeap() { *reinterpret_cast(&buf[last]) = 128; } -void String::setLast(unsigned in) { buf[last] = char(in); } - -void String::copy(const String& other) { - if(other.isOnStack()) { - memcpy(buf, other.buf, len); - } else { - memcpy(allocate(other.data.size), other.data.ptr, other.data.size); - } -} - -String::String() { - buf[0] = '\0'; - setLast(); -} - -String::~String() { - if(!isOnStack()) - delete[] data.ptr; - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -} - -String::String(const char* in) - : String(in, strlen(in)) {} - -String::String(const char* in, unsigned in_size) { - memcpy(allocate(in_size), in, in_size); -} - -String::String(std::istream& in, unsigned in_size) { - in.read(allocate(in_size), in_size); -} - -String::String(const String& other) { copy(other); } - -String& String::operator=(const String& other) { - if(this != &other) { - if(!isOnStack()) - delete[] data.ptr; - - copy(other); - } - - return *this; -} - -String& String::operator+=(const String& other) { - const unsigned my_old_size = size(); - const unsigned other_size = other.size(); - const unsigned total_size = my_old_size + other_size; - if(isOnStack()) { - if(total_size < len) { - // append to the current stack space - memcpy(buf + my_old_size, other.c_str(), other_size + 1); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - setLast(last - total_size); - } else { - // alloc new chunk - char* temp = new char[total_size + 1]; - // copy current data to new location before writing in the union - memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed - // update data in union - setOnHeap(); - data.size = total_size; - data.capacity = data.size + 1; - data.ptr = temp; - // transfer the rest of the data - memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); - } - } else { - if(data.capacity > total_size) { - // append to the current heap block - data.size = total_size; - memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); - } else { - // resize - data.capacity *= 2; - if(data.capacity <= total_size) - data.capacity = total_size + 1; - // alloc new chunk - char* temp = new char[data.capacity]; - // copy current data to new location before releasing it - memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed - // release old chunk - delete[] data.ptr; - // update the rest of the union members - data.size = total_size; - data.ptr = temp; - // transfer the rest of the data - memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); - } - } - - return *this; -} - -String::String(String&& other) { - memcpy(buf, other.buf, len); - other.buf[0] = '\0'; - other.setLast(); -} - -String& String::operator=(String&& other) { - if(this != &other) { - if(!isOnStack()) - delete[] data.ptr; - memcpy(buf, other.buf, len); - other.buf[0] = '\0'; - other.setLast(); - } - return *this; -} - -char String::operator[](unsigned i) const { - return const_cast(this)->operator[](i); // NOLINT -} - -char& String::operator[](unsigned i) { - if(isOnStack()) - return reinterpret_cast(buf)[i]; - return data.ptr[i]; -} - -DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") -unsigned String::size() const { - if(isOnStack()) - return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 - return data.size; -} -DOCTEST_GCC_SUPPRESS_WARNING_POP - -unsigned String::capacity() const { - if(isOnStack()) - return len; - return data.capacity; -} - -int String::compare(const char* other, bool no_case) const { - if(no_case) - return doctest::stricmp(c_str(), other); - return std::strcmp(c_str(), other); -} - -int String::compare(const String& other, bool no_case) const { - return compare(other.c_str(), no_case); -} - -// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; } - -// clang-format off -bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } -bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } -bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } -bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } -bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } -bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } -// clang-format on - -std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } - -namespace { - void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) -} // namespace - -namespace Color { - std::ostream& operator<<(std::ostream& s, Color::Enum code) { - color_to_stream(s, code); - return s; - } -} // namespace Color - -// clang-format off -const char* assertString(assertType::Enum at) { - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled - switch(at) { //!OCLINT missing default in switch statements - case assertType::DT_WARN : return "WARN"; - case assertType::DT_CHECK : return "CHECK"; - case assertType::DT_REQUIRE : return "REQUIRE"; - - case assertType::DT_WARN_FALSE : return "WARN_FALSE"; - case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; - case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; - - case assertType::DT_WARN_THROWS : return "WARN_THROWS"; - case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; - case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; - - case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; - case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; - case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; - - case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH"; - case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH"; - case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH"; - - case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS"; - case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS"; - case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS"; - - case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; - case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; - case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; - - case assertType::DT_WARN_EQ : return "WARN_EQ"; - case assertType::DT_CHECK_EQ : return "CHECK_EQ"; - case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; - case assertType::DT_WARN_NE : return "WARN_NE"; - case assertType::DT_CHECK_NE : return "CHECK_NE"; - case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; - case assertType::DT_WARN_GT : return "WARN_GT"; - case assertType::DT_CHECK_GT : return "CHECK_GT"; - case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; - case assertType::DT_WARN_LT : return "WARN_LT"; - case assertType::DT_CHECK_LT : return "CHECK_LT"; - case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; - case assertType::DT_WARN_GE : return "WARN_GE"; - case assertType::DT_CHECK_GE : return "CHECK_GE"; - case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; - case assertType::DT_WARN_LE : return "WARN_LE"; - case assertType::DT_CHECK_LE : return "CHECK_LE"; - case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; - - case assertType::DT_WARN_UNARY : return "WARN_UNARY"; - case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; - case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; - case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; - case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; - case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; - } - DOCTEST_MSVC_SUPPRESS_WARNING_POP - return ""; -} -// clang-format on - -const char* failureString(assertType::Enum at) { - if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional - return "WARNING"; - if(at & assertType::is_check) //!OCLINT bitwise operator in conditional - return "ERROR"; - if(at & assertType::is_require) //!OCLINT bitwise operator in conditional - return "FATAL ERROR"; - return ""; -} - -DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") -DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") -// depending on the current options this will remove the path of filenames -const char* skipPathFromFilename(const char* file) { -#ifndef DOCTEST_CONFIG_DISABLE - if(getContextOptions()->no_path_in_filenames) { - auto back = std::strrchr(file, '\\'); - auto forward = std::strrchr(file, '/'); - if(back || forward) { - if(back > forward) - forward = back; - return forward + 1; - } - } -#endif // DOCTEST_CONFIG_DISABLE - return file; -} -DOCTEST_CLANG_SUPPRESS_WARNING_POP -DOCTEST_GCC_SUPPRESS_WARNING_POP - -bool SubcaseSignature::operator<(const SubcaseSignature& other) const { - if(m_line != other.m_line) - return m_line < other.m_line; - if(std::strcmp(m_file, other.m_file) != 0) - return std::strcmp(m_file, other.m_file) < 0; - return m_name.compare(other.m_name) < 0; -} - -IContextScope::IContextScope() = default; -IContextScope::~IContextScope() = default; - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(char* in) { return toString(static_cast(in)); } -// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(bool in) { return in ? "true" : "false"; } -String toString(float in) { return fpToString(in, 5) + "f"; } -String toString(double in) { return fpToString(in, 10); } -String toString(double long in) { return fpToString(in, 15); } - -#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \ - String toString(type in) { \ - char buf[64]; \ - std::sprintf(buf, fmt, in); \ - return buf; \ - } - -DOCTEST_TO_STRING_OVERLOAD(char, "%d") -DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") -DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int short, "%d") -DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int, "%d") -DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") -DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") -DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") -DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") - -String toString(std::nullptr_t) { return "NULL"; } - -#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 -String toString(const std::string& in) { return in.c_str(); } -#endif // VS 2019 - -Approx::Approx(double value) - : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) - , m_scale(1.0) - , m_value(value) {} - -Approx Approx::operator()(double value) const { - Approx approx(value); - approx.epsilon(m_epsilon); - approx.scale(m_scale); - return approx; -} - -Approx& Approx::epsilon(double newEpsilon) { - m_epsilon = newEpsilon; - return *this; -} -Approx& Approx::scale(double newScale) { - m_scale = newScale; - return *this; -} - -bool operator==(double lhs, const Approx& rhs) { - // Thanks to Richard Harris for his help refining this formula - return std::fabs(lhs - rhs.m_value) < - rhs.m_epsilon * (rhs.m_scale + std::max(std::fabs(lhs), std::fabs(rhs.m_value))); -} -bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } -bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } -bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } -bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } -bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } -bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } -bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } -bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } -bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } -bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } -bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } - -String toString(const Approx& in) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - return "Approx( " + doctest::toString(in.m_value) + " )"; -} -const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } - -} // namespace doctest - -#ifdef DOCTEST_CONFIG_DISABLE -namespace doctest { -Context::Context(int, const char* const*) {} -Context::~Context() = default; -void Context::applyCommandLine(int, const char* const*) {} -void Context::addFilter(const char*, const char*) {} -void Context::clearFilters() {} -void Context::setOption(const char*, bool) {} -void Context::setOption(const char*, int) {} -void Context::setOption(const char*, const char*) {} -bool Context::shouldExit() { return false; } -void Context::setAsDefaultForAssertsOutOfTestCases() {} -void Context::setAssertHandler(detail::assert_handler) {} -void Context::setCout(std::ostream* out) {} -int Context::run() { return 0; } - -IReporter::~IReporter() = default; - -int IReporter::get_num_active_contexts() { return 0; } -const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } -int IReporter::get_num_stringified_contexts() { return 0; } -const String* IReporter::get_stringified_contexts() { return nullptr; } - -int registerReporter(const char*, int, IReporter*) { return 0; } - -} // namespace doctest -#else // DOCTEST_CONFIG_DISABLE - -#if !defined(DOCTEST_CONFIG_COLORS_NONE) -#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) -#ifdef DOCTEST_PLATFORM_WINDOWS -#define DOCTEST_CONFIG_COLORS_WINDOWS -#else // linux -#define DOCTEST_CONFIG_COLORS_ANSI -#endif // platform -#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI -#endif // DOCTEST_CONFIG_COLORS_NONE - -namespace doctest_detail_test_suite_ns { -// holds the current test suite -doctest::detail::TestSuite& getCurrentTestSuite() { - static doctest::detail::TestSuite data{}; - return data; -} -} // namespace doctest_detail_test_suite_ns - -namespace doctest { -namespace { - // the int (priority) is part of the key for automatic sorting - sadly one can register a - // reporter with a duplicate name and a different priority but hopefully that won't happen often :| - typedef std::map, reporterCreatorFunc> reporterMap; - - reporterMap& getReporters() { - static reporterMap data; - return data; - } - reporterMap& getListeners() { - static reporterMap data; - return data; - } -} // namespace -namespace detail { -#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ - for(auto& curr_rep : g_cs->reporters_currently_used) \ - curr_rep->function(__VA_ARGS__) - - bool checkIfShouldThrow(assertType::Enum at) { - if(at & assertType::is_require) //!OCLINT bitwise operator in conditional - return true; - - if((at & assertType::is_check) //!OCLINT bitwise operator in conditional - && getContextOptions()->abort_after > 0 && - (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= - getContextOptions()->abort_after) - return true; - - return false; - } - -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - DOCTEST_NORETURN void throwException() { - g_cs->shouldLogCurrentException = false; - throw TestFailureException(); - } // NOLINT(cert-err60-cpp) -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - void throwException() {} -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS -} // namespace detail - -namespace { - using namespace detail; - // matching of a string against a wildcard mask (case sensitivity configurable) taken from - // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing - int wildcmp(const char* str, const char* wild, bool caseSensitive) { - const char* cp = str; - const char* mp = wild; - - while((*str) && (*wild != '*')) { - if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && - (*wild != '?')) { - return 0; - } - wild++; - str++; - } - - while(*str) { - if(*wild == '*') { - if(!*++wild) { - return 1; - } - mp = wild; - cp = str + 1; - } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || - (*wild == '?')) { - wild++; - str++; - } else { - wild = mp; //!OCLINT parameter reassignment - str = cp++; //!OCLINT parameter reassignment - } - } - - while(*wild == '*') { - wild++; - } - return !*wild; - } - - //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html - //unsigned hashStr(unsigned const char* str) { - // unsigned long hash = 5381; - // char c; - // while((c = *str++)) - // hash = ((hash << 5) + hash) + c; // hash * 33 + c - // return hash; - //} - - // checks if the name matches any of the filters (and can be configured what to do when empty) - bool matchesAny(const char* name, const std::vector& filters, bool matchEmpty, - bool caseSensitive) { - if(filters.empty() && matchEmpty) - return true; - for(auto& curr : filters) - if(wildcmp(name, curr.c_str(), caseSensitive)) - return true; - return false; - } -} // namespace -namespace detail { - - Subcase::Subcase(const String& name, const char* file, int line) - : m_signature({name, file, line}) { - auto* s = g_cs; - - // check subcase filters - if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { - if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) - return; - if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) - return; - } - - // if a Subcase on the same level has already been entered - if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) { - s->should_reenter = true; - return; - } - - // push the current signature to the stack so we can check if the - // current stack + the current new subcase have been traversed - s->subcasesStack.push_back(m_signature); - if(s->subcasesPassed.count(s->subcasesStack) != 0) { - // pop - revert to previous stack since we've already passed this - s->subcasesStack.pop_back(); - return; - } - - s->subcasesCurrentMaxLevel = s->subcasesStack.size(); - m_entered = true; - - DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); - } - - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - - Subcase::~Subcase() { - if(m_entered) { - // only mark the subcase stack as passed if no subcases have been skipped - if(g_cs->should_reenter == false) - g_cs->subcasesPassed.insert(g_cs->subcasesStack); - g_cs->subcasesStack.pop_back(); - -#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) - if(std::uncaught_exceptions() > 0 -#else - if(std::uncaught_exception() -#endif - && g_cs->shouldLogCurrentException) { - DOCTEST_ITERATE_THROUGH_REPORTERS( - test_case_exception, {"exception thrown in subcase - will translate later " - "when the whole test case has been exited (cannot " - "translate while there is an active exception)", - false}); - g_cs->shouldLogCurrentException = false; - } - DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); - } - } - - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP - DOCTEST_MSVC_SUPPRESS_WARNING_POP - - Subcase::operator bool() const { return m_entered; } - - Result::Result(bool passed, const String& decomposition) - : m_passed(passed) - , m_decomp(decomposition) {} - - ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) - : m_at(at) {} - - TestSuite& TestSuite::operator*(const char* in) { - m_test_suite = in; - return *this; - } - - TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type, int template_id) { - m_file = file; - m_line = line; - m_name = nullptr; // will be later overridden in operator* - m_test_suite = test_suite.m_test_suite; - m_description = test_suite.m_description; - m_skip = test_suite.m_skip; - m_no_breaks = test_suite.m_no_breaks; - m_no_output = test_suite.m_no_output; - m_may_fail = test_suite.m_may_fail; - m_should_fail = test_suite.m_should_fail; - m_expected_failures = test_suite.m_expected_failures; - m_timeout = test_suite.m_timeout; - - m_test = test; - m_type = type; - m_template_id = template_id; - } - - TestCase::TestCase(const TestCase& other) - : TestCaseData() { - *this = other; - } - - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function - DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice - TestCase& TestCase::operator=(const TestCase& other) { - static_cast(*this) = static_cast(other); - - m_test = other.m_test; - m_type = other.m_type; - m_template_id = other.m_template_id; - m_full_name = other.m_full_name; - - if(m_template_id != -1) - m_name = m_full_name.c_str(); - return *this; - } - DOCTEST_MSVC_SUPPRESS_WARNING_POP - - TestCase& TestCase::operator*(const char* in) { - m_name = in; - // make a new name with an appended type for templated test case - if(m_template_id != -1) { - m_full_name = String(m_name) + m_type; - // redirect the name to point to the newly constructed full name - m_name = m_full_name.c_str(); - } - return *this; - } - - bool TestCase::operator<(const TestCase& other) const { - // this will be used only to differentiate between test cases - not relevant for sorting - if(m_line != other.m_line) - return m_line < other.m_line; - const int name_cmp = strcmp(m_name, other.m_name); - if(name_cmp != 0) - return name_cmp < 0; - const int file_cmp = m_file.compare(other.m_file); - if(file_cmp != 0) - return file_cmp < 0; - return m_template_id < other.m_template_id; - } - - // all the registered tests - std::set& getRegisteredTests() { - static std::set data; - return data; - } -} // namespace detail -namespace { - using namespace detail; - // for sorting tests by file/line - bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { - // this is needed because MSVC gives different case for drive letters - // for __FILE__ when evaluated in a header and a source file - const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); - if(res != 0) - return res < 0; - if(lhs->m_line != rhs->m_line) - return lhs->m_line < rhs->m_line; - return lhs->m_template_id < rhs->m_template_id; - } - - // for sorting tests by suite/file/line - bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { - const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); - if(res != 0) - return res < 0; - return fileOrderComparator(lhs, rhs); - } - - // for sorting tests by name/suite/file/line - bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { - const int res = std::strcmp(lhs->m_name, rhs->m_name); - if(res != 0) - return res < 0; - return suiteOrderComparator(lhs, rhs); - } - - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - void color_to_stream(std::ostream& s, Color::Enum code) { - static_cast(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS - static_cast(code); // for DOCTEST_CONFIG_COLORS_NONE -#ifdef DOCTEST_CONFIG_COLORS_ANSI - if(g_no_colors || - (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) - return; - - auto col = ""; - // clang-format off - switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement - case Color::Red: col = "[0;31m"; break; - case Color::Green: col = "[0;32m"; break; - case Color::Blue: col = "[0;34m"; break; - case Color::Cyan: col = "[0;36m"; break; - case Color::Yellow: col = "[0;33m"; break; - case Color::Grey: col = "[1;30m"; break; - case Color::LightGrey: col = "[0;37m"; break; - case Color::BrightRed: col = "[1;31m"; break; - case Color::BrightGreen: col = "[1;32m"; break; - case Color::BrightWhite: col = "[1;37m"; break; - case Color::Bright: // invalid - case Color::None: - case Color::White: - default: col = "[0m"; - } - // clang-format on - s << "\033" << col; -#endif // DOCTEST_CONFIG_COLORS_ANSI - -#ifdef DOCTEST_CONFIG_COLORS_WINDOWS - if(g_no_colors || - (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false)) - return; - - static struct ConsoleHelper { - HANDLE stdoutHandle; - WORD origFgAttrs; - WORD origBgAttrs; - - ConsoleHelper() { - stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); - origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | - BACKGROUND_BLUE | BACKGROUND_INTENSITY); - origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | - FOREGROUND_BLUE | FOREGROUND_INTENSITY); - } - } ch; - -#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs) - - // clang-format off - switch (code) { - case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; - case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; - case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; - case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; - case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; - case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; - case Color::Grey: DOCTEST_SET_ATTR(0); break; - case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; - case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; - case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; - case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; - case Color::None: - case Color::Bright: // invalid - default: DOCTEST_SET_ATTR(ch.origFgAttrs); - } - // clang-format on -#endif // DOCTEST_CONFIG_COLORS_WINDOWS - } - DOCTEST_CLANG_SUPPRESS_WARNING_POP - - std::vector& getExceptionTranslators() { - static std::vector data; - return data; - } - - String translateActiveException() { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - String res; - auto& translators = getExceptionTranslators(); - for(auto& curr : translators) - if(curr->translate(res)) - return res; - // clang-format off - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") - try { - throw; - } catch(std::exception& ex) { - return ex.what(); - } catch(std::string& msg) { - return msg.c_str(); - } catch(const char* msg) { - return msg; - } catch(...) { - return "unknown exception"; - } - DOCTEST_GCC_SUPPRESS_WARNING_POP -// clang-format on -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - return ""; -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - } -} // namespace - -namespace detail { - // used by the macros for registering tests - int regTest(const TestCase& tc) { - getRegisteredTests().insert(tc); - return 0; - } - - // sets the current test suite - int setTestSuite(const TestSuite& ts) { - doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; - return 0; - } - -#ifdef DOCTEST_IS_DEBUGGER_ACTIVE - bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } -#else // DOCTEST_IS_DEBUGGER_ACTIVE -#ifdef DOCTEST_PLATFORM_LINUX - class ErrnoGuard { - public: - ErrnoGuard() : m_oldErrno(errno) {} - ~ErrnoGuard() { errno = m_oldErrno; } - private: - int m_oldErrno; - }; - // See the comments in Catch2 for the reasoning behind this implementation: - // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102 - bool isDebuggerActive() { - ErrnoGuard guard; - std::ifstream in("/proc/self/status"); - for(std::string line; std::getline(in, line);) { - static const int PREFIX_LEN = 11; - if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) { - return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; - } - } - return false; - } -#elif defined(DOCTEST_PLATFORM_MAC) - // The following function is taken directly from the following technical note: - // https://developer.apple.com/library/archive/qa/qa1361/_index.html - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). - bool isDebuggerActive() { - int mib[4]; - kinfo_proc info; - size_t size; - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. - info.kp_proc.p_flag = 0; - // Initialize mib, which tells sysctl the info we want, in this case - // we're looking for information about a specific process ID. - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); - // Call sysctl. - size = sizeof(info); - if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { - std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; - return false; - } - // We're being debugged if the P_TRACED flag is set. - return ((info.kp_proc.p_flag & P_TRACED) != 0); - } -#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) - bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } -#else - bool isDebuggerActive() { return false; } -#endif // Platform -#endif // DOCTEST_IS_DEBUGGER_ACTIVE - - void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { - if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == - getExceptionTranslators().end()) - getExceptionTranslators().push_back(et); - } - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* s, char* in) { *s << in; } - void toStream(std::ostream* s, const char* in) { *s << in; } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } - void toStream(std::ostream* s, float in) { *s << in; } - void toStream(std::ostream* s, double in) { *s << in; } - void toStream(std::ostream* s, double long in) { *s << in; } - - void toStream(std::ostream* s, char in) { *s << in; } - void toStream(std::ostream* s, char signed in) { *s << in; } - void toStream(std::ostream* s, char unsigned in) { *s << in; } - void toStream(std::ostream* s, int short in) { *s << in; } - void toStream(std::ostream* s, int short unsigned in) { *s << in; } - void toStream(std::ostream* s, int in) { *s << in; } - void toStream(std::ostream* s, int unsigned in) { *s << in; } - void toStream(std::ostream* s, int long in) { *s << in; } - void toStream(std::ostream* s, int long unsigned in) { *s << in; } - void toStream(std::ostream* s, int long long in) { *s << in; } - void toStream(std::ostream* s, int long long unsigned in) { *s << in; } - - DOCTEST_THREAD_LOCAL std::vector g_infoContexts; // for logging with INFO() - - ContextScopeBase::ContextScopeBase() { - g_infoContexts.push_back(this); - } - - ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) { - if (other.need_to_destroy) { - other.destroy(); - } - other.need_to_destroy = false; - g_infoContexts.push_back(this); - } - - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - - // destroy cannot be inlined into the destructor because that would mean calling stringify after - // ContextScope has been destroyed (base class destructors run after derived class destructors). - // Instead, ContextScope calls this method directly from its destructor. - void ContextScopeBase::destroy() { -#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) - if(std::uncaught_exceptions() > 0) { -#else - if(std::uncaught_exception()) { -#endif - std::ostringstream s; - this->stringify(&s); - g_cs->stringifiedContexts.push_back(s.str().c_str()); - } - g_infoContexts.pop_back(); - } - - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP - DOCTEST_MSVC_SUPPRESS_WARNING_POP -} // namespace detail -namespace { - using namespace detail; - -#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) - struct FatalConditionHandler - { - static void reset() {} - static void allocateAltStackMem() {} - static void freeAltStackMem() {} - }; -#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH - - void reportFatal(const std::string&); - -#ifdef DOCTEST_PLATFORM_WINDOWS - - struct SignalDefs - { - DWORD id; - const char* name; - }; - // There is no 1-1 mapping between signals and windows exceptions. - // Windows can easily distinguish between SO and SigSegV, - // but SigInt, SigTerm, etc are handled differently. - SignalDefs signalDefs[] = { - {static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), - "SIGILL - Illegal instruction signal"}, - {static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"}, - {static_cast(EXCEPTION_ACCESS_VIOLATION), - "SIGSEGV - Segmentation violation signal"}, - {static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"}, - }; - - struct FatalConditionHandler - { - static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { - // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the - // console just once no matter how many threads have crashed. - static std::mutex mutex; - static bool execute = true; - { - std::lock_guard lock(mutex); - if(execute) { - bool reported = false; - for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { - reportFatal(signalDefs[i].name); - reported = true; - break; - } - } - if(reported == false) - reportFatal("Unhandled SEH exception caught"); - if(isDebuggerActive() && !g_cs->no_breaks) - DOCTEST_BREAK_INTO_DEBUGGER(); - } - execute = false; - } - std::exit(EXIT_FAILURE); - } - - static void allocateAltStackMem() {} - static void freeAltStackMem() {} - - FatalConditionHandler() { - isSet = true; - // 32k seems enough for doctest to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - // Register an unhandled exception filter - previousTop = SetUnhandledExceptionFilter(handleException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); - - // On Windows uncaught exceptions from another thread, exceptions from - // destructors, or calls to std::terminate are not a SEH exception - - // The terminal handler gets called when: - // - std::terminate is called FROM THE TEST RUNNER THREAD - // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD - original_terminate_handler = std::get_terminate(); - std::set_terminate([]() DOCTEST_NOEXCEPT { - reportFatal("Terminate handler called"); - if(isDebuggerActive() && !g_cs->no_breaks) - DOCTEST_BREAK_INTO_DEBUGGER(); - std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well - }); - - // SIGABRT is raised when: - // - std::terminate is called FROM A DIFFERENT THREAD - // - an exception is thrown from a destructor FROM A DIFFERENT THREAD - // - an uncaught exception is thrown FROM A DIFFERENT THREAD - prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT { - if(signal == SIGABRT) { - reportFatal("SIGABRT - Abort (abnormal termination) signal"); - if(isDebuggerActive() && !g_cs->no_breaks) - DOCTEST_BREAK_INTO_DEBUGGER(); - std::exit(EXIT_FAILURE); - } - }); - - // The following settings are taken from google test, and more - // specifically from UnitTest::Run() inside of gtest.cc - - // the user does not want to see pop-up dialogs about crashes - prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | - SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); - // This forces the abort message to go to stderr in all circumstances. - prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR); - // In the debug version, Visual Studio pops up a separate dialog - // offering a choice to debug the aborted program - we want to disable that. - prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); - // In debug mode, the Windows CRT can crash with an assertion over invalid - // input (e.g. passing an invalid file descriptor). The default handling - // for these assertions is to pop up a dialog and wait for user input. - // Instead ask the CRT to dump such assertions to stderr non-interactively. - prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); - prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); - } - - static void reset() { - if(isSet) { - // Unregister handler and restore the old guarantee - SetUnhandledExceptionFilter(previousTop); - SetThreadStackGuarantee(&guaranteeSize); - std::set_terminate(original_terminate_handler); - std::signal(SIGABRT, prev_sigabrt_handler); - SetErrorMode(prev_error_mode_1); - _set_error_mode(prev_error_mode_2); - _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); - static_cast(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode)); - static_cast(_CrtSetReportFile(_CRT_ASSERT, prev_report_file)); - isSet = false; - } - } - - ~FatalConditionHandler() { reset(); } - - private: - static UINT prev_error_mode_1; - static int prev_error_mode_2; - static unsigned int prev_abort_behavior; - static int prev_report_mode; - static _HFILE prev_report_file; - static void (DOCTEST_CDECL *prev_sigabrt_handler)(int); - static std::terminate_handler original_terminate_handler; - static bool isSet; - static ULONG guaranteeSize; - static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; - }; - - UINT FatalConditionHandler::prev_error_mode_1; - int FatalConditionHandler::prev_error_mode_2; - unsigned int FatalConditionHandler::prev_abort_behavior; - int FatalConditionHandler::prev_report_mode; - _HFILE FatalConditionHandler::prev_report_file; - void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int); - std::terminate_handler FatalConditionHandler::original_terminate_handler; - bool FatalConditionHandler::isSet = false; - ULONG FatalConditionHandler::guaranteeSize = 0; - LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; - -#else // DOCTEST_PLATFORM_WINDOWS - - struct SignalDefs - { - int id; - const char* name; - }; - SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, - {SIGILL, "SIGILL - Illegal instruction signal"}, - {SIGFPE, "SIGFPE - Floating point error signal"}, - {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, - {SIGTERM, "SIGTERM - Termination request signal"}, - {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; - - struct FatalConditionHandler - { - static bool isSet; - static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; - static stack_t oldSigStack; - static size_t altStackSize; - static char* altStackMem; - - static void handleSignal(int sig) { - const char* name = ""; - for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - SignalDefs& def = signalDefs[i]; - if(sig == def.id) { - name = def.name; - break; - } - } - reset(); - reportFatal(name); - raise(sig); - } - - static void allocateAltStackMem() { - altStackMem = new char[altStackSize]; - } - - static void freeAltStackMem() { - delete[] altStackMem; - } - - FatalConditionHandler() { - isSet = true; - stack_t sigStack; - sigStack.ss_sp = altStackMem; - sigStack.ss_size = altStackSize; - sigStack.ss_flags = 0; - sigaltstack(&sigStack, &oldSigStack); - struct sigaction sa = {}; - sa.sa_handler = handleSignal; // NOLINT - sa.sa_flags = SA_ONSTACK; - for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); - } - } - - ~FatalConditionHandler() { reset(); } - static void reset() { - if(isSet) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } - } - }; - - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ; - char* FatalConditionHandler::altStackMem = nullptr; - -#endif // DOCTEST_PLATFORM_WINDOWS -#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH - -} // namespace - -namespace { - using namespace detail; - -#ifdef DOCTEST_PLATFORM_WINDOWS -#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) -#else - // TODO: integration with XCode and other IDEs -#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros) -#endif // Platform - - void addAssert(assertType::Enum at) { - if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional - g_cs->numAssertsCurrentTest_atomic++; - } - - void addFailedAssert(assertType::Enum at) { - if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional - g_cs->numAssertsFailedCurrentTest_atomic++; - } - -#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) - void reportFatal(const std::string& message) { - g_cs->failure_flags |= TestCaseFailureReason::Crash; - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); - - while(g_cs->subcasesStack.size()) { - g_cs->subcasesStack.pop_back(); - DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); - } - - g_cs->finalizeTestCaseData(); - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); - } -#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH -} // namespace -namespace detail { - - ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, - const char* exception_type, const char* exception_string) { - m_test_case = g_cs->currentTest; - m_at = at; - m_file = file; - m_line = line; - m_expr = expr; - m_failed = true; - m_threw = false; - m_threw_as = false; - m_exception_type = exception_type; - m_exception_string = exception_string; -#if DOCTEST_MSVC - if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC - ++m_expr; -#endif // MSVC - } - - void ResultBuilder::setResult(const Result& res) { - m_decomp = res.m_decomp; - m_failed = !res.m_passed; - } - - void ResultBuilder::translateException() { - m_threw = true; - m_exception = translateActiveException(); - } - - bool ResultBuilder::log() { - if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional - m_failed = !m_threw; - } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT - m_failed = !m_threw_as || (m_exception != m_exception_string); - } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional - m_failed = !m_threw_as; - } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional - m_failed = m_exception != m_exception_string; - } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional - m_failed = m_threw; - } - - if(m_exception.size()) - m_exception = "\"" + m_exception + "\""; - - if(is_running_in_test) { - addAssert(m_at); - DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); - - if(m_failed) - addFailedAssert(m_at); - } else if(m_failed) { - failed_out_of_a_testing_context(*this); - } - - return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks && - (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger - } - - void ResultBuilder::react() const { - if(m_failed && checkIfShouldThrow(m_at)) - throwException(); - } - - void failed_out_of_a_testing_context(const AssertData& ad) { - if(g_cs->ah) - g_cs->ah(ad); - else - std::abort(); - } - - bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, - Result result) { - bool failed = !result.m_passed; - - // ################################################################################### - // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT - // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED - // ################################################################################### - DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); - DOCTEST_ASSERT_IN_TESTS(result.m_decomp); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - return !failed; - } - - MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { - m_stream = tlssPush(); - m_file = file; - m_line = line; - m_severity = severity; - } - - MessageBuilder::~MessageBuilder() { - if (!logged) - tlssPop(); - } - - IExceptionTranslator::IExceptionTranslator() = default; - IExceptionTranslator::~IExceptionTranslator() = default; - - bool MessageBuilder::log() { - if (!logged) { - m_string = tlssPop(); - logged = true; - } - - DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); - - const bool isWarn = m_severity & assertType::is_warn; - - // warn is just a message in this context so we don't treat it as an assert - if(!isWarn) { - addAssert(m_severity); - addFailedAssert(m_severity); - } - - return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn && - (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger - } - - void MessageBuilder::react() { - if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional - throwException(); - } -} // namespace detail -namespace { - using namespace detail; - - // clang-format off - -// ================================================================================================= -// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp -// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. -// ================================================================================================= - - class XmlEncode { - public: - enum ForWhat { ForTextNodes, ForAttributes }; - - XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); - - void encodeTo( std::ostream& os ) const; - - friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); - - private: - std::string m_str; - ForWhat m_forWhat; - }; - - class XmlWriter { - public: - - class ScopedElement { - public: - ScopedElement( XmlWriter* writer ); - - ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; - ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; - - ~ScopedElement(); - - ScopedElement& writeText( std::string const& text, bool indent = true ); - - template - ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { - m_writer->writeAttribute( name, attribute ); - return *this; - } - - private: - mutable XmlWriter* m_writer = nullptr; - }; - - XmlWriter( std::ostream& os = std::cout ); - ~XmlWriter(); - - XmlWriter( XmlWriter const& ) = delete; - XmlWriter& operator=( XmlWriter const& ) = delete; - - XmlWriter& startElement( std::string const& name ); - - ScopedElement scopedElement( std::string const& name ); - - XmlWriter& endElement(); - - XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); - - XmlWriter& writeAttribute( std::string const& name, const char* attribute ); - - XmlWriter& writeAttribute( std::string const& name, bool attribute ); - - template - XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - std::stringstream rss; - rss << attribute; - return writeAttribute( name, rss.str() ); - } - - XmlWriter& writeText( std::string const& text, bool indent = true ); - - //XmlWriter& writeComment( std::string const& text ); - - //void writeStylesheetRef( std::string const& url ); - - //XmlWriter& writeBlankLine(); - - void ensureTagClosed(); - - private: - - void writeDeclaration(); - - void newlineIfNecessary(); - - bool m_tagIsOpen = false; - bool m_needsNewline = false; - std::vector m_tags; - std::string m_indent; - std::ostream& m_os; - }; - -// ================================================================================================= -// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp -// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. -// ================================================================================================= - -using uchar = unsigned char; - -namespace { - - size_t trailingBytes(unsigned char c) { - if ((c & 0xE0) == 0xC0) { - return 2; - } - if ((c & 0xF0) == 0xE0) { - return 3; - } - if ((c & 0xF8) == 0xF0) { - return 4; - } - DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); - } - - uint32_t headerValue(unsigned char c) { - if ((c & 0xE0) == 0xC0) { - return c & 0x1F; - } - if ((c & 0xF0) == 0xE0) { - return c & 0x0F; - } - if ((c & 0xF8) == 0xF0) { - return c & 0x07; - } - DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); - } - - void hexEscapeChar(std::ostream& os, unsigned char c) { - std::ios_base::fmtflags f(os.flags()); - os << "\\x" - << std::uppercase << std::hex << std::setfill('0') << std::setw(2) - << static_cast(c); - os.flags(f); - } - -} // anonymous namespace - - XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) - : m_str( str ), - m_forWhat( forWhat ) - {} - - void XmlEncode::encodeTo( std::ostream& os ) const { - // Apostrophe escaping not necessary if we always use " to write attributes - // (see: https://www.w3.org/TR/xml/#syntax) - - for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { - uchar c = m_str[idx]; - switch (c) { - case '<': os << "<"; break; - case '&': os << "&"; break; - - case '>': - // See: https://www.w3.org/TR/xml/#syntax - if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') - os << ">"; - else - os << c; - break; - - case '\"': - if (m_forWhat == ForAttributes) - os << """; - else - os << c; - break; - - default: - // Check for control characters and invalid utf-8 - - // Escape control characters in standard ascii - // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 - if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { - hexEscapeChar(os, c); - break; - } - - // Plain ASCII: Write it to stream - if (c < 0x7F) { - os << c; - break; - } - - // UTF-8 territory - // Check if the encoding is valid and if it is not, hex escape bytes. - // Important: We do not check the exact decoded values for validity, only the encoding format - // First check that this bytes is a valid lead byte: - // This means that it is not encoded as 1111 1XXX - // Or as 10XX XXXX - if (c < 0xC0 || - c >= 0xF8) { - hexEscapeChar(os, c); - break; - } - - auto encBytes = trailingBytes(c); - // Are there enough bytes left to avoid accessing out-of-bounds memory? - if (idx + encBytes - 1 >= m_str.size()) { - hexEscapeChar(os, c); - break; - } - // The header is valid, check data - // The next encBytes bytes must together be a valid utf-8 - // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) - bool valid = true; - uint32_t value = headerValue(c); - for (std::size_t n = 1; n < encBytes; ++n) { - uchar nc = m_str[idx + n]; - valid &= ((nc & 0xC0) == 0x80); - value = (value << 6) | (nc & 0x3F); - } - - if ( - // Wrong bit pattern of following bytes - (!valid) || - // Overlong encodings - (value < 0x80) || - ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant - (0x800 < value && value < 0x10000 && encBytes > 3) || - // Encoded value out of range - (value >= 0x110000) - ) { - hexEscapeChar(os, c); - break; - } - - // If we got here, this is in fact a valid(ish) utf-8 sequence - for (std::size_t n = 0; n < encBytes; ++n) { - os << m_str[idx + n]; - } - idx += encBytes - 1; - break; - } - } - } - - std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { - xmlEncode.encodeTo( os ); - return os; - } - - XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) - : m_writer( writer ) - {} - - XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT - : m_writer( other.m_writer ){ - other.m_writer = nullptr; - } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { - if ( m_writer ) { - m_writer->endElement(); - } - m_writer = other.m_writer; - other.m_writer = nullptr; - return *this; - } - - - XmlWriter::ScopedElement::~ScopedElement() { - if( m_writer ) - m_writer->endElement(); - } - - XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { - m_writer->writeText( text, indent ); - return *this; - } - - XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) - { - writeDeclaration(); - } - - XmlWriter::~XmlWriter() { - while( !m_tags.empty() ) - endElement(); - } - - XmlWriter& XmlWriter::startElement( std::string const& name ) { - ensureTagClosed(); - newlineIfNecessary(); - m_os << m_indent << '<' << name; - m_tags.push_back( name ); - m_indent += " "; - m_tagIsOpen = true; - return *this; - } - - XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { - ScopedElement scoped( this ); - startElement( name ); - return scoped; - } - - XmlWriter& XmlWriter::endElement() { - newlineIfNecessary(); - m_indent = m_indent.substr( 0, m_indent.size()-2 ); - if( m_tagIsOpen ) { - m_os << "/>"; - m_tagIsOpen = false; - } - else { - m_os << m_indent << ""; - } - m_os << std::endl; - m_tags.pop_back(); - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) - m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { - if( !name.empty() && attribute && attribute[0] != '\0' ) - m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { - m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { - if( !text.empty() ){ - bool tagWasOpen = m_tagIsOpen; - ensureTagClosed(); - if( tagWasOpen && indent ) - m_os << m_indent; - m_os << XmlEncode( text ); - m_needsNewline = true; - } - return *this; - } - - //XmlWriter& XmlWriter::writeComment( std::string const& text ) { - // ensureTagClosed(); - // m_os << m_indent << ""; - // m_needsNewline = true; - // return *this; - //} - - //void XmlWriter::writeStylesheetRef( std::string const& url ) { - // m_os << "\n"; - //} - - //XmlWriter& XmlWriter::writeBlankLine() { - // ensureTagClosed(); - // m_os << '\n'; - // return *this; - //} - - void XmlWriter::ensureTagClosed() { - if( m_tagIsOpen ) { - m_os << ">" << std::endl; - m_tagIsOpen = false; - } - } - - void XmlWriter::writeDeclaration() { - m_os << "\n"; - } - - void XmlWriter::newlineIfNecessary() { - if( m_needsNewline ) { - m_os << std::endl; - m_needsNewline = false; - } - } - -// ================================================================================================= -// End of copy-pasted code from Catch -// ================================================================================================= - - // clang-format on - - struct XmlReporter : public IReporter - { - XmlWriter xml; - std::mutex mutex; - - // caching pointers/references to objects of these types - safe to do - const ContextOptions& opt; - const TestCaseData* tc = nullptr; - - XmlReporter(const ContextOptions& co) - : xml(*co.cout) - , opt(co) {} - - void log_contexts() { - int num_contexts = get_num_active_contexts(); - if(num_contexts) { - auto contexts = get_active_contexts(); - std::stringstream ss; - for(int i = 0; i < num_contexts; ++i) { - contexts[i]->stringify(&ss); - xml.scopedElement("Info").writeText(ss.str()); - ss.str(""); - } - } - } - - unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } - - void test_case_start_impl(const TestCaseData& in) { - bool open_ts_tag = false; - if(tc != nullptr) { // we have already opened a test suite - if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { - xml.endElement(); - open_ts_tag = true; - } - } - else { - open_ts_tag = true; // first test case ==> first test suite - } - - if(open_ts_tag) { - xml.startElement("TestSuite"); - xml.writeAttribute("name", in.m_test_suite); - } - - tc = ∈ - xml.startElement("TestCase") - .writeAttribute("name", in.m_name) - .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) - .writeAttribute("line", line(in.m_line)) - .writeAttribute("description", in.m_description); - - if(Approx(in.m_timeout) != 0) - xml.writeAttribute("timeout", in.m_timeout); - if(in.m_may_fail) - xml.writeAttribute("may_fail", true); - if(in.m_should_fail) - xml.writeAttribute("should_fail", true); - } - - // ========================================================================================= - // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE - // ========================================================================================= - - void report_query(const QueryData& in) override { - test_run_start(); - if(opt.list_reporters) { - for(auto& curr : getListeners()) - xml.scopedElement("Listener") - .writeAttribute("priority", curr.first.first) - .writeAttribute("name", curr.first.second); - for(auto& curr : getReporters()) - xml.scopedElement("Reporter") - .writeAttribute("priority", curr.first.first) - .writeAttribute("name", curr.first.second); - } else if(opt.count || opt.list_test_cases) { - for(unsigned i = 0; i < in.num_data; ++i) { - xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) - .writeAttribute("testsuite", in.data[i]->m_test_suite) - .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) - .writeAttribute("line", line(in.data[i]->m_line)) - .writeAttribute("skipped", in.data[i]->m_skip); - } - xml.scopedElement("OverallResultsTestCases") - .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); - } else if(opt.list_test_suites) { - for(unsigned i = 0; i < in.num_data; ++i) - xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); - xml.scopedElement("OverallResultsTestCases") - .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); - xml.scopedElement("OverallResultsTestSuites") - .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); - } - xml.endElement(); - } - - void test_run_start() override { - // remove .exe extension - mainly to have the same output on UNIX and Windows - std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); -#ifdef DOCTEST_PLATFORM_WINDOWS - if(binary_name.rfind(".exe") != std::string::npos) - binary_name = binary_name.substr(0, binary_name.length() - 4); -#endif // DOCTEST_PLATFORM_WINDOWS - - xml.startElement("doctest").writeAttribute("binary", binary_name); - if(opt.no_version == false) - xml.writeAttribute("version", DOCTEST_VERSION_STR); - - // only the consequential ones (TODO: filters) - xml.scopedElement("Options") - .writeAttribute("order_by", opt.order_by.c_str()) - .writeAttribute("rand_seed", opt.rand_seed) - .writeAttribute("first", opt.first) - .writeAttribute("last", opt.last) - .writeAttribute("abort_after", opt.abort_after) - .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) - .writeAttribute("case_sensitive", opt.case_sensitive) - .writeAttribute("no_throw", opt.no_throw) - .writeAttribute("no_skip", opt.no_skip); - } - - void test_run_end(const TestRunStats& p) override { - if(tc) // the TestSuite tag - only if there has been at least 1 test case - xml.endElement(); - - xml.scopedElement("OverallResultsAsserts") - .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) - .writeAttribute("failures", p.numAssertsFailed); - - xml.startElement("OverallResultsTestCases") - .writeAttribute("successes", - p.numTestCasesPassingFilters - p.numTestCasesFailed) - .writeAttribute("failures", p.numTestCasesFailed); - if(opt.no_skipped_summary == false) - xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); - xml.endElement(); - - xml.endElement(); - } - - void test_case_start(const TestCaseData& in) override { - test_case_start_impl(in); - xml.ensureTagClosed(); - } - - void test_case_reenter(const TestCaseData&) override {} - - void test_case_end(const CurrentTestCaseStats& st) override { - xml.startElement("OverallResultsAsserts") - .writeAttribute("successes", - st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) - .writeAttribute("failures", st.numAssertsFailedCurrentTest) - .writeAttribute("test_case_success", st.testCaseSuccess); - if(opt.duration) - xml.writeAttribute("duration", st.seconds); - if(tc->m_expected_failures) - xml.writeAttribute("expected_failures", tc->m_expected_failures); - xml.endElement(); - - xml.endElement(); - } - - void test_case_exception(const TestCaseException& e) override { - std::lock_guard lock(mutex); - - xml.scopedElement("Exception") - .writeAttribute("crash", e.is_crash) - .writeText(e.error_string.c_str()); - } - - void subcase_start(const SubcaseSignature& in) override { - xml.startElement("SubCase") - .writeAttribute("name", in.m_name) - .writeAttribute("filename", skipPathFromFilename(in.m_file)) - .writeAttribute("line", line(in.m_line)); - xml.ensureTagClosed(); - } - - void subcase_end() override { xml.endElement(); } - - void log_assert(const AssertData& rb) override { - if(!rb.m_failed && !opt.success) - return; - - std::lock_guard lock(mutex); - - xml.startElement("Expression") - .writeAttribute("success", !rb.m_failed) - .writeAttribute("type", assertString(rb.m_at)) - .writeAttribute("filename", skipPathFromFilename(rb.m_file)) - .writeAttribute("line", line(rb.m_line)); - - xml.scopedElement("Original").writeText(rb.m_expr); - - if(rb.m_threw) - xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); - - if(rb.m_at & assertType::is_throws_as) - xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); - if(rb.m_at & assertType::is_throws_with) - xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string); - if((rb.m_at & assertType::is_normal) && !rb.m_threw) - xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); - - log_contexts(); - - xml.endElement(); - } - - void log_message(const MessageData& mb) override { - std::lock_guard lock(mutex); - - xml.startElement("Message") - .writeAttribute("type", failureString(mb.m_severity)) - .writeAttribute("filename", skipPathFromFilename(mb.m_file)) - .writeAttribute("line", line(mb.m_line)); - - xml.scopedElement("Text").writeText(mb.m_string.c_str()); - - log_contexts(); - - xml.endElement(); - } - - void test_case_skipped(const TestCaseData& in) override { - if(opt.no_skipped_summary == false) { - test_case_start_impl(in); - xml.writeAttribute("skipped", "true"); - xml.endElement(); - } - } - }; - - DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); - - void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { - if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == - 0) //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " - << Color::None; - - if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional - s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; - } else if((rb.m_at & assertType::is_throws_as) && - (rb.m_at & assertType::is_throws_with)) { //!OCLINT - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; - if(rb.m_threw) { - if(!rb.m_failed) { - s << "threw as expected!\n"; - } else { - s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; - } - } else { - s << "did NOT throw at all!\n"; - } - } else if(rb.m_at & - assertType::is_throws_as) { //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " - << rb.m_exception_type << " ) " << Color::None - << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : - "threw a DIFFERENT exception: ") : - "did NOT throw at all!") - << Color::Cyan << rb.m_exception << "\n"; - } else if(rb.m_at & - assertType::is_throws_with) { //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\" ) " << Color::None - << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : - "threw a DIFFERENT exception: ") : - "did NOT throw at all!") - << Color::Cyan << rb.m_exception << "\n"; - } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional - s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan - << rb.m_exception << "\n"; - } else { - s << (rb.m_threw ? "THREW exception: " : - (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); - if(rb.m_threw) - s << rb.m_exception << "\n"; - else - s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; - } - } - - // TODO: - // - log_message() - // - respond to queries - // - honor remaining options - // - more attributes in tags - struct JUnitReporter : public IReporter - { - XmlWriter xml; - std::mutex mutex; - Timer timer; - std::vector deepestSubcaseStackNames; - - struct JUnitTestCaseData - { - static std::string getCurrentTimestamp() { - // Beware, this is not reentrant because of backward compatibility issues - // Also, UTC only, again because of backward compatibility (%z is C++11) - time_t rawtime; - std::time(&rawtime); - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - - std::tm timeInfo; -#ifdef DOCTEST_PLATFORM_WINDOWS - gmtime_s(&timeInfo, &rawtime); -#else // DOCTEST_PLATFORM_WINDOWS - gmtime_r(&rawtime, &timeInfo); -#endif // DOCTEST_PLATFORM_WINDOWS - - char timeStamp[timeStampSize]; - const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; - - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); - return std::string(timeStamp); - } - - struct JUnitTestMessage - { - JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) - : message(_message), type(_type), details(_details) {} - - JUnitTestMessage(const std::string& _message, const std::string& _details) - : message(_message), type(), details(_details) {} - - std::string message, type, details; - }; - - struct JUnitTestCase - { - JUnitTestCase(const std::string& _classname, const std::string& _name) - : classname(_classname), name(_name), time(0), failures() {} - - std::string classname, name; - double time; - std::vector failures, errors; - }; - - void add(const std::string& classname, const std::string& name) { - testcases.emplace_back(classname, name); - } - - void appendSubcaseNamesToLastTestcase(std::vector nameStack) { - for(auto& curr: nameStack) - if(curr.size()) - testcases.back().name += std::string("/") + curr.c_str(); - } - - void addTime(double time) { - if(time < 1e-4) - time = 0; - testcases.back().time = time; - totalSeconds += time; - } - - void addFailure(const std::string& message, const std::string& type, const std::string& details) { - testcases.back().failures.emplace_back(message, type, details); - ++totalFailures; - } - - void addError(const std::string& message, const std::string& details) { - testcases.back().errors.emplace_back(message, details); - ++totalErrors; - } - - std::vector testcases; - double totalSeconds = 0; - int totalErrors = 0, totalFailures = 0; - }; - - JUnitTestCaseData testCaseData; - - // caching pointers/references to objects of these types - safe to do - const ContextOptions& opt; - const TestCaseData* tc = nullptr; - - JUnitReporter(const ContextOptions& co) - : xml(*co.cout) - , opt(co) {} - - unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } - - // ========================================================================================= - // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE - // ========================================================================================= - - void report_query(const QueryData&) override {} - - void test_run_start() override {} - - void test_run_end(const TestRunStats& p) override { - // remove .exe extension - mainly to have the same output on UNIX and Windows - std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); -#ifdef DOCTEST_PLATFORM_WINDOWS - if(binary_name.rfind(".exe") != std::string::npos) - binary_name = binary_name.substr(0, binary_name.length() - 4); -#endif // DOCTEST_PLATFORM_WINDOWS - xml.startElement("testsuites"); - xml.startElement("testsuite").writeAttribute("name", binary_name) - .writeAttribute("errors", testCaseData.totalErrors) - .writeAttribute("failures", testCaseData.totalFailures) - .writeAttribute("tests", p.numAsserts); - if(opt.no_time_in_output == false) { - xml.writeAttribute("time", testCaseData.totalSeconds); - xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); - } - if(opt.no_version == false) - xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); - - for(const auto& testCase : testCaseData.testcases) { - xml.startElement("testcase") - .writeAttribute("classname", testCase.classname) - .writeAttribute("name", testCase.name); - if(opt.no_time_in_output == false) - xml.writeAttribute("time", testCase.time); - // This is not ideal, but it should be enough to mimic gtest's junit output. - xml.writeAttribute("status", "run"); - - for(const auto& failure : testCase.failures) { - xml.scopedElement("failure") - .writeAttribute("message", failure.message) - .writeAttribute("type", failure.type) - .writeText(failure.details, false); - } - - for(const auto& error : testCase.errors) { - xml.scopedElement("error") - .writeAttribute("message", error.message) - .writeText(error.details); - } - - xml.endElement(); - } - xml.endElement(); - xml.endElement(); - } - - void test_case_start(const TestCaseData& in) override { - testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); - timer.start(); - } - - void test_case_reenter(const TestCaseData& in) override { - testCaseData.addTime(timer.getElapsedSeconds()); - testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); - deepestSubcaseStackNames.clear(); - - timer.start(); - testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); - } - - void test_case_end(const CurrentTestCaseStats&) override { - testCaseData.addTime(timer.getElapsedSeconds()); - testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); - deepestSubcaseStackNames.clear(); - } - - void test_case_exception(const TestCaseException& e) override { - std::lock_guard lock(mutex); - testCaseData.addError("exception", e.error_string.c_str()); - } - - void subcase_start(const SubcaseSignature& in) override { - deepestSubcaseStackNames.push_back(in.m_name); - } - - void subcase_end() override {} - - void log_assert(const AssertData& rb) override { - if(!rb.m_failed) // report only failures & ignore the `success` option - return; - - std::lock_guard lock(mutex); - - std::ostringstream os; - os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") - << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; - - fulltext_log_assert_to_stream(os, rb); - log_contexts(os); - testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); - } - - void log_message(const MessageData&) override {} - - void test_case_skipped(const TestCaseData&) override {} - - void log_contexts(std::ostringstream& s) { - int num_contexts = get_num_active_contexts(); - if(num_contexts) { - auto contexts = get_active_contexts(); - - s << " logged: "; - for(int i = 0; i < num_contexts; ++i) { - s << (i == 0 ? "" : " "); - contexts[i]->stringify(&s); - s << std::endl; - } - } - } - }; - - DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); - - struct Whitespace - { - int nrSpaces; - explicit Whitespace(int nr) - : nrSpaces(nr) {} - }; - - std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { - if(ws.nrSpaces != 0) - out << std::setw(ws.nrSpaces) << ' '; - return out; - } - - struct ConsoleReporter : public IReporter - { - std::ostream& s; - bool hasLoggedCurrentTestStart; - std::vector subcasesStack; - size_t currentSubcaseLevel; - std::mutex mutex; - - // caching pointers/references to objects of these types - safe to do - const ContextOptions& opt; - const TestCaseData* tc; - - ConsoleReporter(const ContextOptions& co) - : s(*co.cout) - , opt(co) {} - - ConsoleReporter(const ContextOptions& co, std::ostream& ostr) - : s(ostr) - , opt(co) {} - - // ========================================================================================= - // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE - // ========================================================================================= - - void separator_to_stream() { - s << Color::Yellow - << "===============================================================================" - "\n"; - } - - const char* getSuccessOrFailString(bool success, assertType::Enum at, - const char* success_str) { - if(success) - return success_str; - return failureString(at); - } - - Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { - return success ? Color::BrightGreen : - (at & assertType::is_warn) ? Color::Yellow : Color::Red; - } - - void successOrFailColoredStringToStream(bool success, assertType::Enum at, - const char* success_str = "SUCCESS") { - s << getSuccessOrFailColor(success, at) - << getSuccessOrFailString(success, at, success_str) << ": "; - } - - void log_contexts() { - int num_contexts = get_num_active_contexts(); - if(num_contexts) { - auto contexts = get_active_contexts(); - - s << Color::None << " logged: "; - for(int i = 0; i < num_contexts; ++i) { - s << (i == 0 ? "" : " "); - contexts[i]->stringify(&s); - s << "\n"; - } - } - - s << "\n"; - } - - // this was requested to be made virtual so users could override it - virtual void file_line_to_stream(const char* file, int line, - const char* tail = "") { - s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") - << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option - << (opt.gnu_file_line ? ":" : "):") << tail; - } - - void logTestStart() { - if(hasLoggedCurrentTestStart) - return; - - separator_to_stream(); - file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); - if(tc->m_description) - s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; - if(tc->m_test_suite && tc->m_test_suite[0] != '\0') - s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; - if(strncmp(tc->m_name, " Scenario:", 11) != 0) - s << Color::Yellow << "TEST CASE: "; - s << Color::None << tc->m_name << "\n"; - - for(size_t i = 0; i < currentSubcaseLevel; ++i) { - if(subcasesStack[i].m_name[0] != '\0') - s << " " << subcasesStack[i].m_name << "\n"; - } - - if(currentSubcaseLevel != subcasesStack.size()) { - s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; - for(size_t i = 0; i < subcasesStack.size(); ++i) { - if(subcasesStack[i].m_name[0] != '\0') - s << " " << subcasesStack[i].m_name << "\n"; - } - } - - s << "\n"; - - hasLoggedCurrentTestStart = true; - } - - void printVersion() { - if(opt.no_version == false) - s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" - << DOCTEST_VERSION_STR << "\"\n"; - } - - void printIntro() { - if(opt.no_intro == false) { - printVersion(); - s << Color::Cyan << "[doctest] " << Color::None - << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; - } - } - - void printHelp() { - int sizePrefixDisplay = static_cast(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); - printVersion(); - // clang-format off - s << Color::Cyan << "[doctest]\n" << Color::None; - s << Color::Cyan << "[doctest] " << Color::None; - s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; - s << Color::Cyan << "[doctest] " << Color::None; - s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; - s << Color::Cyan << "[doctest]\n" << Color::None; - s << Color::Cyan << "[doctest] " << Color::None; - s << "filters use wildcards for matching strings\n"; - s << Color::Cyan << "[doctest] " << Color::None; - s << "something passes a filter if any of the strings in a filter matches\n"; -#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - s << Color::Cyan << "[doctest]\n" << Color::None; - s << Color::Cyan << "[doctest] " << Color::None; - s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; -#endif - s << Color::Cyan << "[doctest]\n" << Color::None; - s << Color::Cyan << "[doctest] " << Color::None; - s << "Query flags - the program quits after them. Available:\n\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " - << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " - << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " - << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " - << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " - << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " - << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; - // ================================================================================== << 79 - s << Color::Cyan << "[doctest] " << Color::None; - s << "The available / options/filters are:\n\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case= " - << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude= " - << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file= " - << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude= " - << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite= " - << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude= " - << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase= " - << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude= " - << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters= " - << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " - << Whitespace(sizePrefixDisplay*1) << "output filename\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " - << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; - s << Whitespace(sizePrefixDisplay*3) << " - [file/suite/name/rand/none]\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " - << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " - << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; - s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last= " - << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; - s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after= " - << Whitespace(sizePrefixDisplay*1) << "stop after failed assertions\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels= " - << Whitespace(sizePrefixDisplay*1) << "apply filters for the first levels\n"; - s << Color::Cyan << "\n[doctest] " << Color::None; - s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success= " - << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive= " - << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit= " - << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " - << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal= " - << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet= " - << Whitespace(sizePrefixDisplay*1) << "no console output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " - << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " - << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " - << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro= " - << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " - << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " - << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors= " - << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks= " - << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip= " - << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line= " - << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames= " - << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers= " - << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; - // ================================================================================== << 79 - // clang-format on - - s << Color::Cyan << "\n[doctest] " << Color::None; - s << "for more information visit the project documentation\n\n"; - } - - void printRegisteredReporters() { - printVersion(); - auto printReporters = [this] (const reporterMap& reporters, const char* type) { - if(reporters.size()) { - s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; - for(auto& curr : reporters) - s << "priority: " << std::setw(5) << curr.first.first - << " name: " << curr.first.second << "\n"; - } - }; - printReporters(getListeners(), "listeners"); - printReporters(getReporters(), "reporters"); - } - - // ========================================================================================= - // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE - // ========================================================================================= - - void report_query(const QueryData& in) override { - if(opt.version) { - printVersion(); - } else if(opt.help) { - printHelp(); - } else if(opt.list_reporters) { - printRegisteredReporters(); - } else if(opt.count || opt.list_test_cases) { - if(opt.list_test_cases) { - s << Color::Cyan << "[doctest] " << Color::None - << "listing all test case names\n"; - separator_to_stream(); - } - - for(unsigned i = 0; i < in.num_data; ++i) - s << Color::None << in.data[i]->m_name << "\n"; - - separator_to_stream(); - - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - - } else if(opt.list_test_suites) { - s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; - separator_to_stream(); - - for(unsigned i = 0; i < in.num_data; ++i) - s << Color::None << in.data[i]->m_test_suite << "\n"; - - separator_to_stream(); - - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - s << Color::Cyan << "[doctest] " << Color::None - << "test suites with unskipped test cases passing the current filters: " - << g_cs->numTestSuitesPassingFilters << "\n"; - } - } - - void test_run_start() override { - if(!opt.minimal) - printIntro(); - } - - void test_run_end(const TestRunStats& p) override { - if(opt.minimal && p.numTestCasesFailed == 0) - return; - - separator_to_stream(); - s << std::dec; - - auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast(p.numAsserts))) + 1))); - auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast(p.numAsserts - p.numAssertsFailed))) + 1))); - auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast(p.numAssertsFailed))) + 1))); - const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; - s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) - << p.numTestCasesPassingFilters << " | " - << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : - Color::Green) - << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" - << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) - << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |"; - if(opt.no_skipped_summary == false) { - const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; - s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped - << " skipped" << Color::None; - } - s << "\n"; - s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth) - << p.numAsserts << " | " - << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) - << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None - << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) - << p.numAssertsFailed << " failed" << Color::None << " |\n"; - s << Color::Cyan << "[doctest] " << Color::None - << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) - << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; - } - - void test_case_start(const TestCaseData& in) override { - hasLoggedCurrentTestStart = false; - tc = ∈ - subcasesStack.clear(); - currentSubcaseLevel = 0; - } - - void test_case_reenter(const TestCaseData&) override { - subcasesStack.clear(); - } - - void test_case_end(const CurrentTestCaseStats& st) override { - if(tc->m_no_output) - return; - - // log the preamble of the test case only if there is something - // else to print - something other than that an assert has failed - if(opt.duration || - (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) - logTestStart(); - - if(opt.duration) - s << Color::None << std::setprecision(6) << std::fixed << st.seconds - << " s: " << tc->m_name << "\n"; - - if(st.failure_flags & TestCaseFailureReason::Timeout) - s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) - << std::fixed << tc->m_timeout << "!\n"; - - if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { - s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; - } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { - s << Color::Yellow << "Failed as expected so marking it as not failed\n"; - } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { - s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; - } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { - s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures - << " times so marking it as failed!\n"; - } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { - s << Color::Yellow << "Failed exactly " << tc->m_expected_failures - << " times as expected so marking it as not failed!\n"; - } - if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { - s << Color::Red << "Aborting - too many failed asserts!\n"; - } - s << Color::None; // lgtm [cpp/useless-expression] - } - - void test_case_exception(const TestCaseException& e) override { - std::lock_guard lock(mutex); - if(tc->m_no_output) - return; - - logTestStart(); - - file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); - successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : - assertType::is_check); - s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") - << Color::Cyan << e.error_string << "\n"; - - int num_stringified_contexts = get_num_stringified_contexts(); - if(num_stringified_contexts) { - auto stringified_contexts = get_stringified_contexts(); - s << Color::None << " logged: "; - for(int i = num_stringified_contexts; i > 0; --i) { - s << (i == num_stringified_contexts ? "" : " ") - << stringified_contexts[i - 1] << "\n"; - } - } - s << "\n" << Color::None; - } - - void subcase_start(const SubcaseSignature& subc) override { - subcasesStack.push_back(subc); - ++currentSubcaseLevel; - hasLoggedCurrentTestStart = false; - } - - void subcase_end() override { - --currentSubcaseLevel; - hasLoggedCurrentTestStart = false; - } - - void log_assert(const AssertData& rb) override { - if((!rb.m_failed && !opt.success) || tc->m_no_output) - return; - - std::lock_guard lock(mutex); - - logTestStart(); - - file_line_to_stream(rb.m_file, rb.m_line, " "); - successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); - - fulltext_log_assert_to_stream(s, rb); - - log_contexts(); - } - - void log_message(const MessageData& mb) override { - if(tc->m_no_output) - return; - - std::lock_guard lock(mutex); - - logTestStart(); - - file_line_to_stream(mb.m_file, mb.m_line, " "); - s << getSuccessOrFailColor(false, mb.m_severity) - << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, - "MESSAGE") << ": "; - s << Color::None << mb.m_string << "\n"; - log_contexts(); - } - - void test_case_skipped(const TestCaseData&) override {} - }; - - DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); - -#ifdef DOCTEST_PLATFORM_WINDOWS - struct DebugOutputWindowReporter : public ConsoleReporter - { - DOCTEST_THREAD_LOCAL static std::ostringstream oss; - - DebugOutputWindowReporter(const ContextOptions& co) - : ConsoleReporter(co, oss) {} - -#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ - void func(type arg) override { \ - bool with_col = g_no_colors; \ - g_no_colors = false; \ - ConsoleReporter::func(arg); \ - if(oss.tellp() != std::streampos{}) { \ - DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ - oss.str(""); \ - } \ - g_no_colors = with_col; \ - } - - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) - }; - - DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; -#endif // DOCTEST_PLATFORM_WINDOWS - - // the implementation of parseOption() - bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { - // going from the end to the beginning and stopping on the first occurrence from the end - for(int i = argc; i > 0; --i) { - auto index = i - 1; - auto temp = std::strstr(argv[index], pattern); - if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue - // eliminate matches in which the chars before the option are not '-' - bool noBadCharsFound = true; - auto curr = argv[index]; - while(curr != temp) { - if(*curr++ != '-') { - noBadCharsFound = false; - break; - } - } - if(noBadCharsFound && argv[index][0] == '-') { - if(value) { - // parsing the value of an option - temp += strlen(pattern); - const unsigned len = strlen(temp); - if(len) { - *value = temp; - return true; - } - } else { - // just a flag - no value - return true; - } - } - } - } - return false; - } - - // parses an option and returns the string after the '=' character - bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, - const String& defaultVal = String()) { - if(value) - *value = defaultVal; -#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - // offset (normally 3 for "dt-") to skip prefix - if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) - return true; -#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - return parseOptionImpl(argc, argv, pattern, value); - } - - // locates a flag on the command line - bool parseFlag(int argc, const char* const* argv, const char* pattern) { - return parseOption(argc, argv, pattern); - } - - // parses a comma separated list of words after a pattern in one of the arguments in argv - bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, - std::vector& res) { - String filtersString; - if(parseOption(argc, argv, pattern, &filtersString)) { - // tokenize with "," as a separator, unless escaped with backslash - std::ostringstream s; - auto flush = [&s, &res]() { - auto string = s.str(); - if(string.size() > 0) { - res.push_back(string.c_str()); - } - s.str(""); - }; - - bool seenBackslash = false; - const char* current = filtersString.c_str(); - const char* end = current + strlen(current); - while(current != end) { - char character = *current++; - if(seenBackslash) { - seenBackslash = false; - if(character == ',') { - s.put(','); - continue; - } - s.put('\\'); - } - if(character == '\\') { - seenBackslash = true; - } else if(character == ',') { - flush(); - } else { - s.put(character); - } - } - - if(seenBackslash) { - s.put('\\'); - } - flush(); - return true; - } - return false; - } - - enum optionType - { - option_bool, - option_int - }; - - // parses an int/bool option from the command line - bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, - int& res) { - String parsedValue; - if(!parseOption(argc, argv, pattern, &parsedValue)) - return false; - - if(type == 0) { - // boolean - const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 - const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 - - // if the value matches any of the positive/negative possibilities - for(unsigned i = 0; i < 4; i++) { - if(parsedValue.compare(positive[i], true) == 0) { - res = 1; //!OCLINT parameter reassignment - return true; - } - if(parsedValue.compare(negative[i], true) == 0) { - res = 0; //!OCLINT parameter reassignment - return true; - } - } - } else { - // integer - // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... - int theInt = std::atoi(parsedValue.c_str()); // NOLINT - if(theInt != 0) { - res = theInt; //!OCLINT parameter reassignment - return true; - } - } - return false; - } -} // namespace - -Context::Context(int argc, const char* const* argv) - : p(new detail::ContextState) { - parseArgs(argc, argv, true); - if(argc) - p->binary_name = argv[0]; -} - -Context::~Context() { - if(g_cs == p) - g_cs = nullptr; - delete p; -} - -void Context::applyCommandLine(int argc, const char* const* argv) { - parseArgs(argc, argv); - if(argc) - p->binary_name = argv[0]; -} - -// parses args -void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { - using namespace detail; - - // clang-format off - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); - // clang-format on - - int intRes = 0; - String strRes; - -#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ - if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ - parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ - p->var = static_cast(intRes); \ - else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ - p->var = true; \ - else if(withDefaults) \ - p->var = default - -#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ - if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ - parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ - p->var = intRes; \ - else if(withDefaults) \ - p->var = default - -#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ - if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \ - parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \ - withDefaults) \ - p->var = strRes - - // clang-format off - DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); - DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); - DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); - - DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); - DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); - - DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); - DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); - - DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); - // clang-format on - - if(withDefaults) { - p->help = false; - p->version = false; - p->count = false; - p->list_test_cases = false; - p->list_test_suites = false; - p->list_reporters = false; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { - p->help = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { - p->version = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { - p->count = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { - p->list_test_cases = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { - p->list_test_suites = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { - p->list_reporters = true; - p->exit = true; - } -} - -// allows the user to add procedurally to the filters from the command line -void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } - -// allows the user to clear all filters from the command line -void Context::clearFilters() { - for(auto& curr : p->filters) - curr.clear(); -} - -// allows the user to override procedurally the bool options from the command line -void Context::setOption(const char* option, bool value) { - setOption(option, value ? "true" : "false"); -} - -// allows the user to override procedurally the int options from the command line -void Context::setOption(const char* option, int value) { - setOption(option, toString(value).c_str()); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -} - -// allows the user to override procedurally the string options from the command line -void Context::setOption(const char* option, const char* value) { - auto argv = String("-") + option + "=" + value; - auto lvalue = argv.c_str(); - parseArgs(1, &lvalue); -} - -// users should query this in their main() and exit the program if true -bool Context::shouldExit() { return p->exit; } - -void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } - -void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } - -void Context::setCout(std::ostream* out) { p->cout = out; } - -static class DiscardOStream : public std::ostream -{ -private: - class : public std::streambuf - { - private: - // allowing some buffering decreases the amount of calls to overflow - char buf[1024]; - - protected: - std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; } - - int_type overflow(int_type ch) override { - setp(std::begin(buf), std::end(buf)); - return traits_type::not_eof(ch); - } - } discardBuf; - -public: - DiscardOStream() - : std::ostream(&discardBuf) {} -} discardOut; - -// the main function that does all the filtering and test running -int Context::run() { - using namespace detail; - - // save the old context state in case such was setup - for using asserts out of a testing context - auto old_cs = g_cs; - // this is the current contest - g_cs = p; - is_running_in_test = true; - - g_no_colors = p->no_colors; - p->resetRunData(); - - std::fstream fstr; - if(p->cout == nullptr) { - if(p->quiet) { - p->cout = &discardOut; - } else if(p->out.size()) { - // to a file if specified - fstr.open(p->out.c_str(), std::fstream::out); - p->cout = &fstr; - } else { - // stdout by default - p->cout = &std::cout; - } - } - - FatalConditionHandler::allocateAltStackMem(); - - auto cleanup_and_return = [&]() { - FatalConditionHandler::freeAltStackMem(); - - if(fstr.is_open()) - fstr.close(); - - // restore context - g_cs = old_cs; - is_running_in_test = false; - - // we have to free the reporters which were allocated when the run started - for(auto& curr : p->reporters_currently_used) - delete curr; - p->reporters_currently_used.clear(); - - if(p->numTestCasesFailed && !p->no_exitcode) - return EXIT_FAILURE; - return EXIT_SUCCESS; - }; - - // setup default reporter if none is given through the command line - if(p->filters[8].empty()) - p->filters[8].push_back("console"); - - // check to see if any of the registered reporters has been selected - for(auto& curr : getReporters()) { - if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) - p->reporters_currently_used.push_back(curr.second(*g_cs)); - } - - // TODO: check if there is nothing in reporters_currently_used - - // prepend all listeners - for(auto& curr : getListeners()) - p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); - -#ifdef DOCTEST_PLATFORM_WINDOWS - if(isDebuggerActive() && p->no_debug_output == false) - p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); -#endif // DOCTEST_PLATFORM_WINDOWS - - // handle version, help and no_run - if(p->no_run || p->version || p->help || p->list_reporters) { - DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); - - return cleanup_and_return(); - } - - std::vector testArray; - for(auto& curr : getRegisteredTests()) - testArray.push_back(&curr); - p->numTestCases = testArray.size(); - - // sort the collected records - if(!testArray.empty()) { - if(p->order_by.compare("file", true) == 0) { - std::sort(testArray.begin(), testArray.end(), fileOrderComparator); - } else if(p->order_by.compare("suite", true) == 0) { - std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); - } else if(p->order_by.compare("name", true) == 0) { - std::sort(testArray.begin(), testArray.end(), nameOrderComparator); - } else if(p->order_by.compare("rand", true) == 0) { - std::srand(p->rand_seed); - - // random_shuffle implementation - const auto first = &testArray[0]; - for(size_t i = testArray.size() - 1; i > 0; --i) { - int idxToSwap = std::rand() % (i + 1); // NOLINT - - const auto temp = first[i]; - - first[i] = first[idxToSwap]; - first[idxToSwap] = temp; - } - } else if(p->order_by.compare("none", true) == 0) { - // means no sorting - beneficial for death tests which call into the executable - // with a specific test case in mind - we don't want to slow down the startup times - } - } - - std::set testSuitesPassingFilt; - - bool query_mode = p->count || p->list_test_cases || p->list_test_suites; - std::vector queryResults; - - if(!query_mode) - DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); - - // invoke the registered functions if they match the filter criteria (or just count them) - for(auto& curr : testArray) { - const auto& tc = *curr; - - bool skip_me = false; - if(tc.m_skip && !p->no_skip) - skip_me = true; - - if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) - skip_me = true; - if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) - skip_me = true; - if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) - skip_me = true; - if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) - skip_me = true; - if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) - skip_me = true; - if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) - skip_me = true; - - if(!skip_me) - p->numTestCasesPassingFilters++; - - // skip the test if it is not in the execution range - if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || - (p->first > p->numTestCasesPassingFilters)) - skip_me = true; - - if(skip_me) { - if(!query_mode) - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); - continue; - } - - // do not execute the test if we are to only count the number of filter passing tests - if(p->count) - continue; - - // print the name of the test and don't execute it - if(p->list_test_cases) { - queryResults.push_back(&tc); - continue; - } - - // print the name of the test suite if not done already and don't execute it - if(p->list_test_suites) { - if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { - queryResults.push_back(&tc); - testSuitesPassingFilt.insert(tc.m_test_suite); - p->numTestSuitesPassingFilters++; - } - continue; - } - - // execute the test if it passes all the filtering - { - p->currentTest = &tc; - - p->failure_flags = TestCaseFailureReason::None; - p->seconds = 0; - - // reset atomic counters - p->numAssertsFailedCurrentTest_atomic = 0; - p->numAssertsCurrentTest_atomic = 0; - - p->subcasesPassed.clear(); - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); - - p->timer.start(); - - bool run_test = true; - - do { - // reset some of the fields for subcases (except for the set of fully passed ones) - p->should_reenter = false; - p->subcasesCurrentMaxLevel = 0; - p->subcasesStack.clear(); - - p->shouldLogCurrentException = true; - - // reset stuff for logging with INFO() - p->stringifiedContexts.clear(); - -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - try { -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS -// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method) -DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable - FatalConditionHandler fatalConditionHandler; // Handle signals - // execute the test - tc.m_test(); - fatalConditionHandler.reset(); -DOCTEST_MSVC_SUPPRESS_WARNING_POP -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - } catch(const TestFailureException&) { - p->failure_flags |= TestCaseFailureReason::AssertFailure; - } catch(...) { - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, - {translateActiveException(), false}); - p->failure_flags |= TestCaseFailureReason::Exception; - } -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - - // exit this loop if enough assertions have failed - even if there are more subcases - if(p->abort_after > 0 && - p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { - run_test = false; - p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; - } - - if(p->should_reenter && run_test) - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); - if(!p->should_reenter) - run_test = false; - } while(run_test); - - p->finalizeTestCaseData(); - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); - - p->currentTest = nullptr; - - // stop executing tests if enough assertions have failed - if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) - break; - } - } - - if(!query_mode) { - DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); - } else { - QueryData qdata; - qdata.run_stats = g_cs; - qdata.data = queryResults.data(); - qdata.num_data = unsigned(queryResults.size()); - DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); - } - - return cleanup_and_return(); -} - -IReporter::~IReporter() = default; - -int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } -const IContextScope* const* IReporter::get_active_contexts() { - return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; -} - -int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } -const String* IReporter::get_stringified_contexts() { - return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; -} - -namespace detail { - void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { - if(isReporter) - getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); - else - getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); - } -} // namespace detail - -} // namespace doctest - -#endif // DOCTEST_CONFIG_DISABLE - -#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 -int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } -DOCTEST_MSVC_SUPPRESS_WARNING_POP -#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN - -DOCTEST_CLANG_SUPPRESS_WARNING_POP -DOCTEST_MSVC_SUPPRESS_WARNING_POP -DOCTEST_GCC_SUPPRESS_WARNING_POP - -DOCTEST_SUPPRESS_COMMON_WARNINGS_POP - -#endif // DOCTEST_LIBRARY_IMPLEMENTATION -#endif // DOCTEST_CONFIG_IMPLEMENT diff --git a/binding-tests/replica.cpp b/binding-tests/replica.cpp deleted file mode 100644 index 18364a653..000000000 --- a/binding-tests/replica.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include "doctest.h" -#include "taskchampion.h" - -TEST_CASE("creating an in-memory TCReplica does not crash") { - TCReplica *rep = tc_replica_new(NULL); - CHECK(tc_replica_error(rep) == NULL); - tc_replica_free(rep); -} - -TEST_CASE("creating an on-disk TCReplica does not crash") { - TCReplica *rep = tc_replica_new(tc_string_new("test-db")); - CHECK(tc_replica_error(rep) == NULL); - tc_replica_free(rep); -} - -TEST_CASE("undo on an empty in-memory TCReplica does nothing") { - TCReplica *rep = tc_replica_new(NULL); - CHECK(tc_replica_error(rep) == NULL); - int rv = tc_replica_undo(rep); - CHECK(rv == 0); - CHECK(tc_replica_error(rep) == NULL); - tc_replica_free(rep); -} diff --git a/binding-tests/string.cpp b/binding-tests/string.cpp deleted file mode 100644 index 8b743b266..000000000 --- a/binding-tests/string.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include "doctest.h" -#include "taskchampion.h" - -TEST_CASE("creating borrowed strings does not crash") { - TCString *s = tc_string_new("abcdef"); - tc_string_free(s); -} - -TEST_CASE("creating cloned strings does not crash") { - char *abcdef = strdup("abcdef"); - TCString *s = tc_string_clone(abcdef); - REQUIRE(s != NULL); - free(abcdef); - - CHECK(strcmp(tc_string_content(s), "abcdef") == 0); - tc_string_free(s); -} - -TEST_CASE("borrowed strings echo back their content") { - TCString *s = tc_string_new("abcdef"); - REQUIRE(s != NULL); - - CHECK(strcmp(tc_string_content(s), "abcdef") == 0); - size_t len; - const char *buf = tc_string_content_with_len(s, &len); - REQUIRE(buf != NULL); - CHECK(len == 6); - CHECK(strncmp(buf, "abcdef", len) == 0); - tc_string_free(s); -} - -TEST_CASE("cloned strings echo back their content") { - char *orig = strdup("abcdef"); - TCString *s = tc_string_clone(orig); - REQUIRE(s != NULL); - free(orig); - - CHECK(strcmp(tc_string_content(s), "abcdef") == 0); - - size_t len; - const char *buf = tc_string_content_with_len(s, &len); - REQUIRE(buf != NULL); - CHECK(len == 6); - CHECK(strncmp(buf, "abcdef", len) == 0); - tc_string_free(s); -} - -TEST_CASE("tc_string_content returns NULL for strings containing embedded NULs") { - TCString *s = tc_string_clone_with_len("ab\0de", 5); - REQUIRE(s != NULL); - - CHECK(tc_string_content(s) == NULL); - - size_t len; - const char *buf = tc_string_content_with_len(s, &len); - REQUIRE(buf != NULL); - CHECK(len == 5); - CHECK(strncmp(buf, "ab\0de", len) == 0); - tc_string_free(s); -} diff --git a/binding-tests/task.cpp b/binding-tests/task.cpp deleted file mode 100644 index 9d82767ca..000000000 --- a/binding-tests/task.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include "doctest.h" -#include "taskchampion.h" - -TEST_CASE("creating a Task does not crash") { - TCReplica *rep = tc_replica_new(NULL); - CHECK(tc_replica_error(rep) == NULL); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_new("my task")); - REQUIRE(task != NULL); - - CHECK(tc_task_get_status(task) == TC_STATUS_PENDING); - - TCString *desc = tc_task_get_description(task); - REQUIRE(desc != NULL); - CHECK(strcmp(tc_string_content(desc), "my task") == 0); - tc_string_free(desc); - - tc_task_free(task); - - tc_replica_free(rep); -} diff --git a/binding-tests/uuid.cpp b/binding-tests/uuid.cpp deleted file mode 100644 index aba07d867..000000000 --- a/binding-tests/uuid.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include "doctest.h" -#include "taskchampion.h" - -TEST_CASE("creating UUIDs does not crash") { - TCUuid u1 = tc_uuid_new_v4(); - TCUuid u2 = tc_uuid_nil(); -} - -TEST_CASE("converting UUIDs to string works") { - TCUuid u2 = tc_uuid_nil(); - REQUIRE(TC_UUID_STRING_BYTES == 36); - - char u2str[TC_UUID_STRING_BYTES]; - tc_uuid_to_str(u2, u2str); - CHECK(strncmp(u2str, "00000000-0000-0000-0000-000000000000", TC_UUID_STRING_BYTES) == 0); -} - -TEST_CASE("converting UUIDs from string works") { - TCUuid u; - char ustr[TC_UUID_STRING_BYTES] = "fdc314b7-f938-4845-b8d1-95716e4eb762"; - CHECK(tc_uuid_from_str(ustr, &u)); - CHECK(u.bytes[0] == 0xfd); - // .. if these two bytes are correct, then it probably worked :) - CHECK(u.bytes[15] == 0x62); -} - -TEST_CASE("converting invalid UUIDs from string fails as expected") { - TCUuid u; - char ustr[TC_UUID_STRING_BYTES] = "not-a-valid-uuid"; - CHECK(!tc_uuid_from_str(ustr, &u)); -} - -TEST_CASE("converting invalid UTF-8 UUIDs from string fails as expected") { - TCUuid u; - char ustr[TC_UUID_STRING_BYTES] = "\xf0\x28\x8c\xbc"; - CHECK(!tc_uuid_from_str(ustr, &u)); -} diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml new file mode 100644 index 000000000..f351e17d3 --- /dev/null +++ b/integration-tests/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "integration-tests" +version = "0.4.1" +authors = ["Dustin J. Mitchell "] +edition = "2018" +publish = false +build = "build.rs" + +[dependencies] +taskchampion = { path = "../taskchampion" } + +[build-dependencies] +cc = "1.0" +taskchampion-lib = { path = "../lib" } diff --git a/integration-tests/README.md b/integration-tests/README.md new file mode 100644 index 000000000..21ca29c3b --- /dev/null +++ b/integration-tests/README.md @@ -0,0 +1,32 @@ +# Integration Tests for Taskchampion + +## "Regular" Tests + +Some of the tests in `tests/` are just regular integration tests. +Nothing exciting to see. + +## Bindings Tests + +The bindings tests are a bit more interesting, since they are written in C. +They are composed of a collection of "suites", each in one C file in `integration-tests/src/bindings_tests/`. +Each suite contains a number of tests (using [Unity](http://www.throwtheswitch.org/unity)) and an exported function named after the suite that returns an exit status (1 = failure). + +The build script (`integration-tests/build.rs`) builds these files into a library that is linked with the `integration_tests` library crate. +This crate contains a `bindings_tests` module with a pub function for each suite. + +Finally, the `integration-tests/tests/bindings.rs` test file calls each of those functions in a separate test case. + +### Adding Tests + +To add a test, select a suite and add a new test-case function. +Add a `RUN_TEST` invocation for your new function to the `.._tests` function at the bottom. +Keep the `RUN_TEST`s in the same order as the functions they call. + +### Adding Suites + +To add a suite, + +1. Add a new C file in `integration-tests/src/bindings_tests/`. +1. Add a new `.file(..)` to build that file in `integration-tests/build.rs`. +1. Add a `suite!(..)` to `integration-tests/src/bindings_tests/mod.rs`. +1. Add a `suite!(..)` to `integration-tests/tests/bindings.rs`. diff --git a/integration-tests/build.rs b/integration-tests/build.rs new file mode 100644 index 000000000..78c531024 --- /dev/null +++ b/integration-tests/build.rs @@ -0,0 +1,36 @@ +fn main() { + // This crate has taskchampion-lib in its build-dependencies, so + // libtaskchampion.so should be built already. Hopefully it's in target/$PROFILE, and hopefully + // it's named libtaskchampion.so and not something else + let mut libtaskchampion = std::env::current_dir().unwrap(); + libtaskchampion.pop(); + libtaskchampion.push("target"); + libtaskchampion.push(std::env::var("PROFILE").unwrap()); + libtaskchampion.push("deps"); + libtaskchampion.push("libtaskchampion.so"); + + println!("cargo:rerun-if-changed=build.rs"); + + let mut build = cc::Build::new(); + build.object(libtaskchampion); + build.include("../lib"); + build.include("src/bindings_tests/unity"); + build.file("src/bindings_tests/unity/unity.c"); + + let files = &[ + "src/bindings_tests/test.c", + // keep this list in sync with integration-tests/src/bindings_tests/mod.rs and + // integration-tests/tests/bindings.rs + "src/bindings_tests/uuid.c", + "src/bindings_tests/string.c", + "src/bindings_tests/task.c", + "src/bindings_tests/replica.c", + ]; + + for file in files { + build.file(file); + println!("cargo:rerun-if-changed={}", file); + } + + build.compile("bindings-tests"); +} diff --git a/integration-tests/src/bindings_tests/mod.rs b/integration-tests/src/bindings_tests/mod.rs new file mode 100644 index 000000000..a9cf8119e --- /dev/null +++ b/integration-tests/src/bindings_tests/mod.rs @@ -0,0 +1,20 @@ +// Each suite is represented by a _tests C function in .c. +// All of these C files are built into a library that is linked to the crate -- but not to test +// crates. So, this macro produces a "glue function" that calls the C function, and that can be +// called from test crates. +macro_rules! suite( + { $s:ident } => { + pub fn $s() -> i32 { + extern "C" { + fn $s() -> i32; + } + unsafe { $s() } + } + }; +); + +// keep this list in sync with integration-tests/build.rs and integration-tests/tests/bindings.rs. +suite!(uuid_tests); +suite!(string_tests); +suite!(task_tests); +suite!(replica_tests); diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c new file mode 100644 index 000000000..71eef0efe --- /dev/null +++ b/integration-tests/src/bindings_tests/replica.c @@ -0,0 +1,39 @@ +#include +#include +#include "unity.h" +#include "taskchampion.h" + +// creating an in-memory replica does not crash +static void test_replica_creation(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NOT_NULL(rep); + TEST_ASSERT_NULL(tc_replica_error(rep)); + tc_replica_free(rep); +} + +// creating an on-disk replica does not crash +static void test_replica_creation_disk(void) { + TCReplica *rep = tc_replica_new_on_disk(tc_string_new("test-db"), NULL); + TEST_ASSERT_NOT_NULL(rep); + TEST_ASSERT_NULL(tc_replica_error(rep)); + tc_replica_free(rep); +} + +// undo on an empty in-memory TCReplica does nothing +static void test_replica_undo_empty(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + int rv = tc_replica_undo(rep); + TEST_ASSERT_EQUAL(0, rv); + TEST_ASSERT_NULL(tc_replica_error(rep)); + tc_replica_free(rep); +} + +int replica_tests(void) { + UNITY_BEGIN(); + // each test case above should be named here, in order. + RUN_TEST(test_replica_creation); + RUN_TEST(test_replica_creation_disk); + RUN_TEST(test_replica_undo_empty); + return UNITY_END(); +} diff --git a/integration-tests/src/bindings_tests/string.c b/integration-tests/src/bindings_tests/string.c new file mode 100644 index 000000000..ad6c62559 --- /dev/null +++ b/integration-tests/src/bindings_tests/string.c @@ -0,0 +1,81 @@ +#include +#include +#include "unity.h" +#include "taskchampion.h" + +// creating strings does not crash +static void test_string_creation(void) { + TCString *s = tc_string_new("abcdef"); + tc_string_free(s); +} + +// creating cloned strings does not crash +static void test_string_cloning(void) { + char *abcdef = strdup("abcdef"); + TCString *s = tc_string_clone(abcdef); + TEST_ASSERT_NOT_NULL(s); + free(abcdef); + + TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(s)); + tc_string_free(s); +} + +// borrowed strings echo back their content +static void test_string_borrowed_strings_echo(void) { + TCString *s = tc_string_new("abcdef"); + TEST_ASSERT_NOT_NULL(s); + + TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(s)); + + size_t len; + const char *buf = tc_string_content_with_len(s, &len); + TEST_ASSERT_NOT_NULL(buf); + TEST_ASSERT_EQUAL(6, len); + TEST_ASSERT_EQUAL_MEMORY("abcdef", buf, len); + + tc_string_free(s); +} + +// cloned strings echo back their content +static void test_string_cloned_strings_echo(void) { + char *orig = strdup("abcdef"); + TCString *s = tc_string_clone(orig); + TEST_ASSERT_NOT_NULL(s); + free(orig); + + TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(s)); + + size_t len; + const char *buf = tc_string_content_with_len(s, &len); + TEST_ASSERT_NOT_NULL(buf); + TEST_ASSERT_EQUAL(6, len); + TEST_ASSERT_EQUAL_MEMORY("abcdef", buf, len); + + tc_string_free(s); +} + +// tc_string_content returns NULL for strings containing embedded NULs +static void test_string_content_null_for_embedded_nuls(void) { + TCString *s = tc_string_clone_with_len("ab\0de", 5); + TEST_ASSERT_NOT_NULL(s); + + TEST_ASSERT_NULL(tc_string_content(s)); + + size_t len; + const char *buf = tc_string_content_with_len(s, &len); + TEST_ASSERT_NOT_NULL(buf); + TEST_ASSERT_EQUAL(5, len); + TEST_ASSERT_EQUAL_MEMORY("ab\0de", buf, len); + tc_string_free(s); +} + +int string_tests(void) { + UNITY_BEGIN(); + // each test case above should be named here, in order. + RUN_TEST(test_string_creation); + RUN_TEST(test_string_cloning); + RUN_TEST(test_string_borrowed_strings_echo); + RUN_TEST(test_string_cloned_strings_echo); + RUN_TEST(test_string_content_null_for_embedded_nuls); + return UNITY_END(); +} diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c new file mode 100644 index 000000000..9f77b8170 --- /dev/null +++ b/integration-tests/src/bindings_tests/task.c @@ -0,0 +1,34 @@ +#include +#include +#include "unity.h" +#include "taskchampion.h" + +// creating a task succeeds and the resulting task looks good +static void test_task_creation(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_new("my task")); + TEST_ASSERT_NOT_NULL(task); + + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + + TCString *desc = tc_task_get_description(task); + TEST_ASSERT_NOT_NULL(desc); + TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(desc)); + tc_string_free(desc); + + tc_task_free(task); + + tc_replica_free(rep); +} + +int task_tests(void) { + UNITY_BEGIN(); + // each test case above should be named here, in order. + RUN_TEST(test_task_creation); + return UNITY_END(); +} diff --git a/integration-tests/src/bindings_tests/test.c b/integration-tests/src/bindings_tests/test.c new file mode 100644 index 000000000..609943fc4 --- /dev/null +++ b/integration-tests/src/bindings_tests/test.c @@ -0,0 +1,6 @@ +#include "unity.h" + +// these functions are shared between all test "suites" +// and cannot be customized per-suite. +void setUp(void) { } +void tearDown(void) { } diff --git a/integration-tests/src/bindings_tests/unity/LICENSE.txt b/integration-tests/src/bindings_tests/unity/LICENSE.txt new file mode 100644 index 000000000..b9a329dde --- /dev/null +++ b/integration-tests/src/bindings_tests/unity/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/integration-tests/src/bindings_tests/unity/README.md b/integration-tests/src/bindings_tests/unity/README.md new file mode 100644 index 000000000..6f755ced0 --- /dev/null +++ b/integration-tests/src/bindings_tests/unity/README.md @@ -0,0 +1,3 @@ +# Unity + +This directory contains the src from https://github.com/ThrowTheSwitch/Unity, revision 8ba01386008196a92ef4fdbdb0b00f2434c79563. diff --git a/integration-tests/src/bindings_tests/unity/unity.c b/integration-tests/src/bindings_tests/unity/unity.c new file mode 100644 index 000000000..b88024875 --- /dev/null +++ b/integration-tests/src/bindings_tests/unity/unity.c @@ -0,0 +1,2119 @@ +/* ========================================================================= + Unity Project - A Test Framework for C + Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams + [Released under MIT License. Please refer to license.txt for details] +============================================================================ */ + +#include "unity.h" +#include + +#ifdef AVR +#include +#else +#define PROGMEM +#endif + +/* If omitted from header, declare overrideable prototypes here so they're ready for use */ +#ifdef UNITY_OMIT_OUTPUT_CHAR_HEADER_DECLARATION +void UNITY_OUTPUT_CHAR(int); +#endif + +/* Helpful macros for us to use here in Assert functions */ +#define UNITY_FAIL_AND_BAIL do { Unity.CurrentTestFailed = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) +#define UNITY_IGNORE_AND_BAIL do { Unity.CurrentTestIgnored = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) +#define RETURN_IF_FAIL_OR_IGNORE do { if (Unity.CurrentTestFailed || Unity.CurrentTestIgnored) { TEST_ABORT(); } } while (0) + +struct UNITY_STORAGE_T Unity; + +#ifdef UNITY_OUTPUT_COLOR +const char PROGMEM UnityStrOk[] = "\033[42mOK\033[00m"; +const char PROGMEM UnityStrPass[] = "\033[42mPASS\033[00m"; +const char PROGMEM UnityStrFail[] = "\033[41mFAIL\033[00m"; +const char PROGMEM UnityStrIgnore[] = "\033[43mIGNORE\033[00m"; +#else +const char PROGMEM UnityStrOk[] = "OK"; +const char PROGMEM UnityStrPass[] = "PASS"; +const char PROGMEM UnityStrFail[] = "FAIL"; +const char PROGMEM UnityStrIgnore[] = "IGNORE"; +#endif +static const char PROGMEM UnityStrNull[] = "NULL"; +static const char PROGMEM UnityStrSpacer[] = ". "; +static const char PROGMEM UnityStrExpected[] = " Expected "; +static const char PROGMEM UnityStrWas[] = " Was "; +static const char PROGMEM UnityStrGt[] = " to be greater than "; +static const char PROGMEM UnityStrLt[] = " to be less than "; +static const char PROGMEM UnityStrOrEqual[] = "or equal to "; +static const char PROGMEM UnityStrNotEqual[] = " to be not equal to "; +static const char PROGMEM UnityStrElement[] = " Element "; +static const char PROGMEM UnityStrByte[] = " Byte "; +static const char PROGMEM UnityStrMemory[] = " Memory Mismatch."; +static const char PROGMEM UnityStrDelta[] = " Values Not Within Delta "; +static const char PROGMEM UnityStrPointless[] = " You Asked Me To Compare Nothing, Which Was Pointless."; +static const char PROGMEM UnityStrNullPointerForExpected[] = " Expected pointer to be NULL"; +static const char PROGMEM UnityStrNullPointerForActual[] = " Actual pointer was NULL"; +#ifndef UNITY_EXCLUDE_FLOAT +static const char PROGMEM UnityStrNot[] = "Not "; +static const char PROGMEM UnityStrInf[] = "Infinity"; +static const char PROGMEM UnityStrNegInf[] = "Negative Infinity"; +static const char PROGMEM UnityStrNaN[] = "NaN"; +static const char PROGMEM UnityStrDet[] = "Determinate"; +static const char PROGMEM UnityStrInvalidFloatTrait[] = "Invalid Float Trait"; +#endif +const char PROGMEM UnityStrErrShorthand[] = "Unity Shorthand Support Disabled"; +const char PROGMEM UnityStrErrFloat[] = "Unity Floating Point Disabled"; +const char PROGMEM UnityStrErrDouble[] = "Unity Double Precision Disabled"; +const char PROGMEM UnityStrErr64[] = "Unity 64-bit Support Disabled"; +static const char PROGMEM UnityStrBreaker[] = "-----------------------"; +static const char PROGMEM UnityStrResultsTests[] = " Tests "; +static const char PROGMEM UnityStrResultsFailures[] = " Failures "; +static const char PROGMEM UnityStrResultsIgnored[] = " Ignored "; +#ifndef UNITY_EXCLUDE_DETAILS +static const char PROGMEM UnityStrDetail1Name[] = UNITY_DETAIL1_NAME " "; +static const char PROGMEM UnityStrDetail2Name[] = " " UNITY_DETAIL2_NAME " "; +#endif +/*----------------------------------------------- + * Pretty Printers & Test Result Output Handlers + *-----------------------------------------------*/ + +/*-----------------------------------------------*/ +/* Local helper function to print characters. */ +static void UnityPrintChar(const char* pch) +{ + /* printable characters plus CR & LF are printed */ + if ((*pch <= 126) && (*pch >= 32)) + { + UNITY_OUTPUT_CHAR(*pch); + } + /* write escaped carriage returns */ + else if (*pch == 13) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('r'); + } + /* write escaped line feeds */ + else if (*pch == 10) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('n'); + } + /* unprintable characters are shown as codes */ + else + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)*pch, 2); + } +} + +/*-----------------------------------------------*/ +/* Local helper function to print ANSI escape strings e.g. "\033[42m". */ +#ifdef UNITY_OUTPUT_COLOR +static UNITY_UINT UnityPrintAnsiEscapeString(const char* string) +{ + const char* pch = string; + UNITY_UINT count = 0; + + while (*pch && (*pch != 'm')) + { + UNITY_OUTPUT_CHAR(*pch); + pch++; + count++; + } + UNITY_OUTPUT_CHAR('m'); + count++; + + return count; +} +#endif + +/*-----------------------------------------------*/ +void UnityPrint(const char* string) +{ + const char* pch = string; + + if (pch != NULL) + { + while (*pch) + { +#ifdef UNITY_OUTPUT_COLOR + /* print ANSI escape code */ + if ((*pch == 27) && (*(pch + 1) == '[')) + { + pch += UnityPrintAnsiEscapeString(pch); + continue; + } +#endif + UnityPrintChar(pch); + pch++; + } + } +} +/*-----------------------------------------------*/ +void UnityPrintLen(const char* string, const UNITY_UINT32 length) +{ + const char* pch = string; + + if (pch != NULL) + { + while (*pch && ((UNITY_UINT32)(pch - string) < length)) + { + /* printable characters plus CR & LF are printed */ + if ((*pch <= 126) && (*pch >= 32)) + { + UNITY_OUTPUT_CHAR(*pch); + } + /* write escaped carriage returns */ + else if (*pch == 13) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('r'); + } + /* write escaped line feeds */ + else if (*pch == 10) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('n'); + } + /* unprintable characters are shown as codes */ + else + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)*pch, 2); + } + pch++; + } + } +} + +/*-----------------------------------------------*/ +void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style) +{ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if (style == UNITY_DISPLAY_STYLE_CHAR) + { + /* printable characters plus CR & LF are printed */ + UNITY_OUTPUT_CHAR('\''); + if ((number <= 126) && (number >= 32)) + { + UNITY_OUTPUT_CHAR((int)number); + } + /* write escaped carriage returns */ + else if (number == 13) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('r'); + } + /* write escaped line feeds */ + else if (number == 10) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('n'); + } + /* unprintable characters are shown as codes */ + else + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)number, 2); + } + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrintNumber(number); + } + } + else if ((style & UNITY_DISPLAY_RANGE_UINT) == UNITY_DISPLAY_RANGE_UINT) + { + UnityPrintNumberUnsigned((UNITY_UINT)number); + } + else + { + UNITY_OUTPUT_CHAR('0'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)number, (char)((style & 0xF) * 2)); + } +} + +/*-----------------------------------------------*/ +void UnityPrintNumber(const UNITY_INT number_to_print) +{ + UNITY_UINT number = (UNITY_UINT)number_to_print; + + if (number_to_print < 0) + { + /* A negative number, including MIN negative */ + UNITY_OUTPUT_CHAR('-'); + number = (~number) + 1; + } + UnityPrintNumberUnsigned(number); +} + +/*----------------------------------------------- + * basically do an itoa using as little ram as possible */ +void UnityPrintNumberUnsigned(const UNITY_UINT number) +{ + UNITY_UINT divisor = 1; + + /* figure out initial divisor */ + while (number / divisor > 9) + { + divisor *= 10; + } + + /* now mod and print, then divide divisor */ + do + { + UNITY_OUTPUT_CHAR((char)('0' + (number / divisor % 10))); + divisor /= 10; + } while (divisor > 0); +} + +/*-----------------------------------------------*/ +void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print) +{ + int nibble; + char nibbles = nibbles_to_print; + + if ((unsigned)nibbles > UNITY_MAX_NIBBLES) + { + nibbles = UNITY_MAX_NIBBLES; + } + + while (nibbles > 0) + { + nibbles--; + nibble = (int)(number >> (nibbles * 4)) & 0x0F; + if (nibble <= 9) + { + UNITY_OUTPUT_CHAR((char)('0' + nibble)); + } + else + { + UNITY_OUTPUT_CHAR((char)('A' - 10 + nibble)); + } + } +} + +/*-----------------------------------------------*/ +void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number) +{ + UNITY_UINT current_bit = (UNITY_UINT)1 << (UNITY_INT_WIDTH - 1); + UNITY_INT32 i; + + for (i = 0; i < UNITY_INT_WIDTH; i++) + { + if (current_bit & mask) + { + if (current_bit & number) + { + UNITY_OUTPUT_CHAR('1'); + } + else + { + UNITY_OUTPUT_CHAR('0'); + } + } + else + { + UNITY_OUTPUT_CHAR('X'); + } + current_bit = current_bit >> 1; + } +} + +/*-----------------------------------------------*/ +#ifndef UNITY_EXCLUDE_FLOAT_PRINT +/* + * This function prints a floating-point value in a format similar to + * printf("%.7g") on a single-precision machine or printf("%.9g") on a + * double-precision machine. The 7th digit won't always be totally correct + * in single-precision operation (for that level of accuracy, a more + * complicated algorithm would be needed). + */ +void UnityPrintFloat(const UNITY_DOUBLE input_number) +{ +#ifdef UNITY_INCLUDE_DOUBLE + static const int sig_digits = 9; + static const UNITY_INT32 min_scaled = 100000000; + static const UNITY_INT32 max_scaled = 1000000000; +#else + static const int sig_digits = 7; + static const UNITY_INT32 min_scaled = 1000000; + static const UNITY_INT32 max_scaled = 10000000; +#endif + + UNITY_DOUBLE number = input_number; + + /* print minus sign (does not handle negative zero) */ + if (number < 0.0f) + { + UNITY_OUTPUT_CHAR('-'); + number = -number; + } + + /* handle zero, NaN, and +/- infinity */ + if (number == 0.0f) + { + UnityPrint("0"); + } + else if (isnan(number)) + { + UnityPrint("nan"); + } + else if (isinf(number)) + { + UnityPrint("inf"); + } + else + { + UNITY_INT32 n_int = 0; + UNITY_INT32 n; + int exponent = 0; + int decimals; + int digits; + char buf[16] = {0}; + + /* + * Scale up or down by powers of 10. To minimize rounding error, + * start with a factor/divisor of 10^10, which is the largest + * power of 10 that can be represented exactly. Finally, compute + * (exactly) the remaining power of 10 and perform one more + * multiplication or division. + */ + if (number < 1.0f) + { + UNITY_DOUBLE factor = 1.0f; + + while (number < (UNITY_DOUBLE)max_scaled / 1e10f) { number *= 1e10f; exponent -= 10; } + while (number * factor < (UNITY_DOUBLE)min_scaled) { factor *= 10.0f; exponent--; } + + number *= factor; + } + else if (number > (UNITY_DOUBLE)max_scaled) + { + UNITY_DOUBLE divisor = 1.0f; + + while (number > (UNITY_DOUBLE)min_scaled * 1e10f) { number /= 1e10f; exponent += 10; } + while (number / divisor > (UNITY_DOUBLE)max_scaled) { divisor *= 10.0f; exponent++; } + + number /= divisor; + } + else + { + /* + * In this range, we can split off the integer part before + * doing any multiplications. This reduces rounding error by + * freeing up significant bits in the fractional part. + */ + UNITY_DOUBLE factor = 1.0f; + n_int = (UNITY_INT32)number; + number -= (UNITY_DOUBLE)n_int; + + while (n_int < min_scaled) { n_int *= 10; factor *= 10.0f; exponent--; } + + number *= factor; + } + + /* round to nearest integer */ + n = ((UNITY_INT32)(number + number) + 1) / 2; + +#ifndef UNITY_ROUND_TIES_AWAY_FROM_ZERO + /* round to even if exactly between two integers */ + if ((n & 1) && (((UNITY_DOUBLE)n - number) == 0.5f)) + n--; +#endif + + n += n_int; + + if (n >= max_scaled) + { + n = min_scaled; + exponent++; + } + + /* determine where to place decimal point */ + decimals = ((exponent <= 0) && (exponent >= -(sig_digits + 3))) ? (-exponent) : (sig_digits - 1); + exponent += decimals; + + /* truncate trailing zeroes after decimal point */ + while ((decimals > 0) && ((n % 10) == 0)) + { + n /= 10; + decimals--; + } + + /* build up buffer in reverse order */ + digits = 0; + while ((n != 0) || (digits <= decimals)) + { + buf[digits++] = (char)('0' + n % 10); + n /= 10; + } + + /* print out buffer (backwards) */ + while (digits > 0) + { + if (digits == decimals) + { + UNITY_OUTPUT_CHAR('.'); + } + UNITY_OUTPUT_CHAR(buf[--digits]); + } + + /* print exponent if needed */ + if (exponent != 0) + { + UNITY_OUTPUT_CHAR('e'); + + if (exponent < 0) + { + UNITY_OUTPUT_CHAR('-'); + exponent = -exponent; + } + else + { + UNITY_OUTPUT_CHAR('+'); + } + + digits = 0; + while ((exponent != 0) || (digits < 2)) + { + buf[digits++] = (char)('0' + exponent % 10); + exponent /= 10; + } + while (digits > 0) + { + UNITY_OUTPUT_CHAR(buf[--digits]); + } + } + } +} +#endif /* ! UNITY_EXCLUDE_FLOAT_PRINT */ + +/*-----------------------------------------------*/ +static void UnityTestResultsBegin(const char* file, const UNITY_LINE_TYPE line) +{ +#ifdef UNITY_OUTPUT_FOR_ECLIPSE + UNITY_OUTPUT_CHAR('('); + UnityPrint(file); + UNITY_OUTPUT_CHAR(':'); + UnityPrintNumber((UNITY_INT)line); + UNITY_OUTPUT_CHAR(')'); + UNITY_OUTPUT_CHAR(' '); + UnityPrint(Unity.CurrentTestName); + UNITY_OUTPUT_CHAR(':'); +#else +#ifdef UNITY_OUTPUT_FOR_IAR_WORKBENCH + UnityPrint("'); + UnityPrint(Unity.CurrentTestName); + UnityPrint(" "); +#else +#ifdef UNITY_OUTPUT_FOR_QT_CREATOR + UnityPrint("file://"); + UnityPrint(file); + UNITY_OUTPUT_CHAR(':'); + UnityPrintNumber((UNITY_INT)line); + UNITY_OUTPUT_CHAR(' '); + UnityPrint(Unity.CurrentTestName); + UNITY_OUTPUT_CHAR(':'); +#else + UnityPrint(file); + UNITY_OUTPUT_CHAR(':'); + UnityPrintNumber((UNITY_INT)line); + UNITY_OUTPUT_CHAR(':'); + UnityPrint(Unity.CurrentTestName); + UNITY_OUTPUT_CHAR(':'); +#endif +#endif +#endif +} + +/*-----------------------------------------------*/ +static void UnityTestResultsFailBegin(const UNITY_LINE_TYPE line) +{ + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint(UnityStrFail); + UNITY_OUTPUT_CHAR(':'); +} + +/*-----------------------------------------------*/ +void UnityConcludeTest(void) +{ + if (Unity.CurrentTestIgnored) + { + Unity.TestIgnores++; + } + else if (!Unity.CurrentTestFailed) + { + UnityTestResultsBegin(Unity.TestFile, Unity.CurrentTestLineNumber); + UnityPrint(UnityStrPass); + } + else + { + Unity.TestFailures++; + } + + Unity.CurrentTestFailed = 0; + Unity.CurrentTestIgnored = 0; + UNITY_PRINT_EXEC_TIME(); + UNITY_PRINT_EOL(); + UNITY_FLUSH_CALL(); +} + +/*-----------------------------------------------*/ +static void UnityAddMsgIfSpecified(const char* msg) +{ + if (msg) + { + UnityPrint(UnityStrSpacer); + +#ifdef UNITY_PRINT_TEST_CONTEXT + UNITY_PRINT_TEST_CONTEXT(); +#endif +#ifndef UNITY_EXCLUDE_DETAILS + if (Unity.CurrentDetail1) + { + UnityPrint(UnityStrDetail1Name); + UnityPrint(Unity.CurrentDetail1); + if (Unity.CurrentDetail2) + { + UnityPrint(UnityStrDetail2Name); + UnityPrint(Unity.CurrentDetail2); + } + UnityPrint(UnityStrSpacer); + } +#endif + UnityPrint(msg); + } +} + +/*-----------------------------------------------*/ +static void UnityPrintExpectedAndActualStrings(const char* expected, const char* actual) +{ + UnityPrint(UnityStrExpected); + if (expected != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrint(expected); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } + UnityPrint(UnityStrWas); + if (actual != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrint(actual); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } +} + +/*-----------------------------------------------*/ +static void UnityPrintExpectedAndActualStringsLen(const char* expected, + const char* actual, + const UNITY_UINT32 length) +{ + UnityPrint(UnityStrExpected); + if (expected != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrintLen(expected, length); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } + UnityPrint(UnityStrWas); + if (actual != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrintLen(actual, length); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } +} + +/*----------------------------------------------- + * Assertion & Control Helpers + *-----------------------------------------------*/ + +/*-----------------------------------------------*/ +static int UnityIsOneArrayNull(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_LINE_TYPE lineNumber, + const char* msg) +{ + /* Both are NULL or same pointer */ + if (expected == actual) { return 0; } + + /* print and return true if just expected is NULL */ + if (expected == NULL) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrNullPointerForExpected); + UnityAddMsgIfSpecified(msg); + return 1; + } + + /* print and return true if just actual is NULL */ + if (actual == NULL) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrNullPointerForActual); + UnityAddMsgIfSpecified(msg); + return 1; + } + + return 0; /* return false if neither is NULL */ +} + +/*----------------------------------------------- + * Assertion Functions + *-----------------------------------------------*/ + +/*-----------------------------------------------*/ +void UnityAssertBits(const UNITY_INT mask, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if ((mask & expected) != (mask & actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)expected); + UnityPrint(UnityStrWas); + UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualNumber(const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (expected != actual) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expected, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, + const UNITY_INT actual, + const UNITY_COMPARISON_T compare, + const char *msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style) +{ + int failed = 0; + RETURN_IF_FAIL_OR_IGNORE; + + if ((threshold == actual) && (compare & UNITY_EQUAL_TO)) { return; } + if ((threshold == actual)) { failed = 1; } + + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if ((actual > threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } + if ((actual < threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } + } + else /* UINT or HEX */ + { + if (((UNITY_UINT)actual > (UNITY_UINT)threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } + if (((UNITY_UINT)actual < (UNITY_UINT)threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } + } + + if (failed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(actual, style); + if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } + if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } + if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } + if (compare == UNITY_NOT_EQUAL) { UnityPrint(UnityStrNotEqual); } + UnityPrintNumberByStyle(threshold, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +#define UnityPrintPointlessAndBail() \ +do { \ + UnityTestResultsFailBegin(lineNumber); \ + UnityPrint(UnityStrPointless); \ + UnityAddMsgIfSpecified(msg); \ + UNITY_FAIL_AND_BAIL; \ +} while (0) + +/*-----------------------------------------------*/ +void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + unsigned int length = style & 0xF; + unsigned int increment = 0; + + RETURN_IF_FAIL_OR_IGNORE; + + if (num_elements == 0) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + while ((elements > 0) && (elements--)) + { + UNITY_INT expect_val; + UNITY_INT actual_val; + + switch (length) + { + case 1: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; + increment = sizeof(UNITY_INT8); + break; + + case 2: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; + increment = sizeof(UNITY_INT16); + break; + +#ifdef UNITY_SUPPORT_64 + case 8: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; + increment = sizeof(UNITY_INT64); + break; +#endif + + default: /* default is length 4 bytes */ + case 4: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; + increment = sizeof(UNITY_INT32); + length = 4; + break; + } + + if (expect_val != actual_val) + { + if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8))) + { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ + UNITY_INT mask = 1; + mask = (mask << 8 * length) - 1; + expect_val &= mask; + actual_val &= mask; + } + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expect_val, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual_val, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + /* Walk through array by incrementing the pointers */ + if (flags == UNITY_ARRAY_TO_ARRAY) + { + expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment); + } + actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment); + } +} + +/*-----------------------------------------------*/ +#ifndef UNITY_EXCLUDE_FLOAT +/* Wrap this define in a function with variable types as float or double */ +#define UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff) \ + if (isinf(expected) && isinf(actual) && (((expected) < 0) == ((actual) < 0))) return 1; \ + if (UNITY_NAN_CHECK) return 1; \ + (diff) = (actual) - (expected); \ + if ((diff) < 0) (diff) = -(diff); \ + if ((delta) < 0) (delta) = -(delta); \ + return !(isnan(diff) || isinf(diff) || ((diff) > (delta))) + /* This first part of this condition will catch any NaN or Infinite values */ +#ifndef UNITY_NAN_NOT_EQUAL_NAN + #define UNITY_NAN_CHECK isnan(expected) && isnan(actual) +#else + #define UNITY_NAN_CHECK 0 +#endif + +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ + do { \ + UnityPrint(UnityStrExpected); \ + UnityPrintFloat(expected); \ + UnityPrint(UnityStrWas); \ + UnityPrintFloat(actual); \ + } while (0) +#else + #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ + UnityPrint(UnityStrDelta) +#endif /* UNITY_EXCLUDE_FLOAT_PRINT */ + +/*-----------------------------------------------*/ +static int UnityFloatsWithin(UNITY_FLOAT delta, UNITY_FLOAT expected, UNITY_FLOAT actual) +{ + UNITY_FLOAT diff; + UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); +} + +/*-----------------------------------------------*/ +void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_expected = expected; + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_actual = actual; + + RETURN_IF_FAIL_OR_IGNORE; + + if (elements == 0) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + while (elements--) + { + if (!UnityFloatsWithin(*ptr_expected * UNITY_FLOAT_PRECISION, *ptr_expected, *ptr_actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)*ptr_expected, (UNITY_DOUBLE)*ptr_actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + if (flags == UNITY_ARRAY_TO_ARRAY) + { + ptr_expected++; + } + ptr_actual++; + } +} + +/*-----------------------------------------------*/ +void UnityAssertFloatsWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + + if (!UnityFloatsWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertFloatSpecial(const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style) +{ + const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; + UNITY_INT should_be_trait = ((UNITY_INT)style & 1); + UNITY_INT is_trait = !should_be_trait; + UNITY_INT trait_index = (UNITY_INT)(style >> 1); + + RETURN_IF_FAIL_OR_IGNORE; + + switch (style) + { + case UNITY_FLOAT_IS_INF: + case UNITY_FLOAT_IS_NOT_INF: + is_trait = isinf(actual) && (actual > 0); + break; + case UNITY_FLOAT_IS_NEG_INF: + case UNITY_FLOAT_IS_NOT_NEG_INF: + is_trait = isinf(actual) && (actual < 0); + break; + + case UNITY_FLOAT_IS_NAN: + case UNITY_FLOAT_IS_NOT_NAN: + is_trait = isnan(actual) ? 1 : 0; + break; + + case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ + case UNITY_FLOAT_IS_NOT_DET: + is_trait = !isinf(actual) && !isnan(actual); + break; + + default: /* including UNITY_FLOAT_INVALID_TRAIT */ + trait_index = 0; + trait_names[0] = UnityStrInvalidFloatTrait; + break; + } + + if (is_trait != should_be_trait) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + if (!should_be_trait) + { + UnityPrint(UnityStrNot); + } + UnityPrint(trait_names[trait_index]); + UnityPrint(UnityStrWas); +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + UnityPrintFloat((UNITY_DOUBLE)actual); +#else + if (should_be_trait) + { + UnityPrint(UnityStrNot); + } + UnityPrint(trait_names[trait_index]); +#endif + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +#endif /* not UNITY_EXCLUDE_FLOAT */ + +/*-----------------------------------------------*/ +#ifndef UNITY_EXCLUDE_DOUBLE +static int UnityDoublesWithin(UNITY_DOUBLE delta, UNITY_DOUBLE expected, UNITY_DOUBLE actual) +{ + UNITY_DOUBLE diff; + UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); +} + +/*-----------------------------------------------*/ +void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_expected = expected; + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_actual = actual; + + RETURN_IF_FAIL_OR_IGNORE; + + if (elements == 0) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + while (elements--) + { + if (!UnityDoublesWithin(*ptr_expected * UNITY_DOUBLE_PRECISION, *ptr_expected, *ptr_actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(*ptr_expected, *ptr_actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + if (flags == UNITY_ARRAY_TO_ARRAY) + { + ptr_expected++; + } + ptr_actual++; + } +} + +/*-----------------------------------------------*/ +void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (!UnityDoublesWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style) +{ + const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; + UNITY_INT should_be_trait = ((UNITY_INT)style & 1); + UNITY_INT is_trait = !should_be_trait; + UNITY_INT trait_index = (UNITY_INT)(style >> 1); + + RETURN_IF_FAIL_OR_IGNORE; + + switch (style) + { + case UNITY_FLOAT_IS_INF: + case UNITY_FLOAT_IS_NOT_INF: + is_trait = isinf(actual) && (actual > 0); + break; + case UNITY_FLOAT_IS_NEG_INF: + case UNITY_FLOAT_IS_NOT_NEG_INF: + is_trait = isinf(actual) && (actual < 0); + break; + + case UNITY_FLOAT_IS_NAN: + case UNITY_FLOAT_IS_NOT_NAN: + is_trait = isnan(actual) ? 1 : 0; + break; + + case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ + case UNITY_FLOAT_IS_NOT_DET: + is_trait = !isinf(actual) && !isnan(actual); + break; + + default: /* including UNITY_FLOAT_INVALID_TRAIT */ + trait_index = 0; + trait_names[0] = UnityStrInvalidFloatTrait; + break; + } + + if (is_trait != should_be_trait) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + if (!should_be_trait) + { + UnityPrint(UnityStrNot); + } + UnityPrint(trait_names[trait_index]); + UnityPrint(UnityStrWas); +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + UnityPrintFloat(actual); +#else + if (should_be_trait) + { + UnityPrint(UnityStrNot); + } + UnityPrint(trait_names[trait_index]); +#endif + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +#endif /* not UNITY_EXCLUDE_DOUBLE */ + +/*-----------------------------------------------*/ +void UnityAssertNumbersWithin(const UNITY_UINT delta, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if (actual > expected) + { + Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta); + } + else + { + Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta); + } + } + else + { + if ((UNITY_UINT)actual > (UNITY_UINT)expected) + { + Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta); + } + else + { + Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta); + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrDelta); + UnityPrintNumberByStyle((UNITY_INT)delta, style); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expected, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, + UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + unsigned int length = style & 0xF; + unsigned int increment = 0; + + RETURN_IF_FAIL_OR_IGNORE; + + if (num_elements == 0) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + while ((elements > 0) && (elements--)) + { + UNITY_INT expect_val; + UNITY_INT actual_val; + + switch (length) + { + case 1: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; + increment = sizeof(UNITY_INT8); + break; + + case 2: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; + increment = sizeof(UNITY_INT16); + break; + +#ifdef UNITY_SUPPORT_64 + case 8: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; + increment = sizeof(UNITY_INT64); + break; +#endif + + default: /* default is length 4 bytes */ + case 4: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; + increment = sizeof(UNITY_INT32); + length = 4; + break; + } + + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if (actual_val > expect_val) + { + Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta); + } + else + { + Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta); + } + } + else + { + if ((UNITY_UINT)actual_val > (UNITY_UINT)expect_val) + { + Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta); + } + else + { + Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta); + } + } + + if (Unity.CurrentTestFailed) + { + if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8))) + { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ + UNITY_INT mask = 1; + mask = (mask << 8 * length) - 1; + expect_val &= mask; + actual_val &= mask; + } + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrDelta); + UnityPrintNumberByStyle((UNITY_INT)delta, style); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expect_val, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual_val, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + /* Walk through array by incrementing the pointers */ + if (flags == UNITY_ARRAY_TO_ARRAY) + { + expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment); + } + actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment); + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualString(const char* expected, + const char* actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + UNITY_UINT32 i; + + RETURN_IF_FAIL_OR_IGNORE; + + /* if both pointers not null compare the strings */ + if (expected && actual) + { + for (i = 0; expected[i] || actual[i]; i++) + { + if (expected[i] != actual[i]) + { + Unity.CurrentTestFailed = 1; + break; + } + } + } + else + { /* handle case of one pointers being null (if both null, test should pass) */ + if (expected != actual) + { + Unity.CurrentTestFailed = 1; + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrintExpectedAndActualStrings(expected, actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualStringLen(const char* expected, + const char* actual, + const UNITY_UINT32 length, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + UNITY_UINT32 i; + + RETURN_IF_FAIL_OR_IGNORE; + + /* if both pointers not null compare the strings */ + if (expected && actual) + { + for (i = 0; (i < length) && (expected[i] || actual[i]); i++) + { + if (expected[i] != actual[i]) + { + Unity.CurrentTestFailed = 1; + break; + } + } + } + else + { /* handle case of one pointers being null (if both null, test should pass) */ + if (expected != actual) + { + Unity.CurrentTestFailed = 1; + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrintExpectedAndActualStringsLen(expected, actual, length); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualStringArray(UNITY_INTERNAL_PTR expected, + const char** actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 i = 0; + UNITY_UINT32 j = 0; + const char* expd = NULL; + const char* act = NULL; + + RETURN_IF_FAIL_OR_IGNORE; + + /* if no elements, it's an error */ + if (num_elements == 0) + { + UnityPrintPointlessAndBail(); + } + + if ((const void*)expected == (const void*)actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + if (flags != UNITY_ARRAY_TO_ARRAY) + { + expd = (const char*)expected; + } + + do + { + act = actual[j]; + if (flags == UNITY_ARRAY_TO_ARRAY) + { + expd = ((const char* const*)expected)[j]; + } + + /* if both pointers not null compare the strings */ + if (expd && act) + { + for (i = 0; expd[i] || act[i]; i++) + { + if (expd[i] != act[i]) + { + Unity.CurrentTestFailed = 1; + break; + } + } + } + else + { /* handle case of one pointers being null (if both null, test should pass) */ + if (expd != act) + { + Unity.CurrentTestFailed = 1; + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + if (num_elements > 1) + { + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(j); + } + UnityPrintExpectedAndActualStrings(expd, act); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + } while (++j < num_elements); +} + +/*-----------------------------------------------*/ +void UnityAssertEqualMemory(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 length, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_PTR_ATTRIBUTE const unsigned char* ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; + UNITY_PTR_ATTRIBUTE const unsigned char* ptr_act = (UNITY_PTR_ATTRIBUTE const unsigned char*)actual; + UNITY_UINT32 elements = num_elements; + UNITY_UINT32 bytes; + + RETURN_IF_FAIL_OR_IGNORE; + + if ((elements == 0) || (length == 0)) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + while (elements--) + { + bytes = length; + while (bytes--) + { + if (*ptr_exp != *ptr_act) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrMemory); + if (num_elements > 1) + { + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + } + UnityPrint(UnityStrByte); + UnityPrintNumberUnsigned(length - bytes - 1); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(*ptr_exp, UNITY_DISPLAY_STYLE_HEX8); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(*ptr_act, UNITY_DISPLAY_STYLE_HEX8); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + ptr_exp++; + ptr_act++; + } + if (flags == UNITY_ARRAY_TO_VAL) + { + ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; + } + } +} + +/*-----------------------------------------------*/ + +static union +{ + UNITY_INT8 i8; + UNITY_INT16 i16; + UNITY_INT32 i32; +#ifdef UNITY_SUPPORT_64 + UNITY_INT64 i64; +#endif +#ifndef UNITY_EXCLUDE_FLOAT + float f; +#endif +#ifndef UNITY_EXCLUDE_DOUBLE + double d; +#endif +} UnityQuickCompare; + +UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size) +{ + switch(size) + { + case 1: + UnityQuickCompare.i8 = (UNITY_INT8)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i8); + + case 2: + UnityQuickCompare.i16 = (UNITY_INT16)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i16); + +#ifdef UNITY_SUPPORT_64 + case 8: + UnityQuickCompare.i64 = (UNITY_INT64)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i64); +#endif + + default: /* 4 bytes */ + UnityQuickCompare.i32 = (UNITY_INT32)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i32); + } +} + +#ifndef UNITY_EXCLUDE_FLOAT +/*-----------------------------------------------*/ +UNITY_INTERNAL_PTR UnityFloatToPtr(const float num) +{ + UnityQuickCompare.f = num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.f); +} +#endif + +#ifndef UNITY_EXCLUDE_DOUBLE +/*-----------------------------------------------*/ +UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num) +{ + UnityQuickCompare.d = num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.d); +} +#endif + +/*----------------------------------------------- + * printf helper function + *-----------------------------------------------*/ +#ifdef UNITY_INCLUDE_PRINT_FORMATTED +static void UnityPrintFVA(const char* format, va_list va) +{ + const char* pch = format; + if (pch != NULL) + { + while (*pch) + { + /* format identification character */ + if (*pch == '%') + { + pch++; + + if (pch != NULL) + { + switch (*pch) + { + case 'd': + case 'i': + { + const int number = va_arg(va, int); + UnityPrintNumber((UNITY_INT)number); + break; + } +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + case 'f': + case 'g': + { + const double number = va_arg(va, double); + UnityPrintFloat((UNITY_DOUBLE)number); + break; + } +#endif + case 'u': + { + const unsigned int number = va_arg(va, unsigned int); + UnityPrintNumberUnsigned((UNITY_UINT)number); + break; + } + case 'b': + { + const unsigned int number = va_arg(va, unsigned int); + const UNITY_UINT mask = (UNITY_UINT)0 - (UNITY_UINT)1; + UNITY_OUTPUT_CHAR('0'); + UNITY_OUTPUT_CHAR('b'); + UnityPrintMask(mask, (UNITY_UINT)number); + break; + } + case 'x': + case 'X': + case 'p': + { + const unsigned int number = va_arg(va, unsigned int); + UNITY_OUTPUT_CHAR('0'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)number, 8); + break; + } + case 'c': + { + const int ch = va_arg(va, int); + UnityPrintChar((const char *)&ch); + break; + } + case 's': + { + const char * string = va_arg(va, const char *); + UnityPrint(string); + break; + } + case '%': + { + UnityPrintChar(pch); + break; + } + default: + { + /* print the unknown format character */ + UNITY_OUTPUT_CHAR('%'); + UnityPrintChar(pch); + break; + } + } + } + } +#ifdef UNITY_OUTPUT_COLOR + /* print ANSI escape code */ + else if ((*pch == 27) && (*(pch + 1) == '[')) + { + pch += UnityPrintAnsiEscapeString(pch); + continue; + } +#endif + else if (*pch == '\n') + { + UNITY_PRINT_EOL(); + } + else + { + UnityPrintChar(pch); + } + + pch++; + } + } +} + +void UnityPrintF(const UNITY_LINE_TYPE line, const char* format, ...) +{ + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint("INFO"); + if(format != NULL) + { + UnityPrint(": "); + va_list va; + va_start(va, format); + UnityPrintFVA(format, va); + va_end(va); + } + UNITY_PRINT_EOL(); +} +#endif /* ! UNITY_INCLUDE_PRINT_FORMATTED */ + + +/*----------------------------------------------- + * Control Functions + *-----------------------------------------------*/ + +/*-----------------------------------------------*/ +void UnityFail(const char* msg, const UNITY_LINE_TYPE line) +{ + RETURN_IF_FAIL_OR_IGNORE; + + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint(UnityStrFail); + if (msg != NULL) + { + UNITY_OUTPUT_CHAR(':'); + +#ifdef UNITY_PRINT_TEST_CONTEXT + UNITY_PRINT_TEST_CONTEXT(); +#endif +#ifndef UNITY_EXCLUDE_DETAILS + if (Unity.CurrentDetail1) + { + UnityPrint(UnityStrDetail1Name); + UnityPrint(Unity.CurrentDetail1); + if (Unity.CurrentDetail2) + { + UnityPrint(UnityStrDetail2Name); + UnityPrint(Unity.CurrentDetail2); + } + UnityPrint(UnityStrSpacer); + } +#endif + if (msg[0] != ' ') + { + UNITY_OUTPUT_CHAR(' '); + } + UnityPrint(msg); + } + + UNITY_FAIL_AND_BAIL; +} + +/*-----------------------------------------------*/ +void UnityIgnore(const char* msg, const UNITY_LINE_TYPE line) +{ + RETURN_IF_FAIL_OR_IGNORE; + + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint(UnityStrIgnore); + if (msg != NULL) + { + UNITY_OUTPUT_CHAR(':'); + UNITY_OUTPUT_CHAR(' '); + UnityPrint(msg); + } + UNITY_IGNORE_AND_BAIL; +} + +/*-----------------------------------------------*/ +void UnityMessage(const char* msg, const UNITY_LINE_TYPE line) +{ + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint("INFO"); + if (msg != NULL) + { + UNITY_OUTPUT_CHAR(':'); + UNITY_OUTPUT_CHAR(' '); + UnityPrint(msg); + } + UNITY_PRINT_EOL(); +} + +/*-----------------------------------------------*/ +/* If we have not defined our own test runner, then include our default test runner to make life easier */ +#ifndef UNITY_SKIP_DEFAULT_RUNNER +void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum) +{ + Unity.CurrentTestName = FuncName; + Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)FuncLineNum; + Unity.NumberOfTests++; + UNITY_CLR_DETAILS(); + UNITY_EXEC_TIME_START(); + if (TEST_PROTECT()) + { + setUp(); + Func(); + } + if (TEST_PROTECT()) + { + tearDown(); + } + UNITY_EXEC_TIME_STOP(); + UnityConcludeTest(); +} +#endif + +/*-----------------------------------------------*/ +void UnitySetTestFile(const char* filename) +{ + Unity.TestFile = filename; +} + +/*-----------------------------------------------*/ +void UnityBegin(const char* filename) +{ + Unity.TestFile = filename; + Unity.CurrentTestName = NULL; + Unity.CurrentTestLineNumber = 0; + Unity.NumberOfTests = 0; + Unity.TestFailures = 0; + Unity.TestIgnores = 0; + Unity.CurrentTestFailed = 0; + Unity.CurrentTestIgnored = 0; + + UNITY_CLR_DETAILS(); + UNITY_OUTPUT_START(); +} + +/*-----------------------------------------------*/ +int UnityEnd(void) +{ + UNITY_PRINT_EOL(); + UnityPrint(UnityStrBreaker); + UNITY_PRINT_EOL(); + UnityPrintNumber((UNITY_INT)(Unity.NumberOfTests)); + UnityPrint(UnityStrResultsTests); + UnityPrintNumber((UNITY_INT)(Unity.TestFailures)); + UnityPrint(UnityStrResultsFailures); + UnityPrintNumber((UNITY_INT)(Unity.TestIgnores)); + UnityPrint(UnityStrResultsIgnored); + UNITY_PRINT_EOL(); + if (Unity.TestFailures == 0U) + { + UnityPrint(UnityStrOk); + } + else + { + UnityPrint(UnityStrFail); +#ifdef UNITY_DIFFERENTIATE_FINAL_FAIL + UNITY_OUTPUT_CHAR('E'); UNITY_OUTPUT_CHAR('D'); +#endif + } + UNITY_PRINT_EOL(); + UNITY_FLUSH_CALL(); + UNITY_OUTPUT_COMPLETE(); + return (int)(Unity.TestFailures); +} + +/*----------------------------------------------- + * Command Line Argument Support + *-----------------------------------------------*/ +#ifdef UNITY_USE_COMMAND_LINE_ARGS + +char* UnityOptionIncludeNamed = NULL; +char* UnityOptionExcludeNamed = NULL; +int UnityVerbosity = 1; + +/*-----------------------------------------------*/ +int UnityParseOptions(int argc, char** argv) +{ + int i; + UnityOptionIncludeNamed = NULL; + UnityOptionExcludeNamed = NULL; + + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + switch (argv[i][1]) + { + case 'l': /* list tests */ + return -1; + case 'n': /* include tests with name including this string */ + case 'f': /* an alias for -n */ + if (argv[i][2] == '=') + { + UnityOptionIncludeNamed = &argv[i][3]; + } + else if (++i < argc) + { + UnityOptionIncludeNamed = argv[i]; + } + else + { + UnityPrint("ERROR: No Test String to Include Matches For"); + UNITY_PRINT_EOL(); + return 1; + } + break; + case 'q': /* quiet */ + UnityVerbosity = 0; + break; + case 'v': /* verbose */ + UnityVerbosity = 2; + break; + case 'x': /* exclude tests with name including this string */ + if (argv[i][2] == '=') + { + UnityOptionExcludeNamed = &argv[i][3]; + } + else if (++i < argc) + { + UnityOptionExcludeNamed = argv[i]; + } + else + { + UnityPrint("ERROR: No Test String to Exclude Matches For"); + UNITY_PRINT_EOL(); + return 1; + } + break; + default: + UnityPrint("ERROR: Unknown Option "); + UNITY_OUTPUT_CHAR(argv[i][1]); + UNITY_PRINT_EOL(); + return 1; + } + } + } + + return 0; +} + +/*-----------------------------------------------*/ +int IsStringInBiggerString(const char* longstring, const char* shortstring) +{ + const char* lptr = longstring; + const char* sptr = shortstring; + const char* lnext = lptr; + + if (*sptr == '*') + { + return 1; + } + + while (*lptr) + { + lnext = lptr + 1; + + /* If they current bytes match, go on to the next bytes */ + while (*lptr && *sptr && (*lptr == *sptr)) + { + lptr++; + sptr++; + + /* We're done if we match the entire string or up to a wildcard */ + if (*sptr == '*') + return 1; + if (*sptr == ',') + return 1; + if (*sptr == '"') + return 1; + if (*sptr == '\'') + return 1; + if (*sptr == ':') + return 2; + if (*sptr == 0) + return 1; + } + + /* Otherwise we start in the long pointer 1 character further and try again */ + lptr = lnext; + sptr = shortstring; + } + + return 0; +} + +/*-----------------------------------------------*/ +int UnityStringArgumentMatches(const char* str) +{ + int retval; + const char* ptr1; + const char* ptr2; + const char* ptrf; + + /* Go through the options and get the substrings for matching one at a time */ + ptr1 = str; + while (ptr1[0] != 0) + { + if ((ptr1[0] == '"') || (ptr1[0] == '\'')) + { + ptr1++; + } + + /* look for the start of the next partial */ + ptr2 = ptr1; + ptrf = 0; + do + { + ptr2++; + if ((ptr2[0] == ':') && (ptr2[1] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')) + { + ptrf = &ptr2[1]; + } + } while ((ptr2[0] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')); + + while ((ptr2[0] != 0) && ((ptr2[0] == ':') || (ptr2[0] == '\'') || (ptr2[0] == '"') || (ptr2[0] == ','))) + { + ptr2++; + } + + /* done if complete filename match */ + retval = IsStringInBiggerString(Unity.TestFile, ptr1); + if (retval == 1) + { + return retval; + } + + /* done if testname match after filename partial match */ + if ((retval == 2) && (ptrf != 0)) + { + if (IsStringInBiggerString(Unity.CurrentTestName, ptrf)) + { + return 1; + } + } + + /* done if complete testname match */ + if (IsStringInBiggerString(Unity.CurrentTestName, ptr1) == 1) + { + return 1; + } + + ptr1 = ptr2; + } + + /* we couldn't find a match for any substrings */ + return 0; +} + +/*-----------------------------------------------*/ +int UnityTestMatches(void) +{ + /* Check if this test name matches the included test pattern */ + int retval; + if (UnityOptionIncludeNamed) + { + retval = UnityStringArgumentMatches(UnityOptionIncludeNamed); + } + else + { + retval = 1; + } + + /* Check if this test name matches the excluded test pattern */ + if (UnityOptionExcludeNamed) + { + if (UnityStringArgumentMatches(UnityOptionExcludeNamed)) + { + retval = 0; + } + } + + return retval; +} + +#endif /* UNITY_USE_COMMAND_LINE_ARGS */ +/*-----------------------------------------------*/ diff --git a/integration-tests/src/bindings_tests/unity/unity.h b/integration-tests/src/bindings_tests/unity/unity.h new file mode 100644 index 000000000..14225a354 --- /dev/null +++ b/integration-tests/src/bindings_tests/unity/unity.h @@ -0,0 +1,661 @@ +/* ========================================== + Unity Project - A Test Framework for C + Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams + [Released under MIT License. Please refer to license.txt for details] +========================================== */ + +#ifndef UNITY_FRAMEWORK_H +#define UNITY_FRAMEWORK_H +#define UNITY + +#define UNITY_VERSION_MAJOR 2 +#define UNITY_VERSION_MINOR 5 +#define UNITY_VERSION_BUILD 4 +#define UNITY_VERSION ((UNITY_VERSION_MAJOR << 16) | (UNITY_VERSION_MINOR << 8) | UNITY_VERSION_BUILD) + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "unity_internals.h" + +/*------------------------------------------------------- + * Test Setup / Teardown + *-------------------------------------------------------*/ + +/* These functions are intended to be called before and after each test. + * If using unity directly, these will need to be provided for each test + * executable built. If you are using the test runner generator and/or + * Ceedling, these are optional. */ +void setUp(void); +void tearDown(void); + +/* These functions are intended to be called at the beginning and end of an + * entire test suite. suiteTearDown() is passed the number of tests that + * failed, and its return value becomes the exit code of main(). If using + * Unity directly, you're in charge of calling these if they are desired. + * If using Ceedling or the test runner generator, these will be called + * automatically if they exist. */ +void suiteSetUp(void); +int suiteTearDown(int num_failures); + +/*------------------------------------------------------- + * Test Reset and Verify + *-------------------------------------------------------*/ + +/* These functions are intended to be called before during tests in order + * to support complex test loops, etc. Both are NOT built into Unity. Instead + * the test runner generator will create them. resetTest will run teardown and + * setup again, verifying any end-of-test needs between. verifyTest will only + * run the verification. */ +void resetTest(void); +void verifyTest(void); + +/*------------------------------------------------------- + * Configuration Options + *------------------------------------------------------- + * All options described below should be passed as a compiler flag to all files using Unity. If you must add #defines, place them BEFORE the #include above. + + * Integers/longs/pointers + * - Unity attempts to automatically discover your integer sizes + * - define UNITY_EXCLUDE_STDINT_H to stop attempting to look in + * - define UNITY_EXCLUDE_LIMITS_H to stop attempting to look in + * - If you cannot use the automatic methods above, you can force Unity by using these options: + * - define UNITY_SUPPORT_64 + * - set UNITY_INT_WIDTH + * - set UNITY_LONG_WIDTH + * - set UNITY_POINTER_WIDTH + + * Floats + * - define UNITY_EXCLUDE_FLOAT to disallow floating point comparisons + * - define UNITY_FLOAT_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_FLOAT + * - define UNITY_FLOAT_TYPE to specify doubles instead of single precision floats + * - define UNITY_INCLUDE_DOUBLE to allow double floating point comparisons + * - define UNITY_EXCLUDE_DOUBLE to disallow double floating point comparisons (default) + * - define UNITY_DOUBLE_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_DOUBLE + * - define UNITY_DOUBLE_TYPE to specify something other than double + * - define UNITY_EXCLUDE_FLOAT_PRINT to trim binary size, won't print floating point values in errors + + * Output + * - by default, Unity prints to standard out with putchar. define UNITY_OUTPUT_CHAR(a) with a different function if desired + * - define UNITY_DIFFERENTIATE_FINAL_FAIL to print FAILED (vs. FAIL) at test end summary - for automated search for failure + + * Optimization + * - by default, line numbers are stored in unsigned shorts. Define UNITY_LINE_TYPE with a different type if your files are huge + * - by default, test and failure counters are unsigned shorts. Define UNITY_COUNTER_TYPE with a different type if you want to save space or have more than 65535 Tests. + + * Test Cases + * - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script + + * Parameterized Tests + * - you'll want to create a define of TEST_CASE(...) which basically evaluates to nothing + + * Tests with Arguments + * - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity + + *------------------------------------------------------- + * Basic Fail and Ignore + *-------------------------------------------------------*/ + +#define TEST_FAIL_MESSAGE(message) UNITY_TEST_FAIL(__LINE__, (message)) +#define TEST_FAIL() UNITY_TEST_FAIL(__LINE__, NULL) +#define TEST_IGNORE_MESSAGE(message) UNITY_TEST_IGNORE(__LINE__, (message)) +#define TEST_IGNORE() UNITY_TEST_IGNORE(__LINE__, NULL) +#define TEST_MESSAGE(message) UnityMessage((message), __LINE__) +#define TEST_ONLY() +#ifdef UNITY_INCLUDE_PRINT_FORMATTED +#define TEST_PRINTF(message, ...) UnityPrintF(__LINE__, (message), __VA_ARGS__) +#endif + +/* It is not necessary for you to call PASS. A PASS condition is assumed if nothing fails. + * This method allows you to abort a test immediately with a PASS state, ignoring the remainder of the test. */ +#define TEST_PASS() TEST_ABORT() +#define TEST_PASS_MESSAGE(message) do { UnityMessage((message), __LINE__); TEST_ABORT(); } while (0) + +/* This macro does nothing, but it is useful for build tools (like Ceedling) to make use of this to figure out + * which files should be linked to in order to perform a test. Use it like TEST_FILE("sandwiches.c") */ +#define TEST_FILE(a) + +/*------------------------------------------------------- + * Test Asserts (simple) + *-------------------------------------------------------*/ + +/* Boolean */ +#define TEST_ASSERT(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expression Evaluated To FALSE") +#define TEST_ASSERT_TRUE(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expected TRUE Was FALSE") +#define TEST_ASSERT_UNLESS(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expression Evaluated To TRUE") +#define TEST_ASSERT_FALSE(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expected FALSE Was TRUE") +#define TEST_ASSERT_NULL(pointer) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, " Expected NULL") +#define TEST_ASSERT_NOT_NULL(pointer) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, " Expected Non-NULL") +#define TEST_ASSERT_EMPTY(pointer) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, " Expected Empty") +#define TEST_ASSERT_NOT_EMPTY(pointer) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, " Expected Non-Empty") + +/* Integers (of all sizes) */ +#define TEST_ASSERT_EQUAL_INT(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_size_t(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX8(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX16(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX32(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX64(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_CHAR(expected, actual) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_BITS(mask, expected, actual) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_BITS_HIGH(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(-1), (actual), __LINE__, NULL) +#define TEST_ASSERT_BITS_LOW(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(0), (actual), __LINE__, NULL) +#define TEST_ASSERT_BIT_HIGH(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(-1), (actual), __LINE__, NULL) +#define TEST_ASSERT_BIT_LOW(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(0), (actual), __LINE__, NULL) + +/* Integer Not Equal To (of all sizes) */ +#define TEST_ASSERT_NOT_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, NULL) + +/* Integer Greater Than/ Less Than (of all sizes) */ +#define TEST_ASSERT_GREATER_THAN(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, NULL) + +#define TEST_ASSERT_LESS_THAN(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, NULL) + +#define TEST_ASSERT_GREATER_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL) + +#define TEST_ASSERT_LESS_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL) + +/* Integer Ranges (of all sizes) */ +#define TEST_ASSERT_INT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_size_t_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_CHAR_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, NULL) + +/* Integer Array Ranges (of all sizes) */ +#define TEST_ASSERT_INT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_INT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_size_t_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) +#define TEST_ASSERT_CHAR_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) + + +/* Structs and Strings */ +#define TEST_ASSERT_EQUAL_PTR(expected, actual) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_STRING(expected, actual) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, NULL) + +/* Arrays */ +#define TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_size_t_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_CHAR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) + +/* Arrays Compared To Single Value */ +#define TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_size_t(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_CHAR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, NULL) + +/* Floating Point (If Enabled) */ +#define TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, NULL) + +/* Double (If Enabled) */ +#define TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, NULL) + +/* Shorthand */ +#ifdef UNITY_SHORTHAND_AS_OLD +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal") +#endif +#ifdef UNITY_SHORTHAND_AS_INT +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif +#ifdef UNITY_SHORTHAND_AS_MEM +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif +#ifdef UNITY_SHORTHAND_AS_RAW +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, " Expected Equal") +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal") +#endif +#ifdef UNITY_SHORTHAND_AS_NONE +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif + +/*------------------------------------------------------- + * Test Asserts (with additional messages) + *-------------------------------------------------------*/ + +/* Boolean */ +#define TEST_ASSERT_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) +#define TEST_ASSERT_TRUE_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) +#define TEST_ASSERT_UNLESS_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) +#define TEST_ASSERT_FALSE_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) +#define TEST_ASSERT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, (message)) +#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, (message)) +#define TEST_ASSERT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, (message)) +#define TEST_ASSERT_NOT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, (message)) + +/* Integers (of all sizes) */ +#define TEST_ASSERT_EQUAL_INT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_size_t_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_BITS_MESSAGE(mask, expected, actual, message) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_BITS_HIGH_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) +#define TEST_ASSERT_BITS_LOW_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(0), (actual), __LINE__, (message)) +#define TEST_ASSERT_BIT_HIGH_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) +#define TEST_ASSERT_BIT_LOW_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(0), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_CHAR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, (message)) + +/* Integer Not Equal To (of all sizes) */ +#define TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, (message)) + + +/* Integer Greater Than/ Less Than (of all sizes) */ +#define TEST_ASSERT_GREATER_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, (message)) + +#define TEST_ASSERT_LESS_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, (message)) + +#define TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message)) + +#define TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message)) + +/* Integer Ranges (of all sizes) */ +#define TEST_ASSERT_INT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_size_t_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_CHAR_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, (message)) + +/* Integer Array Ranges (of all sizes) */ +#define TEST_ASSERT_INT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_INT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_INT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_INT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_INT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_UINT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_size_t_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_HEX64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) +#define TEST_ASSERT_CHAR_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) + + +/* Structs and Strings */ +#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_MEMORY_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, (message)) + +/* Arrays */ +#define TEST_ASSERT_EQUAL_INT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_size_t_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_PTR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_STRING_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_MEMORY_ARRAY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_CHAR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) + +/* Arrays Compared To Single Value*/ +#define TEST_ASSERT_EACH_EQUAL_INT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_size_t_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_PTR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_STRING_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_MEMORY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_CHAR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, (message)) + +/* Floating Point (If Enabled) */ +#define TEST_ASSERT_FLOAT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_FLOAT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_FLOAT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, (message)) + +/* Double (If Enabled) */ +#define TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_DOUBLE_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, (message)) + +/* Shorthand */ +#ifdef UNITY_SHORTHAND_AS_OLD +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, (message)) +#endif +#ifdef UNITY_SHORTHAND_AS_INT +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, message) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif +#ifdef UNITY_SHORTHAND_AS_MEM +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, message) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif +#ifdef UNITY_SHORTHAND_AS_RAW +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, message) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, message) +#endif +#ifdef UNITY_SHORTHAND_AS_NONE +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) +#endif + +/* end of UNITY_FRAMEWORK_H */ +#ifdef __cplusplus +} +#endif +#endif diff --git a/integration-tests/src/bindings_tests/unity/unity_internals.h b/integration-tests/src/bindings_tests/unity/unity_internals.h new file mode 100644 index 000000000..d303e8fe7 --- /dev/null +++ b/integration-tests/src/bindings_tests/unity/unity_internals.h @@ -0,0 +1,1053 @@ +/* ========================================== + Unity Project - A Test Framework for C + Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams + [Released under MIT License. Please refer to license.txt for details] +========================================== */ + +#ifndef UNITY_INTERNALS_H +#define UNITY_INTERNALS_H + +#ifdef UNITY_INCLUDE_CONFIG_H +#include "unity_config.h" +#endif + +#ifndef UNITY_EXCLUDE_SETJMP_H +#include +#endif + +#ifndef UNITY_EXCLUDE_MATH_H +#include +#endif + +#ifndef UNITY_EXCLUDE_STDDEF_H +#include +#endif + +#ifdef UNITY_INCLUDE_PRINT_FORMATTED +#include +#endif + +/* Unity Attempts to Auto-Detect Integer Types + * Attempt 1: UINT_MAX, ULONG_MAX in , or default to 32 bits + * Attempt 2: UINTPTR_MAX in , or default to same size as long + * The user may override any of these derived constants: + * UNITY_INT_WIDTH, UNITY_LONG_WIDTH, UNITY_POINTER_WIDTH */ +#ifndef UNITY_EXCLUDE_STDINT_H +#include +#endif + +#ifndef UNITY_EXCLUDE_LIMITS_H +#include +#endif + +#if defined(__GNUC__) || defined(__clang__) + #define UNITY_FUNCTION_ATTR(a) __attribute__((a)) +#else + #define UNITY_FUNCTION_ATTR(a) /* ignore */ +#endif + +#ifndef UNITY_NORETURN + #if defined(__cplusplus) + #if __cplusplus >= 201103L + #define UNITY_NORETURN [[ noreturn ]] + #endif + #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #include + #define UNITY_NORETURN noreturn + #endif +#endif +#ifndef UNITY_NORETURN + #define UNITY_NORETURN UNITY_FUNCTION_ATTR(noreturn) +#endif + +/*------------------------------------------------------- + * Guess Widths If Not Specified + *-------------------------------------------------------*/ + +/* Determine the size of an int, if not already specified. + * We cannot use sizeof(int), because it is not yet defined + * at this stage in the translation of the C program. + * Also sizeof(int) does return the size in addressable units on all platforms, + * which may not necessarily be the size in bytes. + * Therefore, infer it from UINT_MAX if possible. */ +#ifndef UNITY_INT_WIDTH + #ifdef UINT_MAX + #if (UINT_MAX == 0xFFFF) + #define UNITY_INT_WIDTH (16) + #elif (UINT_MAX == 0xFFFFFFFF) + #define UNITY_INT_WIDTH (32) + #elif (UINT_MAX == 0xFFFFFFFFFFFFFFFF) + #define UNITY_INT_WIDTH (64) + #endif + #else /* Set to default */ + #define UNITY_INT_WIDTH (32) + #endif /* UINT_MAX */ +#endif + +/* Determine the size of a long, if not already specified. */ +#ifndef UNITY_LONG_WIDTH + #ifdef ULONG_MAX + #if (ULONG_MAX == 0xFFFF) + #define UNITY_LONG_WIDTH (16) + #elif (ULONG_MAX == 0xFFFFFFFF) + #define UNITY_LONG_WIDTH (32) + #elif (ULONG_MAX == 0xFFFFFFFFFFFFFFFF) + #define UNITY_LONG_WIDTH (64) + #endif + #else /* Set to default */ + #define UNITY_LONG_WIDTH (32) + #endif /* ULONG_MAX */ +#endif + +/* Determine the size of a pointer, if not already specified. */ +#ifndef UNITY_POINTER_WIDTH + #ifdef UINTPTR_MAX + #if (UINTPTR_MAX <= 0xFFFF) + #define UNITY_POINTER_WIDTH (16) + #elif (UINTPTR_MAX <= 0xFFFFFFFF) + #define UNITY_POINTER_WIDTH (32) + #elif (UINTPTR_MAX <= 0xFFFFFFFFFFFFFFFF) + #define UNITY_POINTER_WIDTH (64) + #endif + #else /* Set to default */ + #define UNITY_POINTER_WIDTH UNITY_LONG_WIDTH + #endif /* UINTPTR_MAX */ +#endif + +/*------------------------------------------------------- + * Int Support (Define types based on detected sizes) + *-------------------------------------------------------*/ + +#if (UNITY_INT_WIDTH == 32) + typedef unsigned char UNITY_UINT8; + typedef unsigned short UNITY_UINT16; + typedef unsigned int UNITY_UINT32; + typedef signed char UNITY_INT8; + typedef signed short UNITY_INT16; + typedef signed int UNITY_INT32; +#elif (UNITY_INT_WIDTH == 16) + typedef unsigned char UNITY_UINT8; + typedef unsigned int UNITY_UINT16; + typedef unsigned long UNITY_UINT32; + typedef signed char UNITY_INT8; + typedef signed int UNITY_INT16; + typedef signed long UNITY_INT32; +#else + #error Invalid UNITY_INT_WIDTH specified! (16 or 32 are supported) +#endif + +/*------------------------------------------------------- + * 64-bit Support + *-------------------------------------------------------*/ + +/* Auto-detect 64 Bit Support */ +#ifndef UNITY_SUPPORT_64 + #if UNITY_LONG_WIDTH == 64 || UNITY_POINTER_WIDTH == 64 + #define UNITY_SUPPORT_64 + #endif +#endif + +/* 64-Bit Support Dependent Configuration */ +#ifndef UNITY_SUPPORT_64 + /* No 64-bit Support */ + typedef UNITY_UINT32 UNITY_UINT; + typedef UNITY_INT32 UNITY_INT; + #define UNITY_MAX_NIBBLES (8) /* Maximum number of nibbles in a UNITY_(U)INT */ +#else + /* 64-bit Support */ + #if (UNITY_LONG_WIDTH == 32) + typedef unsigned long long UNITY_UINT64; + typedef signed long long UNITY_INT64; + #elif (UNITY_LONG_WIDTH == 64) + typedef unsigned long UNITY_UINT64; + typedef signed long UNITY_INT64; + #else + #error Invalid UNITY_LONG_WIDTH specified! (32 or 64 are supported) + #endif + typedef UNITY_UINT64 UNITY_UINT; + typedef UNITY_INT64 UNITY_INT; + #define UNITY_MAX_NIBBLES (16) /* Maximum number of nibbles in a UNITY_(U)INT */ +#endif + +/*------------------------------------------------------- + * Pointer Support + *-------------------------------------------------------*/ + +#if (UNITY_POINTER_WIDTH == 32) + #define UNITY_PTR_TO_INT UNITY_INT32 + #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX32 +#elif (UNITY_POINTER_WIDTH == 64) + #define UNITY_PTR_TO_INT UNITY_INT64 + #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX64 +#elif (UNITY_POINTER_WIDTH == 16) + #define UNITY_PTR_TO_INT UNITY_INT16 + #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX16 +#else + #error Invalid UNITY_POINTER_WIDTH specified! (16, 32 or 64 are supported) +#endif + +#ifndef UNITY_PTR_ATTRIBUTE + #define UNITY_PTR_ATTRIBUTE +#endif + +#ifndef UNITY_INTERNAL_PTR + #define UNITY_INTERNAL_PTR UNITY_PTR_ATTRIBUTE const void* +#endif + +/*------------------------------------------------------- + * Float Support + *-------------------------------------------------------*/ + +#ifdef UNITY_EXCLUDE_FLOAT + +/* No Floating Point Support */ +#ifndef UNITY_EXCLUDE_DOUBLE +#define UNITY_EXCLUDE_DOUBLE /* Remove double when excluding float support */ +#endif +#ifndef UNITY_EXCLUDE_FLOAT_PRINT +#define UNITY_EXCLUDE_FLOAT_PRINT +#endif + +#else + +/* Floating Point Support */ +#ifndef UNITY_FLOAT_PRECISION +#define UNITY_FLOAT_PRECISION (0.00001f) +#endif +#ifndef UNITY_FLOAT_TYPE +#define UNITY_FLOAT_TYPE float +#endif +typedef UNITY_FLOAT_TYPE UNITY_FLOAT; + +/* isinf & isnan macros should be provided by math.h */ +#ifndef isinf +/* The value of Inf - Inf is NaN */ +#define isinf(n) (isnan((n) - (n)) && !isnan(n)) +#endif + +#ifndef isnan +/* NaN is the only floating point value that does NOT equal itself. + * Therefore if n != n, then it is NaN. */ +#define isnan(n) ((n != n) ? 1 : 0) +#endif + +#endif + +/*------------------------------------------------------- + * Double Float Support + *-------------------------------------------------------*/ + +/* unlike float, we DON'T include by default */ +#if defined(UNITY_EXCLUDE_DOUBLE) || !defined(UNITY_INCLUDE_DOUBLE) + + /* No Floating Point Support */ + #ifndef UNITY_EXCLUDE_DOUBLE + #define UNITY_EXCLUDE_DOUBLE + #else + #undef UNITY_INCLUDE_DOUBLE + #endif + + #ifndef UNITY_EXCLUDE_FLOAT + #ifndef UNITY_DOUBLE_TYPE + #define UNITY_DOUBLE_TYPE double + #endif + typedef UNITY_FLOAT UNITY_DOUBLE; + /* For parameter in UnityPrintFloat(UNITY_DOUBLE), which aliases to double or float */ + #endif + +#else + + /* Double Floating Point Support */ + #ifndef UNITY_DOUBLE_PRECISION + #define UNITY_DOUBLE_PRECISION (1e-12) + #endif + + #ifndef UNITY_DOUBLE_TYPE + #define UNITY_DOUBLE_TYPE double + #endif + typedef UNITY_DOUBLE_TYPE UNITY_DOUBLE; + +#endif + +/*------------------------------------------------------- + * Output Method: stdout (DEFAULT) + *-------------------------------------------------------*/ +#ifndef UNITY_OUTPUT_CHAR + /* Default to using putchar, which is defined in stdio.h */ + #include + #define UNITY_OUTPUT_CHAR(a) (void)putchar(a) +#else + /* If defined as something else, make sure we declare it here so it's ready for use */ + #ifdef UNITY_OUTPUT_CHAR_HEADER_DECLARATION + extern void UNITY_OUTPUT_CHAR_HEADER_DECLARATION; + #endif +#endif + +#ifndef UNITY_OUTPUT_FLUSH + #ifdef UNITY_USE_FLUSH_STDOUT + /* We want to use the stdout flush utility */ + #include + #define UNITY_OUTPUT_FLUSH() (void)fflush(stdout) + #else + /* We've specified nothing, therefore flush should just be ignored */ + #define UNITY_OUTPUT_FLUSH() (void)0 + #endif +#else + /* If defined as something else, make sure we declare it here so it's ready for use */ + #ifdef UNITY_OUTPUT_FLUSH_HEADER_DECLARATION + extern void UNITY_OUTPUT_FLUSH_HEADER_DECLARATION; + #endif +#endif + +#ifndef UNITY_OUTPUT_FLUSH +#define UNITY_FLUSH_CALL() +#else +#define UNITY_FLUSH_CALL() UNITY_OUTPUT_FLUSH() +#endif + +#ifndef UNITY_PRINT_EOL +#define UNITY_PRINT_EOL() UNITY_OUTPUT_CHAR('\n') +#endif + +#ifndef UNITY_OUTPUT_START +#define UNITY_OUTPUT_START() +#endif + +#ifndef UNITY_OUTPUT_COMPLETE +#define UNITY_OUTPUT_COMPLETE() +#endif + +#ifdef UNITY_INCLUDE_EXEC_TIME + #if !defined(UNITY_EXEC_TIME_START) && \ + !defined(UNITY_EXEC_TIME_STOP) && \ + !defined(UNITY_PRINT_EXEC_TIME) && \ + !defined(UNITY_TIME_TYPE) + /* If none any of these macros are defined then try to provide a default implementation */ + + #if defined(UNITY_CLOCK_MS) + /* This is a simple way to get a default implementation on platforms that support getting a millisecond counter */ + #define UNITY_TIME_TYPE UNITY_UINT + #define UNITY_EXEC_TIME_START() Unity.CurrentTestStartTime = UNITY_CLOCK_MS() + #define UNITY_EXEC_TIME_STOP() Unity.CurrentTestStopTime = UNITY_CLOCK_MS() + #define UNITY_PRINT_EXEC_TIME() { \ + UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \ + UnityPrint(" ("); \ + UnityPrintNumberUnsigned(execTimeMs); \ + UnityPrint(" ms)"); \ + } + #elif defined(_WIN32) + #include + #define UNITY_TIME_TYPE clock_t + #define UNITY_GET_TIME(t) t = (clock_t)((clock() * 1000) / CLOCKS_PER_SEC) + #define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime) + #define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime) + #define UNITY_PRINT_EXEC_TIME() { \ + UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \ + UnityPrint(" ("); \ + UnityPrintNumberUnsigned(execTimeMs); \ + UnityPrint(" ms)"); \ + } + #elif defined(__unix__) || defined(__APPLE__) + #include + #define UNITY_TIME_TYPE struct timespec + #define UNITY_GET_TIME(t) clock_gettime(CLOCK_MONOTONIC, &t) + #define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime) + #define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime) + #define UNITY_PRINT_EXEC_TIME() { \ + UNITY_UINT execTimeMs = ((Unity.CurrentTestStopTime.tv_sec - Unity.CurrentTestStartTime.tv_sec) * 1000L); \ + execTimeMs += ((Unity.CurrentTestStopTime.tv_nsec - Unity.CurrentTestStartTime.tv_nsec) / 1000000L); \ + UnityPrint(" ("); \ + UnityPrintNumberUnsigned(execTimeMs); \ + UnityPrint(" ms)"); \ + } + #endif + #endif +#endif + +#ifndef UNITY_EXEC_TIME_START +#define UNITY_EXEC_TIME_START() do { /* nothing*/ } while (0) +#endif + +#ifndef UNITY_EXEC_TIME_STOP +#define UNITY_EXEC_TIME_STOP() do { /* nothing*/ } while (0) +#endif + +#ifndef UNITY_TIME_TYPE +#define UNITY_TIME_TYPE UNITY_UINT +#endif + +#ifndef UNITY_PRINT_EXEC_TIME +#define UNITY_PRINT_EXEC_TIME() do { /* nothing*/ } while (0) +#endif + +/*------------------------------------------------------- + * Footprint + *-------------------------------------------------------*/ + +#ifndef UNITY_LINE_TYPE +#define UNITY_LINE_TYPE UNITY_UINT +#endif + +#ifndef UNITY_COUNTER_TYPE +#define UNITY_COUNTER_TYPE UNITY_UINT +#endif + +/*------------------------------------------------------- + * Internal Structs Needed + *-------------------------------------------------------*/ + +typedef void (*UnityTestFunction)(void); + +#define UNITY_DISPLAY_RANGE_INT (0x10) +#define UNITY_DISPLAY_RANGE_UINT (0x20) +#define UNITY_DISPLAY_RANGE_HEX (0x40) +#define UNITY_DISPLAY_RANGE_CHAR (0x80) + +typedef enum +{ + UNITY_DISPLAY_STYLE_INT = (UNITY_INT_WIDTH / 8) + UNITY_DISPLAY_RANGE_INT, + UNITY_DISPLAY_STYLE_INT8 = 1 + UNITY_DISPLAY_RANGE_INT, + UNITY_DISPLAY_STYLE_INT16 = 2 + UNITY_DISPLAY_RANGE_INT, + UNITY_DISPLAY_STYLE_INT32 = 4 + UNITY_DISPLAY_RANGE_INT, +#ifdef UNITY_SUPPORT_64 + UNITY_DISPLAY_STYLE_INT64 = 8 + UNITY_DISPLAY_RANGE_INT, +#endif + + UNITY_DISPLAY_STYLE_UINT = (UNITY_INT_WIDTH / 8) + UNITY_DISPLAY_RANGE_UINT, + UNITY_DISPLAY_STYLE_UINT8 = 1 + UNITY_DISPLAY_RANGE_UINT, + UNITY_DISPLAY_STYLE_UINT16 = 2 + UNITY_DISPLAY_RANGE_UINT, + UNITY_DISPLAY_STYLE_UINT32 = 4 + UNITY_DISPLAY_RANGE_UINT, +#ifdef UNITY_SUPPORT_64 + UNITY_DISPLAY_STYLE_UINT64 = 8 + UNITY_DISPLAY_RANGE_UINT, +#endif + + UNITY_DISPLAY_STYLE_HEX8 = 1 + UNITY_DISPLAY_RANGE_HEX, + UNITY_DISPLAY_STYLE_HEX16 = 2 + UNITY_DISPLAY_RANGE_HEX, + UNITY_DISPLAY_STYLE_HEX32 = 4 + UNITY_DISPLAY_RANGE_HEX, +#ifdef UNITY_SUPPORT_64 + UNITY_DISPLAY_STYLE_HEX64 = 8 + UNITY_DISPLAY_RANGE_HEX, +#endif + + UNITY_DISPLAY_STYLE_CHAR = 1 + UNITY_DISPLAY_RANGE_CHAR + UNITY_DISPLAY_RANGE_INT, + + UNITY_DISPLAY_STYLE_UNKNOWN +} UNITY_DISPLAY_STYLE_T; + +typedef enum +{ + UNITY_WITHIN = 0x0, + UNITY_EQUAL_TO = 0x1, + UNITY_GREATER_THAN = 0x2, + UNITY_GREATER_OR_EQUAL = 0x2 + UNITY_EQUAL_TO, + UNITY_SMALLER_THAN = 0x4, + UNITY_SMALLER_OR_EQUAL = 0x4 + UNITY_EQUAL_TO, + UNITY_NOT_EQUAL = 0x0, + UNITY_UNKNOWN +} UNITY_COMPARISON_T; + +#ifndef UNITY_EXCLUDE_FLOAT +typedef enum UNITY_FLOAT_TRAIT +{ + UNITY_FLOAT_IS_NOT_INF = 0, + UNITY_FLOAT_IS_INF, + UNITY_FLOAT_IS_NOT_NEG_INF, + UNITY_FLOAT_IS_NEG_INF, + UNITY_FLOAT_IS_NOT_NAN, + UNITY_FLOAT_IS_NAN, + UNITY_FLOAT_IS_NOT_DET, + UNITY_FLOAT_IS_DET, + UNITY_FLOAT_INVALID_TRAIT +} UNITY_FLOAT_TRAIT_T; +#endif + +typedef enum +{ + UNITY_ARRAY_TO_VAL = 0, + UNITY_ARRAY_TO_ARRAY, + UNITY_ARRAY_UNKNOWN +} UNITY_FLAGS_T; + +struct UNITY_STORAGE_T +{ + const char* TestFile; + const char* CurrentTestName; +#ifndef UNITY_EXCLUDE_DETAILS + const char* CurrentDetail1; + const char* CurrentDetail2; +#endif + UNITY_LINE_TYPE CurrentTestLineNumber; + UNITY_COUNTER_TYPE NumberOfTests; + UNITY_COUNTER_TYPE TestFailures; + UNITY_COUNTER_TYPE TestIgnores; + UNITY_COUNTER_TYPE CurrentTestFailed; + UNITY_COUNTER_TYPE CurrentTestIgnored; +#ifdef UNITY_INCLUDE_EXEC_TIME + UNITY_TIME_TYPE CurrentTestStartTime; + UNITY_TIME_TYPE CurrentTestStopTime; +#endif +#ifndef UNITY_EXCLUDE_SETJMP_H + jmp_buf AbortFrame; +#endif +}; + +extern struct UNITY_STORAGE_T Unity; + +/*------------------------------------------------------- + * Test Suite Management + *-------------------------------------------------------*/ + +void UnityBegin(const char* filename); +int UnityEnd(void); +void UnitySetTestFile(const char* filename); +void UnityConcludeTest(void); + +#ifndef RUN_TEST +void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum); +#else +#define UNITY_SKIP_DEFAULT_RUNNER +#endif + +/*------------------------------------------------------- + * Details Support + *-------------------------------------------------------*/ + +#ifdef UNITY_EXCLUDE_DETAILS +#define UNITY_CLR_DETAILS() +#define UNITY_SET_DETAIL(d1) +#define UNITY_SET_DETAILS(d1,d2) +#else +#define UNITY_CLR_DETAILS() do { Unity.CurrentDetail1 = 0; Unity.CurrentDetail2 = 0; } while (0) +#define UNITY_SET_DETAIL(d1) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = 0; } while (0) +#define UNITY_SET_DETAILS(d1,d2) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = (d2); } while (0) + +#ifndef UNITY_DETAIL1_NAME +#define UNITY_DETAIL1_NAME "Function" +#endif + +#ifndef UNITY_DETAIL2_NAME +#define UNITY_DETAIL2_NAME "Argument" +#endif +#endif + +#ifdef UNITY_PRINT_TEST_CONTEXT +void UNITY_PRINT_TEST_CONTEXT(void); +#endif + +/*------------------------------------------------------- + * Test Output + *-------------------------------------------------------*/ + +void UnityPrint(const char* string); + +#ifdef UNITY_INCLUDE_PRINT_FORMATTED +void UnityPrintF(const UNITY_LINE_TYPE line, const char* format, ...); +#endif + +void UnityPrintLen(const char* string, const UNITY_UINT32 length); +void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number); +void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style); +void UnityPrintNumber(const UNITY_INT number_to_print); +void UnityPrintNumberUnsigned(const UNITY_UINT number); +void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print); + +#ifndef UNITY_EXCLUDE_FLOAT_PRINT +void UnityPrintFloat(const UNITY_DOUBLE input_number); +#endif + +/*------------------------------------------------------- + * Test Assertion Functions + *------------------------------------------------------- + * Use the macros below this section instead of calling + * these directly. The macros have a consistent naming + * convention and will pull in file and line information + * for you. */ + +void UnityAssertEqualNumber(const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style); + +void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, + const UNITY_INT actual, + const UNITY_COMPARISON_T compare, + const char *msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style); + +void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags); + +void UnityAssertBits(const UNITY_INT mask, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualString(const char* expected, + const char* actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualStringLen(const char* expected, + const char* actual, + const UNITY_UINT32 length, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualStringArray( UNITY_INTERNAL_PTR expected, + const char** actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertEqualMemory( UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 length, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertNumbersWithin(const UNITY_UINT delta, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style); + +void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, + UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags); + +#ifndef UNITY_EXCLUDE_SETJMP_H +UNITY_NORETURN void UnityFail(const char* message, const UNITY_LINE_TYPE line); +UNITY_NORETURN void UnityIgnore(const char* message, const UNITY_LINE_TYPE line); +#else +void UnityFail(const char* message, const UNITY_LINE_TYPE line); +void UnityIgnore(const char* message, const UNITY_LINE_TYPE line); +#endif + +void UnityMessage(const char* message, const UNITY_LINE_TYPE line); + +#ifndef UNITY_EXCLUDE_FLOAT +void UnityAssertFloatsWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertFloatSpecial(const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style); +#endif + +#ifndef UNITY_EXCLUDE_DOUBLE +void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style); +#endif + +/*------------------------------------------------------- + * Helpers + *-------------------------------------------------------*/ + +UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size); +#ifndef UNITY_EXCLUDE_FLOAT +UNITY_INTERNAL_PTR UnityFloatToPtr(const float num); +#endif +#ifndef UNITY_EXCLUDE_DOUBLE +UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num); +#endif + +/*------------------------------------------------------- + * Error Strings We Might Need + *-------------------------------------------------------*/ + +extern const char UnityStrOk[]; +extern const char UnityStrPass[]; +extern const char UnityStrFail[]; +extern const char UnityStrIgnore[]; + +extern const char UnityStrErrFloat[]; +extern const char UnityStrErrDouble[]; +extern const char UnityStrErr64[]; +extern const char UnityStrErrShorthand[]; + +/*------------------------------------------------------- + * Test Running Macros + *-------------------------------------------------------*/ + +#ifndef UNITY_EXCLUDE_SETJMP_H +#define TEST_PROTECT() (setjmp(Unity.AbortFrame) == 0) +#define TEST_ABORT() longjmp(Unity.AbortFrame, 1) +#else +#define TEST_PROTECT() 1 +#define TEST_ABORT() return +#endif + +/* This tricky series of macros gives us an optional line argument to treat it as RUN_TEST(func, num=__LINE__) */ +#ifndef RUN_TEST +#ifdef __STDC_VERSION__ +#if __STDC_VERSION__ >= 199901L +#define UNITY_SUPPORT_VARIADIC_MACROS +#endif +#endif +#ifdef UNITY_SUPPORT_VARIADIC_MACROS +#define RUN_TEST(...) RUN_TEST_AT_LINE(__VA_ARGS__, __LINE__, throwaway) +#define RUN_TEST_AT_LINE(func, line, ...) UnityDefaultTestRun(func, #func, line) +#endif +#endif + +/* If we can't do the tricky version, we'll just have to require them to always include the line number */ +#ifndef RUN_TEST +#ifdef CMOCK +#define RUN_TEST(func, num) UnityDefaultTestRun(func, #func, num) +#else +#define RUN_TEST(func) UnityDefaultTestRun(func, #func, __LINE__) +#endif +#endif + +#define TEST_LINE_NUM (Unity.CurrentTestLineNumber) +#define TEST_IS_IGNORED (Unity.CurrentTestIgnored) +#define UNITY_NEW_TEST(a) \ + Unity.CurrentTestName = (a); \ + Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)(__LINE__); \ + Unity.NumberOfTests++; + +#ifndef UNITY_BEGIN +#define UNITY_BEGIN() UnityBegin(__FILE__) +#endif + +#ifndef UNITY_END +#define UNITY_END() UnityEnd() +#endif + +#ifndef UNITY_SHORTHAND_AS_INT +#ifndef UNITY_SHORTHAND_AS_MEM +#ifndef UNITY_SHORTHAND_AS_NONE +#ifndef UNITY_SHORTHAND_AS_RAW +#define UNITY_SHORTHAND_AS_OLD +#endif +#endif +#endif +#endif + +/*----------------------------------------------- + * Command Line Argument Support + *-----------------------------------------------*/ + +#ifdef UNITY_USE_COMMAND_LINE_ARGS +int UnityParseOptions(int argc, char** argv); +int UnityTestMatches(void); +#endif + +/*------------------------------------------------------- + * Basic Fail and Ignore + *-------------------------------------------------------*/ + +#define UNITY_TEST_FAIL(line, message) UnityFail( (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_IGNORE(line, message) UnityIgnore( (message), (UNITY_LINE_TYPE)(line)) + +/*------------------------------------------------------- + * Test Asserts + *-------------------------------------------------------*/ + +#define UNITY_TEST_ASSERT(condition, line, message) do { if (condition) { /* nothing*/ } else { UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), (message)); } } while (0) +#define UNITY_TEST_ASSERT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) == NULL), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_NOT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) != NULL), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) == 0), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_NOT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) != 0), (UNITY_LINE_TYPE)(line), (message)) + +#define UNITY_TEST_ASSERT_EQUAL_INT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_EQUAL_INT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_EQUAL_INT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_EQUAL_INT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_EQUAL_UINT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_EQUAL_UINT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_EQUAL_UINT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_EQUAL_UINT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_EQUAL_HEX8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_EQUAL_HEX16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_EQUAL_HEX32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_EQUAL_CHAR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) +#define UNITY_TEST_ASSERT_BITS(mask, expected, actual, line, message) UnityAssertBits((UNITY_INT)(mask), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line)) + +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_NOT_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_GREATER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_GREATER_THAN_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_SMALLER_THAN_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 ) (threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16) (threshold), (UNITY_INT)(UNITY_INT16) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32) (threshold), (UNITY_INT)(UNITY_INT32) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 ) (threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_INT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin( (delta), (UNITY_INT) (expected), (UNITY_INT) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_INT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 ) (expected), (UNITY_INT)(UNITY_INT8 ) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_INT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_INT16) (expected), (UNITY_INT)(UNITY_INT16) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_INT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_INT32) (expected), (UNITY_INT)(UNITY_INT32) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_UINT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin( (delta), (UNITY_INT) (expected), (UNITY_INT) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_UINT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_UINT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_UINT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_HEX8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_HEX16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_HEX32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_CHAR_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 ) (expected), (UNITY_INT)(UNITY_INT8 ) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) + +#define UNITY_TEST_ASSERT_INT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_ARRAY) + + +#define UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_PTR_TO_INT)(expected), (UNITY_PTR_TO_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER) +#define UNITY_TEST_ASSERT_EQUAL_STRING(expected, actual, line, message) UnityAssertEqualString((const char*)(expected), (const char*)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len, line, message) UnityAssertEqualStringLen((const char*)(expected), (const char*)(actual), (UNITY_UINT32)(len), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_MEMORY(expected, actual, len, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), 1, (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) + +#define UNITY_TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_ARRAY) + +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), (UNITY_INT_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), (UNITY_INT_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT16)(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT32)(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_PTR_TO_INT) (expected), (UNITY_POINTER_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_CHAR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_VAL) + +#ifdef UNITY_SUPPORT_64 +#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_NOT_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY) +#else +#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#endif + +#ifdef UNITY_EXCLUDE_FLOAT +#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#else +#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray((UNITY_FLOAT*)(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray(UnityFloatToPtr(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) +#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) +#endif + +#ifdef UNITY_EXCLUDE_DOUBLE +#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#else +#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray((UNITY_DOUBLE*)(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray(UnityDoubleToPtr(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) +#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) +#endif + +/* End of UNITY_INTERNALS_H */ +#endif diff --git a/integration-tests/src/bindings_tests/uuid.c b/integration-tests/src/bindings_tests/uuid.c new file mode 100644 index 000000000..869d0cc17 --- /dev/null +++ b/integration-tests/src/bindings_tests/uuid.c @@ -0,0 +1,44 @@ +#include +#include "unity.h" +#include "taskchampion.h" + +// creating UUIDs does not crash +static void test_uuid_creation(void) { + tc_uuid_new_v4(); + tc_uuid_nil(); +} + +// converting UUIDs from string works +static void test_uuid_conversion_to_string(void) { + TEST_ASSERT_EQUAL(TC_UUID_STRING_BYTES, 36); + + TCUuid u2 = tc_uuid_nil(); + + char u2str[TC_UUID_STRING_BYTES]; + tc_uuid_to_str(u2, u2str); + TEST_ASSERT_EQUAL_MEMORY("00000000-0000-0000-0000-000000000000", u2str, TC_UUID_STRING_BYTES); +} + +// converting invalid UUIDs from string fails as expected +static void test_uuid_invalid_string_fails(void) { + TCUuid u; + char ustr[36] = "not-a-valid-uuid"; + TEST_ASSERT_FALSE(tc_uuid_from_str(ustr, &u)); +} + +// converting invalid UTF-8 UUIDs from string fails as expected +static void test_uuid_bad_utf8(void) { + TCUuid u; + char ustr[36] = "\xf0\x28\x8c\xbc"; + TEST_ASSERT_FALSE(tc_uuid_from_str(ustr, &u)); +} + +int uuid_tests(void) { + UNITY_BEGIN(); + // each test case above should be named here, in order. + RUN_TEST(test_uuid_creation); + RUN_TEST(test_uuid_conversion_to_string); + RUN_TEST(test_uuid_invalid_string_fails); + RUN_TEST(test_uuid_bad_utf8); + return UNITY_END(); +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs new file mode 100644 index 000000000..71b0b274c --- /dev/null +++ b/integration-tests/src/lib.rs @@ -0,0 +1 @@ +pub mod bindings_tests; diff --git a/integration-tests/tests/bindings.rs b/integration-tests/tests/bindings.rs new file mode 100644 index 000000000..b4df3a85a --- /dev/null +++ b/integration-tests/tests/bindings.rs @@ -0,0 +1,15 @@ +macro_rules! suite( + { $s:ident } => { + #[test] + fn $s() { + assert_eq!(integration_tests::bindings_tests::$s(), 0); + } + }; +); + +// keep this list in sync with integration-tests/build.rs and +// integration-tests/src/bindings_tests/mod.rs +suite!(uuid_tests); +suite!(string_tests); +suite!(task_tests); +suite!(replica_tests); diff --git a/lib/build.rs b/lib/build.rs index d1ebc7bf1..462b707a4 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -7,10 +7,12 @@ fn main() { Builder::new() .with_crate(crate_dir) - .with_language(Language::C) .with_config(Config { + language: Language::C, cpp_compat: true, + sys_includes: vec!["stdbool.h".into(), "stdint.h".into()], usize_is_size_t: true, + no_includes: true, enumeration: EnumConfig { // this appears to still default to true for C enum_class: false, diff --git a/lib/src/replica.rs b/lib/src/replica.rs index c137b105a..08bab398f 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,14 +1,14 @@ use crate::{status::TCStatus, string::TCString, task::TCTask}; -use libc::c_char; -use std::ffi::CString; use taskchampion::{Replica, StorageConfig}; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. +/// +/// TCReplicas are not threadsafe. pub struct TCReplica { // TODO: make this a RefCell so that it can be take()n when holding a mut ref inner: Replica, - error: Option, + error: Option>, } /// Utility function to safely convert *mut TCReplica into &mut TCReplica @@ -17,6 +17,10 @@ fn rep_ref(rep: *mut TCReplica) -> &'static mut TCReplica { unsafe { &mut *rep } } +fn err_to_tcstring(e: impl std::string::ToString) -> TCString<'static> { + TCString::from(e.to_string()) +} + /// Utility function to allow using `?` notation to return an error value. fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T where @@ -26,41 +30,48 @@ where match f(&mut rep.inner) { Ok(v) => v, Err(e) => { - let error = e.to_string(); - let error = match CString::new(error.as_bytes()) { - Ok(e) => e, - Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(), - }; - rep.error = Some(error); + rep.error = Some(err_to_tcstring(e)); err_value } } } -/// Create a new TCReplica. -/// -/// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the -/// on-disk storage for this replica. The path argument is no longer referenced after return. -/// -/// Returns NULL on error; see tc_replica_error. -/// -/// TCReplicas are not threadsafe. +/// Create a new TCReplica with an in-memory database. The contents of the database will be +/// lost when it is freed. #[no_mangle] -pub extern "C" fn tc_replica_new<'a>(path: *mut TCString) -> *mut TCReplica { - let storage_res = if path.is_null() { - StorageConfig::InMemory.into_storage() - } else { - let path = TCString::from_arg(path); - StorageConfig::OnDisk { - taskdb_dir: path.to_path_buf(), - } +pub extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { + let storage = StorageConfig::InMemory .into_storage() - }; + .expect("in-memory always succeeds"); + Box::into_raw(Box::new(TCReplica { + inner: Replica::new(storage), + error: None, + })) +} + +/// Create a new TCReplica with an on-disk database. On error, a string is written to the +/// `error_out` parameter (if it is not NULL) and NULL is returned. +#[no_mangle] +pub extern "C" fn tc_replica_new_on_disk<'a>( + path: *mut TCString, + error_out: *mut *mut TCString, +) -> *mut TCReplica { + let path = TCString::from_arg(path); + let storage_res = StorageConfig::OnDisk { + taskdb_dir: path.to_path_buf(), + } + .into_storage(); let storage = match storage_res { Ok(storage) => storage, - // TODO: report errors somehow - Err(_) => return std::ptr::null_mut(), + Err(e) => { + if !error_out.is_null() { + unsafe { + *error_out = err_to_tcstring(e).return_val(); + } + } + return std::ptr::null_mut(); + } }; Box::into_raw(Box::new(TCReplica { @@ -111,15 +122,15 @@ pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica) -> i32 { } /// Get the latest error for a replica, or NULL if the last operation succeeded. -/// -/// The returned string is valid until the next replica operation. +/// Subsequent calls to this function will return NULL. The caller must free the +/// returned string. #[no_mangle] -pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *const c_char { - let rep: &'a TCReplica = rep_ref(rep); - if let Some(ref e) = rep.error { - e.as_ptr() +pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'static> { + let rep: &'a mut TCReplica = rep_ref(rep); + if let Some(tcstring) = rep.error.take() { + tcstring.return_val() } else { - std::ptr::null() + std::ptr::null_mut() } } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 560f23eb1..02e2284fd 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -1,131 +1,185 @@ -#include -#include -#include -#include -#include -#include +#include +#include -/// The status of a task, as defined by the task data model. -enum TCStatus { +/** + * The status of a task, as defined by the task data model. + */ +typedef enum TCStatus { TC_STATUS_PENDING, TC_STATUS_COMPLETED, TC_STATUS_DELETED, - /// Unknown signifies a status in the task DB that was not - /// recognized. + /** + * 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. -struct TCReplica; +/** + * A replica represents an instance of a user's task data, providing an easy interface + * for querying and modifying that data. + * + * TCReplicas are not threadsafe. + */ +typedef struct TCReplica TCReplica; -/// TCString supports passing strings into and out of the TaskChampion API. -/// -/// Unless specified otherwise, functions in this API take ownership of a TCString when it appears -/// as a function argument, and transfer ownership to the caller when the TCString appears as a -/// return value or output argument. -struct TCString; +/** + * TCString supports passing strings into and out of the TaskChampion API. + * + * Unless specified otherwise, functions in this API take ownership of a TCString when it appears + * as a function argument, and transfer ownership to the caller when the TCString appears as a + * return value or output argument. + */ +typedef struct TCString TCString; -/// A task, as publicly exposed by this library. -/// -/// A task carries no reference to the replica that created it, and can -/// be used until it is freed or converted to a TaskMut. -struct TCTask; +/** + * A task, as publicly exposed by this library. + * + * A task carries no reference to the replica that created it, and can + * be used until it is freed or converted to a TaskMut. + */ +typedef struct TCTask TCTask; -/// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. -/// Uuids are typically treated as opaque, but the bytes are available in big-endian format. -/// -struct TCUuid { +/** + * 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; +#ifdef __cplusplus extern "C" { +#endif // __cplusplus extern const size_t TC_UUID_STRING_BYTES; -/// Create a new TCReplica. -/// -/// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the -/// on-disk storage for this replica. The path argument is no longer referenced after return. -/// -/// Returns NULL on error; see tc_replica_error. -/// -/// TCReplicas are not threadsafe. -TCReplica *tc_replica_new(TCString *path); +/** + * Create a new TCReplica with an in-memory database. The contents of the database will be + * lost when it is freed. + */ +struct TCReplica *tc_replica_new_in_memory(void); -/// Create a new task. The task must not already exist. -/// -/// Returns the task, or NULL on error. -TCTask *tc_replica_new_task(TCReplica *rep, TCStatus status, TCString *description); +/** + * Create a new TCReplica with an on-disk database. On error, a string is written to the + * `error_out` parameter (if it is not NULL) and NULL is returned. + */ +struct TCReplica *tc_replica_new_on_disk(struct TCString *path, struct TCString **error_out); -/// Undo local operations until the most recent UndoPoint. -/// -/// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were -/// undone. -int32_t tc_replica_undo(TCReplica *rep); +/** + * 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); -/// Get the latest error for a replica, or NULL if the last operation succeeded. -/// -/// The returned string is valid until the next replica operation. -const char *tc_replica_error(TCReplica *rep); +/** + * Undo local operations until the most recent UndoPoint. + * + * Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were + * undone. + */ +int32_t tc_replica_undo(struct TCReplica *rep); -/// Free a TCReplica. -void tc_replica_free(TCReplica *rep); +/** + * Get the latest error for a replica, or NULL if the last operation succeeded. + * Subsequent calls to this function will return NULL. The caller must free the + * returned string. + */ +struct TCString *tc_replica_error(struct TCReplica *rep); -/// Create a new TCString referencing the given C string. The C string must remain valid until -/// after the TCString is freed. It's typically easiest to ensure this by using a static string. -TCString *tc_string_new(const char *cstr); +/** + * Free a TCReplica. + */ +void tc_replica_free(struct TCReplica *rep); -/// Create a new TCString by cloning the content of the given C string. -TCString *tc_string_clone(const char *cstr); +/** + * Create a new TCString referencing the given C string. The C string must remain valid until + * after the TCString is freed. It's typically easiest to ensure this by using a static string. + */ +struct TCString *tc_string_new(const char *cstr); -/// Create a new TCString containing the given string with the given length. This allows creation -/// of strings containing embedded NUL characters. If the given string is not valid UTF-8, this -/// function will return NULL. -TCString *tc_string_clone_with_len(const char *buf, size_t len); +/** + * Create a new TCString by cloning the content of the given C string. + */ +struct TCString *tc_string_clone(const char *cstr); -/// Get the content of the string as a regular C string. The given string must not be NULL. The -/// returned value is NULL if the string contains NUL bytes. The returned string is valid until -/// the TCString is freed or passed to another TC API function. -/// -/// This function does _not_ take ownership of the TCString. -const char *tc_string_content(TCString *tcstring); +/** + * Create a new TCString containing the given string with the given length. This allows creation + * of strings containing embedded NUL characters. If the given string is not valid UTF-8, this + * function will return NULL. + */ +struct TCString *tc_string_clone_with_len(const char *buf, size_t len); -/// 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. The returned string is -/// valid until the TCString is freed or passed to another TC API function. -/// -/// This function does _not_ take ownership of the TCString. -const char *tc_string_content_with_len(TCString *tcstring, size_t *len_out); +/** + * Get the content of the string as a regular C string. The given string must not be NULL. The + * returned value is NULL if the string contains NUL bytes. The returned string is valid until + * the TCString is freed or passed to another TC API function. + * + * This function does _not_ take ownership of the TCString. + */ +const char *tc_string_content(struct TCString *tcstring); -/// Free a TCString. -void tc_string_free(TCString *string); +/** + * 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. The returned string is + * valid until the TCString is freed or passed to another TC API function. + * + * This function does _not_ take ownership of the TCString. + */ +const char *tc_string_content_with_len(struct TCString *tcstring, size_t *len_out); -/// Get a task's UUID. -TCUuid tc_task_get_uuid(const TCTask *task); +/** + * Free a TCString. + */ +void tc_string_free(struct TCString *string); -/// Get a task's status. -TCStatus tc_task_get_status(const TCTask *task); +/** + * Get a task's UUID. + */ +struct TCUuid tc_task_get_uuid(const struct TCTask *task); -/// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it -/// contains embedded NUL characters). -TCString *tc_task_get_description(const TCTask *task); +/** + * Get a task's status. + */ +enum TCStatus tc_task_get_status(const struct TCTask *task); -/// Free a task. -void tc_task_free(TCTask *task); +/** + * Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it + * contains embedded NUL characters). + */ +struct TCString *tc_task_get_description(const struct TCTask *task); -/// Create a new, randomly-generated UUID. -TCUuid tc_uuid_new_v4(); +/** + * Free a task. + */ +void tc_task_free(struct TCTask *task); -/// Create a new UUID with the nil value. -TCUuid tc_uuid_nil(); +/** + * Create a new, randomly-generated UUID. + */ +struct TCUuid tc_uuid_new_v4(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. -void tc_uuid_to_str(TCUuid uuid, char *out); +/** + * Create a new UUID with the nil value. + */ +struct TCUuid tc_uuid_nil(void); -/// Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns -/// false on failure. -bool tc_uuid_from_str(const char *val, TCUuid *out); +/** + * 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_str(struct TCUuid uuid, char *out); +/** + * Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns + * false on failure. + */ +bool tc_uuid_from_str(const char *val, struct TCUuid *out); + +#ifdef __cplusplus } // extern "C" +#endif // __cplusplus From ca0279a73f12bb6c66bb4e1dab3d1b9a04c1d52c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 25 Jan 2022 01:32:22 +0000 Subject: [PATCH 441/548] move existing integration tests into new crate --- Cargo.lock | 23 +++++++------------ Cargo.toml | 1 - integration-tests/Cargo.toml | 10 ++++++++ .../tests/cross-sync.rs | 0 .../tests/snapshots.rs | 0 replica-server-tests/Cargo.toml | 21 ----------------- replica-server-tests/src/lib.rs | 1 - 7 files changed, 18 insertions(+), 38 deletions(-) rename {replica-server-tests => integration-tests}/tests/cross-sync.rs (100%) rename {replica-server-tests => integration-tests}/tests/snapshots.rs (100%) delete mode 100644 replica-server-tests/Cargo.toml delete mode 100644 replica-server-tests/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7924f76e9..b2b905807 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1524,9 +1524,17 @@ dependencies = [ name = "integration-tests" version = "0.4.1" dependencies = [ + "actix-rt", + "actix-web", + "anyhow", "cc", + "env_logger 0.8.4", + "log", + "pretty_assertions", "taskchampion", "taskchampion-lib", + "taskchampion-sync-server", + "tempfile", ] [[package]] @@ -2488,21 +2496,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "replica-server-tests" -version = "0.4.1" -dependencies = [ - "actix-rt", - "actix-web", - "anyhow", - "env_logger 0.8.4", - "log", - "pretty_assertions", - "taskchampion", - "taskchampion-sync-server", - "tempfile", -] - [[package]] name = "resolv-conf" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index 09c0a9677..18399678b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "taskchampion", "cli", "sync-server", - "replica-server-tests", "lib", "integration-tests", ] diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index f351e17d3..71f39b6e6 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -8,6 +8,16 @@ build = "build.rs" [dependencies] taskchampion = { path = "../taskchampion" } +taskchampion-sync-server = { path = "../sync-server" } + +[dev-dependencies] +anyhow = "1.0" +actix-web = "^3.3.2" +actix-rt = "^1.1.1" +tempfile = "3" +pretty_assertions = "1" +log = "^0.4.14" +env_logger = "^0.8.3" [build-dependencies] cc = "1.0" diff --git a/replica-server-tests/tests/cross-sync.rs b/integration-tests/tests/cross-sync.rs similarity index 100% rename from replica-server-tests/tests/cross-sync.rs rename to integration-tests/tests/cross-sync.rs diff --git a/replica-server-tests/tests/snapshots.rs b/integration-tests/tests/snapshots.rs similarity index 100% rename from replica-server-tests/tests/snapshots.rs rename to integration-tests/tests/snapshots.rs diff --git a/replica-server-tests/Cargo.toml b/replica-server-tests/Cargo.toml deleted file mode 100644 index efe369b3c..000000000 --- a/replica-server-tests/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "replica-server-tests" -version = "0.4.1" -authors = ["Dustin J. Mitchell "] -edition = "2018" -publish = false - -[dependencies.taskchampion-sync-server] -path = "../sync-server" - -[dependencies.taskchampion] -path = "../taskchampion" - -[dev-dependencies] -anyhow = "1.0" -actix-web = "^3.3.2" -actix-rt = "^1.1.1" -tempfile = "3" -pretty_assertions = "1" -log = "^0.4.14" -env_logger = "^0.8.3" diff --git a/replica-server-tests/src/lib.rs b/replica-server-tests/src/lib.rs deleted file mode 100644 index e783558fc..000000000 --- a/replica-server-tests/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -// test-only crate From 0d68e65354fdb9d83a4d4e6270b68a6aeafb8209 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 25 Jan 2022 23:29:52 +0000 Subject: [PATCH 442/548] some polish on strings --- .../src/bindings_tests/replica.c | 4 +- integration-tests/src/bindings_tests/string.c | 4 +- integration-tests/src/bindings_tests/task.c | 2 +- lib/src/lib.rs | 1 + lib/src/replica.rs | 20 ++++++-- lib/src/result.rs | 10 ++++ lib/src/string.rs | 37 +++++++++++--- lib/taskchampion.h | 51 +++++++++++++++---- 8 files changed, 100 insertions(+), 29 deletions(-) create mode 100644 lib/src/result.rs diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 71eef0efe..613b15666 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -13,7 +13,7 @@ static void test_replica_creation(void) { // creating an on-disk replica does not crash static void test_replica_creation_disk(void) { - TCReplica *rep = tc_replica_new_on_disk(tc_string_new("test-db"), NULL); + TCReplica *rep = tc_replica_new_on_disk(tc_string_borrow("test-db"), NULL); TEST_ASSERT_NOT_NULL(rep); TEST_ASSERT_NULL(tc_replica_error(rep)); tc_replica_free(rep); @@ -24,7 +24,7 @@ static void test_replica_undo_empty(void) { TCReplica *rep = tc_replica_new_in_memory(); TEST_ASSERT_NULL(tc_replica_error(rep)); int rv = tc_replica_undo(rep); - TEST_ASSERT_EQUAL(0, rv); + TEST_ASSERT_EQUAL(TC_RESULT_FALSE, rv); TEST_ASSERT_NULL(tc_replica_error(rep)); tc_replica_free(rep); } diff --git a/integration-tests/src/bindings_tests/string.c b/integration-tests/src/bindings_tests/string.c index ad6c62559..9926d79bd 100644 --- a/integration-tests/src/bindings_tests/string.c +++ b/integration-tests/src/bindings_tests/string.c @@ -5,7 +5,7 @@ // creating strings does not crash static void test_string_creation(void) { - TCString *s = tc_string_new("abcdef"); + TCString *s = tc_string_borrow("abcdef"); tc_string_free(s); } @@ -22,7 +22,7 @@ static void test_string_cloning(void) { // borrowed strings echo back their content static void test_string_borrowed_strings_echo(void) { - TCString *s = tc_string_new("abcdef"); + TCString *s = tc_string_borrow("abcdef"); TEST_ASSERT_NOT_NULL(s); TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(s)); diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 9f77b8170..b0bbc4117 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -11,7 +11,7 @@ static void test_task_creation(void) { TCTask *task = tc_replica_new_task( rep, TC_STATUS_PENDING, - tc_string_new("my task")); + tc_string_borrow("my task")); TEST_ASSERT_NOT_NULL(task); TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index a97d0f732..649febbd9 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,4 +1,5 @@ pub mod replica; +pub mod result; pub mod status; pub mod string; pub mod task; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 08bab398f..7ae4cc286 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,4 +1,4 @@ -use crate::{status::TCStatus, string::TCString, task::TCTask}; +use crate::{result::TCResult, status::TCStatus, string::TCString, task::TCTask}; use taskchampion::{Replica, StorageConfig}; /// A replica represents an instance of a user's task data, providing an easy interface @@ -114,11 +114,21 @@ pub extern "C" fn tc_replica_new_task<'a>( /// Undo local operations until the most recent UndoPoint. /// -/// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were -/// undone. +/// Returns TC_RESULT_TRUE if an undo occurred, TC_RESULT_FALSE if there are no operations +/// to be undone, or TC_RESULT_ERROR on error. #[no_mangle] -pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica) -> i32 { - wrap(rep, |rep| Ok(if rep.undo()? { 1 } else { 0 }), -1) +pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica) -> TCResult { + wrap( + rep, + |rep| { + Ok(if rep.undo()? { + TCResult::True + } else { + TCResult::False + }) + }, + TCResult::Error, + ) } /// Get the latest error for a replica, or NULL if the last operation succeeded. diff --git a/lib/src/result.rs b/lib/src/result.rs new file mode 100644 index 000000000..e807e4690 --- /dev/null +++ b/lib/src/result.rs @@ -0,0 +1,10 @@ +/// A result combines a boolean success value with +/// an error response. It is equivalent to `Result`. +/// cbindgen:prefix-with-name +/// cbindgen:rename-all=ScreamingSnakeCase +#[repr(C)] +pub enum TCResult { + True, + False, + Error, +} diff --git a/lib/src/string.rs b/lib/src/string.rs index 1816d2a7d..1cb716b62 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -2,11 +2,20 @@ use std::ffi::{CStr, CString, OsStr}; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; -/// TCString supports passing strings into and out of the TaskChampion API. +// TODO: is utf-8-ness always checked? (no) when? + +/// TCString supports passing strings into and out of the TaskChampion API. A string must contain +/// valid UTF-8, and can contain embedded NUL characters. Strings containing such 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. /// -/// Unless specified otherwise, functions in this API take ownership of a TCString when it appears -/// as a function argument, and transfer ownership to the caller when the TCString appears as a -/// return value or output argument. +/// Unless specified otherwise, functions in this API take ownership of a TCString when it is given +/// as a function argument, and free the string before returning. Thus the following is valid: +/// +/// When a TCString appears as a return value or output argument, it is the responsibility of the +/// caller to free the string. pub enum TCString<'a> { CString(CString), CStr(&'a CStr), @@ -82,13 +91,24 @@ impl<'a> From<&str> for TCString<'a> { /// Create a new TCString referencing the given C string. The C string must remain valid 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 C string itself. +/// The underlying string once the TCString has been freed. Among other times, TCStrings are +/// freed when they are passed to API functions (unless documented otherwise). For example: +/// +/// ``` +/// 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 +/// ``` #[no_mangle] -pub extern "C" fn tc_string_new(cstr: *const libc::c_char) -> *mut TCString<'static> { +pub extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<'static> { let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; TCString::CStr(cstr).return_val() } -/// Create a new TCString by cloning the content of the given C string. +/// 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. #[no_mangle] pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'static> { let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; @@ -96,8 +116,9 @@ pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'s } /// Create a new TCString containing the given string with the given length. This allows creation -/// of strings containing embedded NUL characters. If the given string is not valid UTF-8, this -/// function will return NULL. +/// 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. If the +/// given string is not valid UTF-8, this function will return NULL. #[no_mangle] pub extern "C" fn tc_string_clone_with_len( buf: *const libc::c_char, diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 02e2284fd..9e3354c14 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -1,6 +1,16 @@ #include #include +/** + * A result combines a boolean success value with + * an error response. It is equivalent to `Result`. + */ +typedef enum TCResult { + TC_RESULT_TRUE, + TC_RESULT_FALSE, + TC_RESULT_ERROR, +} TCResult; + /** * The status of a task, as defined by the task data model. */ @@ -24,11 +34,18 @@ typedef enum TCStatus { typedef struct TCReplica TCReplica; /** - * TCString supports passing strings into and out of the TaskChampion API. + * TCString supports passing strings into and out of the TaskChampion API. A string must contain + * valid UTF-8, and can contain embedded NUL characters. Strings containing such 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. * - * Unless specified otherwise, functions in this API take ownership of a TCString when it appears - * as a function argument, and transfer ownership to the caller when the TCString appears as a - * return value or output argument. + * Unless specified otherwise, functions in this API take ownership of a TCString when it is given + * as a function argument, and free the string before returning. Thus the following is valid: + * + * When a TCString appears as a return value or output argument, it is the responsibility of the + * caller to free the string. */ typedef struct TCString TCString; @@ -79,10 +96,10 @@ struct TCTask *tc_replica_new_task(struct TCReplica *rep, /** * Undo local operations until the most recent UndoPoint. * - * Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were - * undone. + * Returns TC_RESULT_TRUE if an undo occurred, TC_RESULT_FALSE if there are no operations + * to be undone, or TC_RESULT_ERROR on error. */ -int32_t tc_replica_undo(struct TCReplica *rep); +enum TCResult tc_replica_undo(struct TCReplica *rep); /** * Get the latest error for a replica, or NULL if the last operation succeeded. @@ -99,18 +116,30 @@ void tc_replica_free(struct TCReplica *rep); /** * Create a new TCString referencing the given C string. The C string must remain valid 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 C string itself. + * The underlying string once the TCString has been freed. Among other times, TCStrings are + * freed when they are passed to API functions (unless documented otherwise). For example: + * + * ``` + * 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_new(const char *cstr); +struct TCString *tc_string_borrow(const char *cstr); /** - * Create a new TCString by cloning the content of the given C string. + * 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 TCString containing the given string with the given length. This allows creation - * of strings containing embedded NUL characters. If the given string is not valid UTF-8, this - * function will return NULL. + * 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. If the + * given string is not valid UTF-8, this function will return NULL. */ struct TCString *tc_string_clone_with_len(const char *buf, size_t len); From c5ff2398f79c9abcc7f47eb2d73c456c65e7b557 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 26 Jan 2022 00:35:03 +0000 Subject: [PATCH 443/548] ignore test-db --- integration-tests/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 integration-tests/.gitignore diff --git a/integration-tests/.gitignore b/integration-tests/.gitignore new file mode 100644 index 000000000..e525de89a --- /dev/null +++ b/integration-tests/.gitignore @@ -0,0 +1 @@ +test-db From f8cffb798c963a92fa4e19d59343ab18358db38c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 26 Jan 2022 00:36:33 +0000 Subject: [PATCH 444/548] fix confusing doc string --- lib/src/string.rs | 7 ++++--- lib/taskchampion.h | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/src/string.rs b/lib/src/string.rs index 1cb716b62..1e3968004 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -92,9 +92,10 @@ impl<'a> From<&str> for TCString<'a> { /// Create a new TCString referencing the given C string. The C string must remain valid 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 C string itself. -/// The underlying string once the TCString has been freed. Among other times, TCStrings are -/// freed when they are passed to API functions (unless documented otherwise). For example: +/// NOTE: this function does _not_ take responsibility for freeing the C string itself. The +/// underlying string can be freed once the TCString referencing it has been freed. +/// +/// For example: /// /// ``` /// char *url = get_item_url(..); // dynamically allocate C string diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 9e3354c14..786ead2c0 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -117,9 +117,10 @@ void tc_replica_free(struct TCReplica *rep); * Create a new TCString referencing the given C string. The C string must remain valid 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 C string itself. - * The underlying string once the TCString has been freed. Among other times, TCStrings are - * freed when they are passed to API functions (unless documented otherwise). For example: + * NOTE: this function does _not_ take responsibility for freeing the C string itself. The + * underlying string can be freed once the TCString referencing it has been freed. + * + * For example: * * ``` * char *url = get_item_url(..); // dynamically allocate C string From dd87f7da1ed1f943a86beb58a296f5dce10c89ce Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 26 Jan 2022 00:56:48 +0000 Subject: [PATCH 445/548] simplify defining suites --- integration-tests/README.md | 6 +-- integration-tests/build.rs | 46 ++++++++++++++------- integration-tests/src/bindings_tests/mod.rs | 6 +-- integration-tests/tests/bindings.rs | 7 +--- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/integration-tests/README.md b/integration-tests/README.md index 21ca29c3b..b0a94d050 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -26,7 +26,5 @@ Keep the `RUN_TEST`s in the same order as the functions they call. To add a suite, -1. Add a new C file in `integration-tests/src/bindings_tests/`. -1. Add a new `.file(..)` to build that file in `integration-tests/build.rs`. -1. Add a `suite!(..)` to `integration-tests/src/bindings_tests/mod.rs`. -1. Add a `suite!(..)` to `integration-tests/tests/bindings.rs`. +1. Add a new C file in `integration-tests/src/bindings_tests/`, based off of one of hte others. +1. Add a the suite name to `suites` in `integration-tests/build.rs`. diff --git a/integration-tests/build.rs b/integration-tests/build.rs index 78c531024..04cb6b85d 100644 --- a/integration-tests/build.rs +++ b/integration-tests/build.rs @@ -1,36 +1,50 @@ -fn main() { +use std::env; +use std::fs; +use std::path::Path; + +fn build_libtaskchampion(suites: &[&'static str]) { // This crate has taskchampion-lib in its build-dependencies, so // libtaskchampion.so should be built already. Hopefully it's in target/$PROFILE, and hopefully // it's named libtaskchampion.so and not something else - let mut libtaskchampion = std::env::current_dir().unwrap(); + let mut libtaskchampion = env::current_dir().unwrap(); libtaskchampion.pop(); libtaskchampion.push("target"); - libtaskchampion.push(std::env::var("PROFILE").unwrap()); + libtaskchampion.push(env::var("PROFILE").unwrap()); libtaskchampion.push("deps"); libtaskchampion.push("libtaskchampion.so"); - println!("cargo:rerun-if-changed=build.rs"); - let mut build = cc::Build::new(); build.object(libtaskchampion); build.include("../lib"); build.include("src/bindings_tests/unity"); build.file("src/bindings_tests/unity/unity.c"); - let files = &[ - "src/bindings_tests/test.c", - // keep this list in sync with integration-tests/src/bindings_tests/mod.rs and - // integration-tests/tests/bindings.rs - "src/bindings_tests/uuid.c", - "src/bindings_tests/string.c", - "src/bindings_tests/task.c", - "src/bindings_tests/replica.c", - ]; - + let mut files = vec!["src/bindings_tests/test.c".to_string()]; + for suite in suites { + files.push(format!("src/bindings_tests/{}.c", suite)); + } for file in files { - build.file(file); + build.file(&file); println!("cargo:rerun-if-changed={}", file); } build.compile("bindings-tests"); } + +fn make_suite_file(suites: &[&'static str]) { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("bindings_test_suites.rs"); + let mut content = String::new(); + for suite in suites { + content.push_str(format!("suite!({}_tests);\n", suite).as_ref()); + } + fs::write(&dest_path, content).unwrap(); +} + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + let suites = &["uuid", "string", "task", "replica"]; + build_libtaskchampion(suites); + make_suite_file(suites); +} diff --git a/integration-tests/src/bindings_tests/mod.rs b/integration-tests/src/bindings_tests/mod.rs index a9cf8119e..4eba5b421 100644 --- a/integration-tests/src/bindings_tests/mod.rs +++ b/integration-tests/src/bindings_tests/mod.rs @@ -13,8 +13,4 @@ macro_rules! suite( }; ); -// keep this list in sync with integration-tests/build.rs and integration-tests/tests/bindings.rs. -suite!(uuid_tests); -suite!(string_tests); -suite!(task_tests); -suite!(replica_tests); +include!(concat!(env!("OUT_DIR"), "/bindings_test_suites.rs")); diff --git a/integration-tests/tests/bindings.rs b/integration-tests/tests/bindings.rs index b4df3a85a..d4574c92f 100644 --- a/integration-tests/tests/bindings.rs +++ b/integration-tests/tests/bindings.rs @@ -7,9 +7,4 @@ macro_rules! suite( }; ); -// keep this list in sync with integration-tests/build.rs and -// integration-tests/src/bindings_tests/mod.rs -suite!(uuid_tests); -suite!(string_tests); -suite!(task_tests); -suite!(replica_tests); +include!(concat!(env!("OUT_DIR"), "/bindings_test_suites.rs")); From 8f703fd63aa562ee96794e71352c515552183416 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 26 Jan 2022 01:29:16 +0000 Subject: [PATCH 446/548] use TCString in UUIDs --- integration-tests/src/bindings_tests/uuid.c | 37 ++++++++++++++++----- lib/src/replica.rs | 16 ++++----- lib/src/uuid.rs | 29 +++++++++++----- lib/taskchampion.h | 10 ++++-- 4 files changed, 63 insertions(+), 29 deletions(-) diff --git a/integration-tests/src/bindings_tests/uuid.c b/integration-tests/src/bindings_tests/uuid.c index 869d0cc17..2e8d0c806 100644 --- a/integration-tests/src/bindings_tests/uuid.c +++ b/integration-tests/src/bindings_tests/uuid.c @@ -8,36 +8,57 @@ static void test_uuid_creation(void) { tc_uuid_nil(); } -// converting UUIDs from string works -static void test_uuid_conversion_to_string(void) { +// converting UUIDs to a buf works +static void test_uuid_to_buf(void) { TEST_ASSERT_EQUAL(TC_UUID_STRING_BYTES, 36); TCUuid u2 = tc_uuid_nil(); char u2str[TC_UUID_STRING_BYTES]; - tc_uuid_to_str(u2, u2str); + tc_uuid_to_buf(u2, u2str); TEST_ASSERT_EQUAL_MEMORY("00000000-0000-0000-0000-000000000000", u2str, TC_UUID_STRING_BYTES); } +// converting UUIDs to a buf works +static void test_uuid_to_str(void) { + TCUuid u = tc_uuid_nil(); + TCString *s = tc_uuid_to_str(u); + TEST_ASSERT_EQUAL_STRING( + "00000000-0000-0000-0000-000000000000", + tc_string_content(s)); + tc_string_free(s); +} + +// converting valid UUIDs from string works +static void test_uuid_valid_from_str(void) { + TCUuid u; + char *ustr = "23cb25e0-5d1a-4932-8131-594ac6d3a843"; + TEST_ASSERT_TRUE(tc_uuid_from_str(tc_string_borrow(ustr), &u)); + TEST_ASSERT_EQUAL(0x23, u.bytes[0]); + TEST_ASSERT_EQUAL(0x43, u.bytes[15]); +} + // converting invalid UUIDs from string fails as expected static void test_uuid_invalid_string_fails(void) { TCUuid u; - char ustr[36] = "not-a-valid-uuid"; - TEST_ASSERT_FALSE(tc_uuid_from_str(ustr, &u)); + char *ustr = "not-a-valid-uuid"; + TEST_ASSERT_FALSE(tc_uuid_from_str(tc_string_borrow(ustr), &u)); } // converting invalid UTF-8 UUIDs from string fails as expected static void test_uuid_bad_utf8(void) { TCUuid u; - char ustr[36] = "\xf0\x28\x8c\xbc"; - TEST_ASSERT_FALSE(tc_uuid_from_str(ustr, &u)); + char *ustr = "\xf0\x28\x8c\xbc"; + TEST_ASSERT_FALSE(tc_uuid_from_str(tc_string_borrow(ustr), &u)); } int uuid_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. RUN_TEST(test_uuid_creation); - RUN_TEST(test_uuid_conversion_to_string); + RUN_TEST(test_uuid_valid_from_str); + RUN_TEST(test_uuid_to_buf); + RUN_TEST(test_uuid_to_str); RUN_TEST(test_uuid_invalid_string_fails); RUN_TEST(test_uuid_bad_utf8); return UNITY_END(); diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 7ae4cc286..3d2eafd2f 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -80,13 +80,10 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( })) } -/* - * TODO: - * - tc_replica_all_tasks - * - tc_replica_all_task_uuids - * - tc_replica_working_set - * - tc_replica_get_task - */ +// TODO: tc_replica_all_tasks +// TODO: tc_replica_all_task_uuids +// TODO: tc_replica_working_set +// TODO: tc_replica_get_task /// Create a new task. The task must not already exist. /// @@ -108,9 +105,8 @@ pub extern "C" fn tc_replica_new_task<'a>( ) } -/* - tc_replica_import_task_with_uuid - * - tc_replica_sync - */ +// TODO: tc_replica_import_task_with_uuid +// TODO: tc_replica_sync /// Undo local operations until the most recent UndoPoint. /// diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 5e3492bea..f25399909 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -1,3 +1,4 @@ +use crate::string::TCString; use libc; use taskchampion::Uuid; @@ -33,31 +34,41 @@ pub extern "C" fn tc_uuid_nil() -> TCUuid { } /// Length, in bytes, of a C string containing a TCUuid. +// TODO: why not a const? #[no_mangle] pub static TC_UUID_STRING_BYTES: usize = ::uuid::adapter::Hyphenated::LENGTH; /// 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. #[no_mangle] -pub extern "C" fn tc_uuid_to_str<'a>(uuid: TCUuid, out: *mut libc::c_char) { - debug_assert!(!out.is_null()); +pub extern "C" fn tc_uuid_to_buf<'a>(uuid: TCUuid, buf: *mut libc::c_char) { + debug_assert!(!buf.is_null()); let buf: &'a mut [u8] = unsafe { - std::slice::from_raw_parts_mut(out as *mut u8, ::uuid::adapter::Hyphenated::LENGTH) + std::slice::from_raw_parts_mut(buf as *mut u8, ::uuid::adapter::Hyphenated::LENGTH) }; let uuid: Uuid = uuid.into(); uuid.to_hyphenated().encode_lower(buf); } +/// 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. +#[no_mangle] +pub extern "C" fn tc_uuid_to_str(uuid: TCUuid) -> *mut TCString<'static> { + let uuid: Uuid = uuid.into(); + let s = uuid.to_string(); + TCString::from(s).return_val() +} + /// Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns /// false on failure. #[no_mangle] -pub extern "C" fn tc_uuid_from_str<'a>(val: *const libc::c_char, out: *mut TCUuid) -> bool { - debug_assert!(!val.is_null()); - debug_assert!(!out.is_null()); - let slice = unsafe { std::slice::from_raw_parts(val as *const u8, TC_UUID_STRING_BYTES) }; - if let Ok(s) = std::str::from_utf8(slice) { +pub extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> bool { + debug_assert!(!s.is_null()); + debug_assert!(!uuid_out.is_null()); + let s = TCString::from_arg(s); + if let Ok(s) = s.as_str() { if let Ok(u) = Uuid::parse_str(s) { - unsafe { *out = u.into() }; + unsafe { *uuid_out = u.into() }; return true; } } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 786ead2c0..b09a8f4d5 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -202,13 +202,19 @@ 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. */ -void tc_uuid_to_str(struct TCUuid uuid, char *out); +void tc_uuid_to_buf(struct TCUuid uuid, char *buf); + +/** + * 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. + */ +struct TCString *tc_uuid_to_str(struct TCUuid uuid); /** * Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns * false on failure. */ -bool tc_uuid_from_str(const char *val, struct TCUuid *out); +bool tc_uuid_from_str(struct TCString *s, struct TCUuid *uuid_out); #ifdef __cplusplus } // extern "C" From 96b59dd5b2bd99cb1fe05463ec17c46492e7c5e6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 26 Jan 2022 01:38:25 +0000 Subject: [PATCH 447/548] serialize C integration tests --- Cargo.lock | 1 + integration-tests/Cargo.toml | 1 + integration-tests/tests/bindings.rs | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index b2b905807..094b83445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1529,6 +1529,7 @@ dependencies = [ "anyhow", "cc", "env_logger 0.8.4", + "lazy_static", "log", "pretty_assertions", "taskchampion", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 71f39b6e6..2b4a3bd8a 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -18,6 +18,7 @@ tempfile = "3" pretty_assertions = "1" log = "^0.4.14" env_logger = "^0.8.3" +lazy_static = "1" [build-dependencies] cc = "1.0" diff --git a/integration-tests/tests/bindings.rs b/integration-tests/tests/bindings.rs index d4574c92f..9f14ec47f 100644 --- a/integration-tests/tests/bindings.rs +++ b/integration-tests/tests/bindings.rs @@ -1,7 +1,17 @@ +use lazy_static::lazy_static; +use std::sync::Mutex; + +lazy_static! { + // the C library running the tests is not reentrant, so we use a mutex to ensure that only one + // test runs at a time. + static ref MUTEX: Mutex<()> = Mutex::new(()); +} + macro_rules! suite( { $s:ident } => { #[test] fn $s() { + let _guard = MUTEX.lock().unwrap(); assert_eq!(integration_tests::bindings_tests::$s(), 0); } }; From f3b73ca0e4f120df773a69ccc7d8d6515ac62159 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 26 Jan 2022 01:49:06 +0000 Subject: [PATCH 448/548] add task_import_with_uuid --- integration-tests/src/bindings_tests/task.c | 23 ++++++++++++++++++ lib/src/replica.rs | 26 +++++++++++++++++---- lib/taskchampion.h | 7 ++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index b0bbc4117..bd9e14f4b 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -26,9 +26,32 @@ static void test_task_creation(void) { tc_replica_free(rep); } +// importing a task succeeds and the resulting task looks good +static void test_task_import(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCUuid uuid; + TEST_ASSERT_TRUE(tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); + TCTask *task = tc_replica_import_task_with_uuid(rep, uuid); + TEST_ASSERT_NOT_NULL(task); + + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + + TCString *desc = tc_task_get_description(task); + TEST_ASSERT_NOT_NULL(desc); + TEST_ASSERT_EQUAL_STRING("", tc_string_content(desc)); // default value + tc_string_free(desc); + + tc_task_free(task); + + tc_replica_free(rep); +} + int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. RUN_TEST(test_task_creation); + RUN_TEST(test_task_import); return UNITY_END(); } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 3d2eafd2f..f77612b41 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,5 +1,5 @@ -use crate::{result::TCResult, status::TCStatus, string::TCString, task::TCTask}; -use taskchampion::{Replica, StorageConfig}; +use crate::{result::TCResult, status::TCStatus, string::TCString, task::TCTask, uuid::TCUuid}; +use taskchampion::{Replica, StorageConfig, Uuid}; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. @@ -89,7 +89,7 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( /// /// Returns the task, or NULL on error. #[no_mangle] -pub extern "C" fn tc_replica_new_task<'a>( +pub extern "C" fn tc_replica_new_task( rep: *mut TCReplica, status: TCStatus, description: *mut TCString, @@ -105,7 +105,25 @@ pub extern "C" fn tc_replica_new_task<'a>( ) } -// TODO: tc_replica_import_task_with_uuid +/// Create a new task. The task must not already exist. +/// +/// Returns the task, or NULL on error. +#[no_mangle] +pub extern "C" fn tc_replica_import_task_with_uuid( + rep: *mut TCReplica, + uuid: TCUuid, +) -> *mut TCTask { + wrap( + rep, + |rep| { + let uuid: Uuid = uuid.into(); + let task = rep.import_task_with_uuid(uuid)?; + Ok(TCTask::as_ptr(task)) + }, + std::ptr::null_mut(), + ) +} + // TODO: tc_replica_sync /// Undo local operations until the most recent UndoPoint. diff --git a/lib/taskchampion.h b/lib/taskchampion.h index b09a8f4d5..a82ef49a3 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -93,6 +93,13 @@ 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 uuid); + /** * Undo local operations until the most recent UndoPoint. * From e1c348b96e368df0b566292fc3560e3d4b5e70c6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 26 Jan 2022 02:15:52 +0000 Subject: [PATCH 449/548] tc_replica_get_task --- .../src/bindings_tests/replica.c | 78 +++++++++++++++++++ integration-tests/src/bindings_tests/task.c | 23 ------ lib/src/replica.rs | 22 +++++- lib/taskchampion.h | 8 ++ 4 files changed, 107 insertions(+), 24 deletions(-) diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 613b15666..756c9acfe 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -29,11 +29,89 @@ static void test_replica_undo_empty(void) { tc_replica_free(rep); } +// creating a task succeeds and the resulting task looks good +static void test_replica_task_creation(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + TCUuid uuid = tc_task_get_uuid(task); + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + + TCString *desc = tc_task_get_description(task); + TEST_ASSERT_NOT_NULL(desc); + TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(desc)); + tc_string_free(desc); + + tc_task_free(task); + + // get the task again and verify it + task = tc_replica_get_task(rep, uuid); + TEST_ASSERT_NOT_NULL(task); + TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes)); + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + + tc_task_free(task); + + tc_replica_free(rep); +} + +// importing a task succeeds and the resulting task looks good +static void test_replica_task_import(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCUuid uuid; + TEST_ASSERT_TRUE(tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); + TCTask *task = tc_replica_import_task_with_uuid(rep, uuid); + TEST_ASSERT_NOT_NULL(task); + + TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes)); + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + + TCString *desc = tc_task_get_description(task); + TEST_ASSERT_NOT_NULL(desc); + TEST_ASSERT_EQUAL_STRING("", tc_string_content(desc)); // default value + tc_string_free(desc); + + tc_task_free(task); + + // get the task again and verify it + task = tc_replica_get_task(rep, uuid); + TEST_ASSERT_NOT_NULL(task); + TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes)); + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + + tc_task_free(task); + + tc_replica_free(rep); +} + +// importing a task succeeds and the resulting task looks good +static void test_replica_get_task_not_found(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCUuid uuid; + TEST_ASSERT_TRUE(tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); + TCTask *task = tc_replica_get_task(rep, uuid); + TEST_ASSERT_NULL(task); + TEST_ASSERT_NULL(tc_replica_error(rep)); +} + int replica_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. RUN_TEST(test_replica_creation); RUN_TEST(test_replica_creation_disk); RUN_TEST(test_replica_undo_empty); + RUN_TEST(test_replica_task_creation); + RUN_TEST(test_replica_task_import); + RUN_TEST(test_replica_get_task_not_found); return UNITY_END(); } diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index bd9e14f4b..b0bbc4117 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -26,32 +26,9 @@ static void test_task_creation(void) { tc_replica_free(rep); } -// importing a task succeeds and the resulting task looks good -static void test_task_import(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); - - TCUuid uuid; - TEST_ASSERT_TRUE(tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); - TCTask *task = tc_replica_import_task_with_uuid(rep, uuid); - TEST_ASSERT_NOT_NULL(task); - - TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - - TCString *desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc); - TEST_ASSERT_EQUAL_STRING("", tc_string_content(desc)); // default value - tc_string_free(desc); - - tc_task_free(task); - - tc_replica_free(rep); -} - int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. RUN_TEST(test_task_creation); - RUN_TEST(test_task_import); return UNITY_END(); } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index f77612b41..6bb89b209 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -27,6 +27,7 @@ where F: FnOnce(&mut Replica) -> anyhow::Result, { let rep: &'a mut TCReplica = rep_ref(rep); + rep.error = None; match f(&mut rep.inner) { Ok(v) => v, Err(e) => { @@ -83,7 +84,26 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( // TODO: tc_replica_all_tasks // TODO: tc_replica_all_task_uuids // TODO: tc_replica_working_set -// TODO: tc_replica_get_task + +/// 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. +#[no_mangle] +pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, uuid: TCUuid) -> *mut TCTask { + wrap( + rep, + |rep| { + let uuid: Uuid = uuid.into(); + if let Some(task) = rep.get_task(uuid)? { + Ok(TCTask::as_ptr(task)) + } else { + Ok(std::ptr::null_mut()) + } + }, + std::ptr::null_mut(), + ) +} /// Create a new task. The task must not already exist. /// diff --git a/lib/taskchampion.h b/lib/taskchampion.h index a82ef49a3..466e98994 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -84,6 +84,14 @@ struct TCReplica *tc_replica_new_in_memory(void); */ struct TCReplica *tc_replica_new_on_disk(struct TCString *path, struct TCString **error_out); +/** + * 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 uuid); + /** * Create a new task. The task must not already exist. * From b5201a28c34b755be681c6f4072f5a648f00204d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 26 Jan 2022 14:20:50 +0000 Subject: [PATCH 450/548] build bindings-test shared --- integration-tests/build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-tests/build.rs b/integration-tests/build.rs index 04cb6b85d..5182784b3 100644 --- a/integration-tests/build.rs +++ b/integration-tests/build.rs @@ -14,6 +14,7 @@ fn build_libtaskchampion(suites: &[&'static str]) { libtaskchampion.push("libtaskchampion.so"); let mut build = cc::Build::new(); + build.shared_flag(true); build.object(libtaskchampion); build.include("../lib"); build.include("src/bindings_tests/unity"); From 633ea5cf470e93622c5f9b1edb30c690944d42e7 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 27 Jan 2022 01:54:00 +0000 Subject: [PATCH 451/548] correctly handle invalid utf-8 --- integration-tests/src/bindings_tests/string.c | 39 +++++++++++- integration-tests/src/bindings_tests/uuid.c | 7 +++ lib/src/string.rs | 62 ++++++++++++------- lib/taskchampion.h | 31 ++++++---- 4 files changed, 104 insertions(+), 35 deletions(-) diff --git a/integration-tests/src/bindings_tests/string.c b/integration-tests/src/bindings_tests/string.c index 9926d79bd..0eebec8f7 100644 --- a/integration-tests/src/bindings_tests/string.c +++ b/integration-tests/src/bindings_tests/string.c @@ -20,6 +20,24 @@ static void test_string_cloning(void) { tc_string_free(s); } +// creating cloned strings with invalid utf-8 does not crash +// ..but content is NULL and content_and_len returns the value +static void test_string_cloning_invalid_utf8(void) { + TCString *s = tc_string_clone("\xf0\x28\x8c\x28"); + TEST_ASSERT_NOT_NULL(s); + + // NOTE: this is not one of the cases where invalid UTF-8 results in NULL, + // but that may change. + + size_t len; + const char *buf = tc_string_content_with_len(s, &len); + TEST_ASSERT_NOT_NULL(buf); + TEST_ASSERT_EQUAL(4, len); + TEST_ASSERT_EQUAL_MEMORY("\xf0\x28\x8c\x28", buf, len); + + tc_string_free(s); +} + // borrowed strings echo back their content static void test_string_borrowed_strings_echo(void) { TCString *s = tc_string_borrow("abcdef"); @@ -54,7 +72,8 @@ static void test_string_cloned_strings_echo(void) { tc_string_free(s); } -// tc_string_content returns NULL for strings containing embedded NULs +// tc_clone_with_len can have NULs, and tc_string_content returns NULL for +// strings containing embedded NULs static void test_string_content_null_for_embedded_nuls(void) { TCString *s = tc_string_clone_with_len("ab\0de", 5); TEST_ASSERT_NOT_NULL(s); @@ -69,13 +88,31 @@ static void test_string_content_null_for_embedded_nuls(void) { tc_string_free(s); } +// tc_string_clone_with_len will accept invalid utf-8, but then tc_string_content +// returns NULL. +static void test_string_clone_with_len_invalid_utf8(void) { + TCString *s = tc_string_clone_with_len("\xf0\x28\x8c\x28", 4); + TEST_ASSERT_NOT_NULL(s); + + TEST_ASSERT_NULL(tc_string_content(s)); + + size_t len; + const char *buf = tc_string_content_with_len(s, &len); + TEST_ASSERT_NOT_NULL(buf); + TEST_ASSERT_EQUAL(4, len); + TEST_ASSERT_EQUAL_MEMORY("\xf0\x28\x8c\x28", buf, len); + tc_string_free(s); +} + int string_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. RUN_TEST(test_string_creation); RUN_TEST(test_string_cloning); + RUN_TEST(test_string_cloning_invalid_utf8); RUN_TEST(test_string_borrowed_strings_echo); RUN_TEST(test_string_cloned_strings_echo); RUN_TEST(test_string_content_null_for_embedded_nuls); + RUN_TEST(test_string_clone_with_len_invalid_utf8); return UNITY_END(); } diff --git a/integration-tests/src/bindings_tests/uuid.c b/integration-tests/src/bindings_tests/uuid.c index 2e8d0c806..572a85322 100644 --- a/integration-tests/src/bindings_tests/uuid.c +++ b/integration-tests/src/bindings_tests/uuid.c @@ -52,6 +52,12 @@ static void test_uuid_bad_utf8(void) { TEST_ASSERT_FALSE(tc_uuid_from_str(tc_string_borrow(ustr), &u)); } +// converting a string with embedded NUL fails as expected +static void test_uuid_embedded_nul(void) { + TCUuid u; + TEST_ASSERT_FALSE(tc_uuid_from_str(tc_string_clone_with_len("ab\0de", 5), &u)); +} + int uuid_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. @@ -61,5 +67,6 @@ int uuid_tests(void) { RUN_TEST(test_uuid_to_str); RUN_TEST(test_uuid_invalid_string_fails); RUN_TEST(test_uuid_bad_utf8); + RUN_TEST(test_uuid_embedded_nul); return UNITY_END(); } diff --git a/lib/src/string.rs b/lib/src/string.rs index 1e3968004..1614a0c02 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -1,18 +1,20 @@ use std::ffi::{CStr, CString, OsStr}; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; +use std::str::Utf8Error; -// TODO: is utf-8-ness always checked? (no) when? - -/// TCString supports passing strings into and out of the TaskChampion API. A string must contain -/// valid UTF-8, and can contain embedded NUL characters. Strings containing such 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. +/// TCString supports passing strings into and out of the TaskChampion API. A string can contain +/// embedded NUL characters. Strings containing such 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. +/// +/// Rust expects all strings to be UTF-8, and API functions will fail if given a TCString +/// containing invalid UTF-8. /// /// Unless specified otherwise, functions in this API take ownership of a TCString when it is given -/// as a function argument, and free the string before returning. Thus the following is valid: +/// as a function argument, and free the string before returning. /// /// When a TCString appears as a return value or output argument, it is the responsibility of the /// caller to free the string. @@ -21,6 +23,10 @@ pub enum TCString<'a> { CStr(&'a CStr), String(String), + /// This variant denotes an input string that was not valid UTF-8. This allows reporting this + /// error when the string is read, with the constructor remaining infallible. + InvalidUtf8(Utf8Error, Vec), + /// None is the default value for TCString, but this variant is never seen by C code or by Rust /// code outside of this module. None, @@ -52,15 +58,17 @@ impl<'a> TCString<'a> { TCString::CString(cstring) => cstring.as_c_str().to_str(), TCString::CStr(cstr) => cstr.to_str(), TCString::String(string) => Ok(string.as_ref()), + TCString::InvalidUtf8(e, _) => Err(*e), TCString::None => unreachable!(), } } - pub(crate) fn as_bytes(&self) -> &[u8] { + fn as_bytes(&self) -> &[u8] { match self { TCString::CString(cstring) => cstring.as_bytes(), TCString::CStr(cstr) => cstr.to_bytes(), TCString::String(string) => string.as_bytes(), + TCString::InvalidUtf8(_, data) => data.as_ref(), TCString::None => unreachable!(), } } @@ -118,8 +126,7 @@ pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'s /// 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. If the -/// given string is not valid UTF-8, this function will return NULL. +/// TCString is independent of the passed buffer, which may be reused or freed immediately. #[no_mangle] pub extern "C" fn tc_string_clone_with_len( buf: *const libc::c_char, @@ -127,21 +134,30 @@ pub extern "C" fn tc_string_clone_with_len( ) -> *mut TCString<'static> { let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) }; let vec = slice.to_vec(); - if let Ok(string) = String::from_utf8(vec) { - TCString::String(string).return_val() - } else { - std::ptr::null_mut() + // try converting to a string, which is the only variant that can contain embedded NULs. If + // the bytes are not valid utf-8, store that information for reporting later. + match String::from_utf8(vec) { + Ok(string) => TCString::String(string), + Err(e) => { + let (e, vec) = (e.utf8_error(), e.into_bytes()); + TCString::InvalidUtf8(e, vec) + } } + .return_val() } /// Get the content of the string as a regular C string. The given string must not be NULL. The -/// returned value is NULL if the string contains NUL bytes. The returned string is valid until -/// the TCString is freed or passed to another TC API function. +/// 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 does _not_ take ownership of the TCString. #[no_mangle] pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { let tcstring = TCString::from_arg_ref(tcstring); + // if we have a String, we need to consume it and turn it into // a CString. if matches!(tcstring, TCString::String(_)) { @@ -153,7 +169,7 @@ pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_c Err(nul_err) => { // recover the underlying String from the NulError let original_bytes = nul_err.into_vec(); - // SAFETY: original_bytes just came from a String, so must be valid utf8 + // SAFETY: original_bytes came from a String moments ago, so still valid utf8 let string = unsafe { String::from_utf8_unchecked(original_bytes) }; *tcstring = TCString::String(string); @@ -170,13 +186,14 @@ pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_c TCString::CString(cstring) => cstring.as_ptr(), TCString::String(_) => unreachable!(), // just converted to CString TCString::CStr(cstr) => cstr.as_ptr(), + TCString::InvalidUtf8(_, _) => std::ptr::null(), TCString::None => unreachable!(), } } /// 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. The returned string is -/// valid until the TCString is freed or passed to another TC API function. +/// 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 TC API function. /// /// This function does _not_ take ownership of the TCString. #[no_mangle] @@ -184,11 +201,14 @@ pub extern "C" fn tc_string_content_with_len( tcstring: *mut TCString, len_out: *mut usize, ) -> *const libc::c_char { + debug_assert!(!tcstring.is_null()); + debug_assert!(!len_out.is_null()); let tcstring = TCString::from_arg_ref(tcstring); let bytes = match tcstring { TCString::CString(cstring) => cstring.as_bytes(), TCString::String(string) => string.as_bytes(), TCString::CStr(cstr) => cstr.to_bytes(), + TCString::InvalidUtf8(_, ref v) => v.as_ref(), TCString::None => unreachable!(), }; unsafe { *len_out = bytes.len() }; diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 466e98994..bf746289c 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -34,15 +34,18 @@ typedef enum TCStatus { typedef struct TCReplica TCReplica; /** - * TCString supports passing strings into and out of the TaskChampion API. A string must contain - * valid UTF-8, and can contain embedded NUL characters. Strings containing such 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. + * TCString supports passing strings into and out of the TaskChampion API. A string can contain + * embedded NUL characters. Strings containing such 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. + * + * Rust expects all strings to be UTF-8, and API functions will fail if given a TCString + * containing invalid UTF-8. * * Unless specified otherwise, functions in this API take ownership of a TCString when it is given - * as a function argument, and free the string before returning. Thus the following is valid: + * as a function argument, and free the string before returning. * * When a TCString appears as a return value or output argument, it is the responsibility of the * caller to free the string. @@ -154,15 +157,17 @@ 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. If the - * given string is not valid UTF-8, this function will return NULL. + * TCString is independent of the passed buffer, which may be reused or freed immediately. */ 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 not be NULL. The - * returned value is NULL if the string contains NUL bytes. The returned string is valid until - * the TCString is freed or passed to another TC API function. + * 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 does _not_ take ownership of the TCString. */ @@ -170,8 +175,8 @@ const char *tc_string_content(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. The returned string is - * valid until the TCString is freed or passed to another TC API function. + * 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 TC API function. * * This function does _not_ take ownership of the TCString. */ From 1470bbf7418d59e4c3135354ed79ac4acd2f2850 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 27 Jan 2022 02:22:39 +0000 Subject: [PATCH 452/548] mark unsafe utils as such; add safety comments --- lib/src/replica.rs | 17 ++++++--- lib/src/string.rs | 89 ++++++++++++++++++++++++++++++++++++---------- lib/src/uuid.rs | 8 +++-- lib/taskchampion.h | 29 +++++++++------ 4 files changed, 107 insertions(+), 36 deletions(-) diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 6bb89b209..1d8014424 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -50,14 +50,18 @@ pub extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { })) } -/// Create a new TCReplica with an on-disk database. On error, a string is written to the -/// `error_out` parameter (if it is not NULL) and NULL is returned. +/// Create a new TCReplica with an on-disk database having the given filename. The filename must +/// not be NULL. On error, a string is written to the `error_out` parameter (if it is not NULL) and +/// NULL is returned. #[no_mangle] pub extern "C" fn tc_replica_new_on_disk<'a>( path: *mut TCString, error_out: *mut *mut TCString, ) -> *mut TCReplica { - let path = TCString::from_arg(path); + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - caller is exclusive owner of tcstring (implicitly promised by caller) + let path = unsafe { TCString::from_arg(path) }; let storage_res = StorageConfig::OnDisk { taskdb_dir: path.to_path_buf(), } @@ -107,6 +111,8 @@ pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, uuid: TCUuid) -> *mut /// Create a new task. The task must not already exist. /// +/// The description must not be NULL. +/// /// Returns the task, or NULL on error. #[no_mangle] pub extern "C" fn tc_replica_new_task( @@ -114,10 +120,13 @@ pub extern "C" fn tc_replica_new_task( status: TCStatus, description: *mut TCString, ) -> *mut TCTask { + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - caller is exclusive owner of tcstring (implicitly promised by caller) + let description = unsafe { TCString::from_arg(description) }; wrap( rep, |rep| { - let description = TCString::from_arg(description); let task = rep.new_task(status.into(), description.as_str()?.to_string())?; Ok(TCTask::as_ptr(task)) }, diff --git a/lib/src/string.rs b/lib/src/string.rs index 1614a0c02..df0eebddb 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -14,7 +14,8 @@ use std::str::Utf8Error; /// containing invalid UTF-8. /// /// Unless specified otherwise, functions in this API take ownership of a TCString when it is given -/// as a function argument, and free the string before returning. +/// as a function argument, and free the string before returning. Callers must not use or free +/// strings after passing them to such API functions. /// /// When a TCString appears as a return value or output argument, it is the responsibility of the /// caller to free the string. @@ -39,17 +40,31 @@ impl<'a> Default for TCString<'a> { } impl<'a> TCString<'a> { - /// Take a TCString from C as an argument. C callers generally expect TC functions to take - /// ownership of a string, which is what this function does. - pub(crate) fn from_arg(tcstring: *mut TCString<'a>) -> Self { + /// Take a TCString from C as an argument. + /// + /// C callers generally expect TC functions to take ownership of a string, which is what this + /// function does. + /// + /// # Safety + /// + /// The pointer must not be NULL. It is the caller's responsibility to ensure that the + /// lifetime assigned to the reference and the lifetime of the TCString itself do not outlive + /// the lifetime promised by C. + pub(crate) unsafe fn from_arg(tcstring: *mut TCString<'a>) -> Self { debug_assert!(!tcstring.is_null()); - *(unsafe { Box::from_raw(tcstring) }) + *(Box::from_raw(tcstring)) } /// Borrow a TCString from C as an argument. - pub(crate) fn from_arg_ref(tcstring: *mut TCString<'a>) -> &'a mut Self { + /// + /// # Safety + /// + /// The pointer must not be NULL. It is the caller's responsibility to ensure that the + /// lifetime assigned to the reference and the lifetime of the TCString itself do not outlive + /// the lifetime promised by C. + pub(crate) unsafe fn from_arg_ref(tcstring: *mut TCString<'a>) -> &'a mut Self { debug_assert!(!tcstring.is_null()); - unsafe { &mut *tcstring } + &mut *tcstring } /// Get a regular Rust &str for this value. @@ -97,11 +112,12 @@ impl<'a> From<&str> for TCString<'a> { } } -/// Create a new TCString referencing the given C string. The C string must remain valid until -/// after the TCString is freed. It's typically easiest to ensure this by using a static string. +/// 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 C string itself. The -/// underlying string can be freed once the TCString referencing it has been freed. +/// 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: /// @@ -112,6 +128,12 @@ impl<'a> From<&str> for TCString<'a> { /// ``` #[no_mangle] pub extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<'static> { + debug_assert!(!cstr.is_null()); + // SAFETY: + // - cstr is not NULL (promised by caller, verified by assertion) + // - cstr's lifetime exceeds that of the TCString (promised by caller) + // - cstr contains a valid NUL terminator (promised by caller) + // - cstr's content will not change before it is destroyed (promised by caller) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; TCString::CStr(cstr).return_val() } @@ -120,6 +142,12 @@ pub extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<' /// is independent of the given string, which can be freed or overwritten immediately. #[no_mangle] pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'static> { + debug_assert!(!cstr.is_null()); + // SAFETY: + // - cstr is not NULL (promised by caller, verified by assertion) + // - cstr's lifetime exceeds that of this function (by C convention) + // - cstr contains a valid NUL terminator (promised by caller) + // - cstr's content will not change before it is destroyed (by C convention) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; TCString::CString(cstr.into()).return_val() } @@ -127,11 +155,21 @@ pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'s /// 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 given length must be less than half the maximum value of usize. #[no_mangle] pub extern "C" fn tc_string_clone_with_len( buf: *const libc::c_char, len: usize, ) -> *mut TCString<'static> { + debug_assert!(!buf.is_null()); + debug_assert!(len < isize::MAX as usize); + // SAFETY: + // - buf is valid for len bytes (by C convention) + // - (no alignment requirements for a byte slice) + // - content of buf will not be mutated during the lifetime of this slice (lifetime + // does not outlive this function call) + // - the length of the buffer is less than isize::MAX (promised by caller) let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) }; let vec = slice.to_vec(); // try converting to a string, which is the only variant that can contain embedded NULs. If @@ -156,7 +194,11 @@ pub extern "C" fn tc_string_clone_with_len( /// This function does _not_ take ownership of the TCString. #[no_mangle] pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { - let tcstring = TCString::from_arg_ref(tcstring); + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - lifetime of tcstring outlives the lifetime of this function + // - lifetime of tcstring outlives the lifetime of the returned pointer (promised by caller) + let tcstring = unsafe { TCString::from_arg_ref(tcstring) }; // if we have a String, we need to consume it and turn it into // a CString. @@ -201,9 +243,13 @@ pub extern "C" fn tc_string_content_with_len( tcstring: *mut TCString, len_out: *mut usize, ) -> *const libc::c_char { - debug_assert!(!tcstring.is_null()); + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - lifetime of tcstring outlives the lifetime of this function + // - lifetime of tcstring outlives the lifetime of the returned pointer (promised by caller) + let tcstring = unsafe { TCString::from_arg_ref(tcstring) }; debug_assert!(!len_out.is_null()); - let tcstring = TCString::from_arg_ref(tcstring); + let bytes = match tcstring { TCString::CString(cstring) => cstring.as_bytes(), TCString::String(string) => string.as_bytes(), @@ -211,13 +257,20 @@ pub extern "C" fn tc_string_content_with_len( TCString::InvalidUtf8(_, ref v) => v.as_ref(), TCString::None => unreachable!(), }; + // SAFETY: + // - len_out is not NULL (checked by assertion, promised by caller) + // - len_out points to valid memory (promised by caller) + // - len_out is properly aligned (C convention) unsafe { *len_out = bytes.len() }; bytes.as_ptr() as *const libc::c_char } -/// Free a TCString. +/// 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. #[no_mangle] -pub extern "C" fn tc_string_free(string: *mut TCString) { - debug_assert!(!string.is_null()); - drop(unsafe { Box::from_raw(string) }); +pub extern "C" fn tc_string_free(tcstring: *mut TCString) { + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - caller is exclusive owner of tcstring (promised by caller) + drop(unsafe { TCString::from_arg(tcstring) }); } diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index f25399909..535c03086 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -59,13 +59,15 @@ pub extern "C" fn tc_uuid_to_str(uuid: TCUuid) -> *mut TCString<'static> { TCString::from(s).return_val() } -/// Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns -/// false on failure. +/// Parse the given string as a UUID. The string must not be NULL. Returns false on failure. #[no_mangle] pub extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> bool { debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null()); - let s = TCString::from_arg(s); + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - caller is exclusive owner of tcstring (implicitly promised by caller) + let s = unsafe { TCString::from_arg(s) }; if let Ok(s) = s.as_str() { if let Ok(u) = Uuid::parse_str(s) { unsafe { *uuid_out = u.into() }; diff --git a/lib/taskchampion.h b/lib/taskchampion.h index bf746289c..070b366e0 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -45,7 +45,8 @@ typedef struct TCReplica TCReplica; * containing invalid UTF-8. * * Unless specified otherwise, functions in this API take ownership of a TCString when it is given - * as a function argument, and free the string before returning. + * as a function argument, and free the string before returning. Callers must not use or free + * strings after passing them to such API functions. * * When a TCString appears as a return value or output argument, it is the responsibility of the * caller to free the string. @@ -82,8 +83,9 @@ extern const size_t TC_UUID_STRING_BYTES; struct TCReplica *tc_replica_new_in_memory(void); /** - * Create a new TCReplica with an on-disk database. On error, a string is written to the - * `error_out` parameter (if it is not NULL) and NULL is returned. + * Create a new TCReplica with an on-disk database having the given filename. The filename must + * not be NULL. On error, a string is written to the `error_out` parameter (if it is not NULL) and + * NULL is returned. */ struct TCReplica *tc_replica_new_on_disk(struct TCString *path, struct TCString **error_out); @@ -98,6 +100,8 @@ struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid uuid); /** * Create a new task. The task must not already exist. * + * The description must not be NULL. + * * Returns the task, or NULL on error. */ struct TCTask *tc_replica_new_task(struct TCReplica *rep, @@ -132,11 +136,12 @@ struct TCString *tc_replica_error(struct TCReplica *rep); void tc_replica_free(struct TCReplica *rep); /** - * Create a new TCString referencing the given C string. The C string must remain valid until - * after the TCString is freed. It's typically easiest to ensure this by using a static string. + * 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 C string itself. The - * underlying string can be freed once the TCString referencing it has been freed. + * 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: * @@ -158,6 +163,8 @@ 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 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); @@ -183,9 +190,10 @@ const char *tc_string_content(struct TCString *tcstring); const char *tc_string_content_with_len(struct TCString *tcstring, size_t *len_out); /** - * Free a TCString. + * 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 *string); +void tc_string_free(struct TCString *tcstring); /** * Get a task's UUID. @@ -231,8 +239,7 @@ void tc_uuid_to_buf(struct TCUuid uuid, char *buf); struct TCString *tc_uuid_to_str(struct TCUuid uuid); /** - * Parse the given value as a UUID. The value must be exactly TC_UUID_STRING_BYTES long. Returns - * false on failure. + * Parse the given string as a UUID. The string must not be NULL. Returns false on failure. */ bool tc_uuid_from_str(struct TCString *s, struct TCUuid *uuid_out); From b3cbec1af3664d1a05a3d41d478fdf3f68d228e4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 28 Jan 2022 02:11:13 +0000 Subject: [PATCH 453/548] more unsafe notations --- lib/src/replica.rs | 36 +++++++++++++++++++++++++----------- lib/src/task.rs | 43 ++++++++++++++++++++++++++++++++----------- lib/src/uuid.rs | 9 +++++++++ lib/taskchampion.h | 7 ++++--- 4 files changed, 70 insertions(+), 25 deletions(-) diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 1d8014424..47220ba7f 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -11,10 +11,18 @@ pub struct TCReplica { error: Option>, } -/// Utility function to safely convert *mut TCReplica into &mut TCReplica -fn rep_ref(rep: *mut TCReplica) -> &'static mut TCReplica { - debug_assert!(!rep.is_null()); - unsafe { &mut *rep } +impl TCReplica { + /// Borrow a TCReplica from C as an argument. + /// + /// # Safety + /// + /// The pointer must not be NULL. It is the caller's responsibility to ensure that the + /// lifetime assigned to the reference and the lifetime of the TCReplica itself do not outlive + /// the lifetime promised by C. + pub(crate) unsafe fn from_arg_ref<'a>(tcstring: *mut TCReplica) -> &'a mut Self { + debug_assert!(!tcstring.is_null()); + &mut *tcstring + } } fn err_to_tcstring(e: impl std::string::ToString) -> TCString<'static> { @@ -26,7 +34,10 @@ fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T where F: FnOnce(&mut Replica) -> anyhow::Result, { - let rep: &'a mut TCReplica = rep_ref(rep); + // SAFETY: + // - rep is not null (promised by caller) + // - rep outlives 'a (promised by caller) + let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; rep.error = None; match f(&mut rep.inner) { Ok(v) => v, @@ -100,7 +111,7 @@ pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, uuid: TCUuid) -> *mut |rep| { let uuid: Uuid = uuid.into(); if let Some(task) = rep.get_task(uuid)? { - Ok(TCTask::as_ptr(task)) + Ok(TCTask::return_val(task)) } else { Ok(std::ptr::null_mut()) } @@ -128,7 +139,7 @@ pub extern "C" fn tc_replica_new_task( rep, |rep| { let task = rep.new_task(status.into(), description.as_str()?.to_string())?; - Ok(TCTask::as_ptr(task)) + Ok(TCTask::return_val(task)) }, std::ptr::null_mut(), ) @@ -147,7 +158,7 @@ pub extern "C" fn tc_replica_import_task_with_uuid( |rep| { let uuid: Uuid = uuid.into(); let task = rep.import_task_with_uuid(uuid)?; - Ok(TCTask::as_ptr(task)) + Ok(TCTask::return_val(task)) }, std::ptr::null_mut(), ) @@ -174,12 +185,15 @@ pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica) -> TCResult { ) } -/// Get the latest error for a replica, or NULL if the last operation succeeded. -/// Subsequent calls to this function will return NULL. The caller must free the +/// Get the latest error for a replica, or NULL if the last operation succeeded. Subsequent calls +/// to this function will return NULL. The rep pointer must not be NULL. The caller must free the /// returned string. #[no_mangle] pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'static> { - let rep: &'a mut TCReplica = rep_ref(rep); + // SAFETY: + // - rep is not null (promised by caller) + // - rep outlives 'a (promised by caller) + let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; if let Some(tcstring) = rep.error.take() { tcstring.return_val() } else { diff --git a/lib/src/task.rs b/lib/src/task.rs index ba129caa4..21b9d177a 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -10,21 +10,31 @@ pub struct TCTask { } impl TCTask { - pub(crate) fn as_ptr(task: Task) -> *mut TCTask { + /// Borrow a TCTask from C as an argument. + /// + /// # Safety + /// + /// The pointer must not be NULL. It is the caller's responsibility to ensure that the + /// lifetime assigned to the reference and the lifetime of the TCTask itself do not outlive + /// the lifetime promised by C. + pub(crate) unsafe fn from_arg_ref<'a>(tcstring: *const TCTask) -> &'a Self { + debug_assert!(!tcstring.is_null()); + &*tcstring + } + + /// Convert this to a return value for handing off to C. + pub(crate) fn return_val(task: Task) -> *mut TCTask { Box::into_raw(Box::new(TCTask { inner: task })) } } -/// Utility function to safely convert *const TCTask into &Task -fn task_ref(task: *const TCTask) -> &'static Task { - debug_assert!(!task.is_null()); - unsafe { &(*task).inner } -} - /// Get a task's UUID. #[no_mangle] pub extern "C" fn tc_task_get_uuid<'a>(task: *const TCTask) -> TCUuid { - let task: &'a Task = task_ref(task); + // SAFETY: + // - task is not NULL (promised by caller) + // - task's lifetime exceeds this function (promised by caller) + let task: &'a Task = &unsafe { TCTask::from_arg_ref(task) }.inner; let uuid = task.get_uuid(); uuid.into() } @@ -32,7 +42,10 @@ pub extern "C" fn tc_task_get_uuid<'a>(task: *const TCTask) -> TCUuid { /// Get a task's status. #[no_mangle] pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { - let task: &'a Task = task_ref(task); + // SAFETY: + // - task is not NULL (promised by caller) + // - task's lifetime exceeds this function (promised by caller) + let task: &'a Task = &unsafe { TCTask::from_arg_ref(task) }.inner; task.get_status().into() } @@ -45,7 +58,10 @@ pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { /// contains embedded NUL characters). #[no_mangle] pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCString<'static> { - let task = task_ref(task); + // SAFETY: + // - task is not NULL (promised by caller) + // - task's lifetime exceeds this function (promised by caller) + let task: &'a Task = &unsafe { TCTask::from_arg_ref(task) }.inner; let descr: TCString = task.get_description().into(); descr.return_val() } @@ -64,9 +80,14 @@ pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCStr * get_modified */ -/// Free a 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. #[no_mangle] pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) { debug_assert!(!task.is_null()); + // SAFETY: + // - task is not NULL (promised by caller) + // - task's lifetime exceeds the drop (promised by caller) + // - task does not outlive this function (promised by caller) drop(unsafe { Box::from_raw(task) }); } diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 535c03086..43fffe97b 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -43,6 +43,12 @@ pub static TC_UUID_STRING_BYTES: usize = ::uuid::adapter::Hyphenated::LENGTH; #[no_mangle] pub extern "C" fn tc_uuid_to_buf<'a>(uuid: TCUuid, buf: *mut libc::c_char) { debug_assert!(!buf.is_null()); + // SAFETY: + // - buf is valid for len bytes (by C convention) + // - (no alignment requirements for a byte slice) + // - content of buf will not be mutated during the lifetime of this slice (lifetime + // does not outlive this function call) + // - the length of the buffer is less than isize::MAX (promised by caller) let buf: &'a mut [u8] = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, ::uuid::adapter::Hyphenated::LENGTH) }; @@ -70,6 +76,9 @@ pub extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) let s = unsafe { TCString::from_arg(s) }; if let Ok(s) = s.as_str() { if let Ok(u) = Uuid::parse_str(s) { + // SAFETY: + // - uuid_out is not NULL (promised by caller) + // - alignment is not required unsafe { *uuid_out = u.into() }; return true; } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 070b366e0..a42060ac6 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -124,8 +124,8 @@ struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TC enum TCResult tc_replica_undo(struct TCReplica *rep); /** - * Get the latest error for a replica, or NULL if the last operation succeeded. - * Subsequent calls to this function will return NULL. The caller must free the + * Get the latest error for a replica, or NULL if the last operation succeeded. 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); @@ -212,7 +212,8 @@ enum TCStatus tc_task_get_status(const struct TCTask *task); struct TCString *tc_task_get_description(const struct TCTask *task); /** - * Free a 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. */ void tc_task_free(struct TCTask *task); From 82459e699caf61aa1fe93c549318ad632db99904 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 28 Jan 2022 03:51:58 +0000 Subject: [PATCH 454/548] use a simple constant --- lib/src/uuid.rs | 7 +++---- lib/taskchampion.h | 8 ++++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 43fffe97b..9dcd3797b 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -33,10 +33,9 @@ pub extern "C" fn tc_uuid_nil() -> TCUuid { Uuid::nil().into() } -/// Length, in bytes, of a C string containing a TCUuid. -// TODO: why not a const? -#[no_mangle] -pub static TC_UUID_STRING_BYTES: usize = ::uuid::adapter::Hyphenated::LENGTH; +// 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; /// 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. diff --git a/lib/taskchampion.h b/lib/taskchampion.h index a42060ac6..9c7c030b1 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -1,5 +1,11 @@ #include #include +#define TC_UUID_STRING_BYTES 36 + +/** + * Length, in bytes, of the string representation of a UUID (without NUL terminator) + */ +#define TC_UUID_STRING_BYTES 36 /** * A result combines a boolean success value with @@ -74,8 +80,6 @@ typedef struct TCUuid { extern "C" { #endif // __cplusplus -extern const size_t TC_UUID_STRING_BYTES; - /** * Create a new TCReplica with an in-memory database. The contents of the database will be * lost when it is freed. From 50aceb96964e4025c9253c94b6f84bbf5bd991ee Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 29 Jan 2022 01:00:55 +0000 Subject: [PATCH 455/548] use RefCell for replica, more consistent rust methods --- lib/src/replica.rs | 61 ++++++++++++++++++++++++++-------------------- lib/src/task.rs | 48 +++++++++++++++++++----------------- lib/taskchampion.h | 1 - 3 files changed, 59 insertions(+), 51 deletions(-) diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 47220ba7f..53e5d84c7 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,4 +1,5 @@ use crate::{result::TCResult, status::TCStatus, string::TCString, task::TCTask, uuid::TCUuid}; +use std::cell::{RefCell, RefMut}; use taskchampion::{Replica, StorageConfig, Uuid}; /// A replica represents an instance of a user's task data, providing an easy interface @@ -6,8 +7,7 @@ use taskchampion::{Replica, StorageConfig, Uuid}; /// /// TCReplicas are not threadsafe. pub struct TCReplica { - // TODO: make this a RefCell so that it can be take()n when holding a mut ref - inner: Replica, + inner: RefCell, error: Option>, } @@ -19,9 +19,23 @@ impl TCReplica { /// The pointer must not be NULL. It is the caller's responsibility to ensure that the /// lifetime assigned to the reference and the lifetime of the TCReplica itself do not outlive /// the lifetime promised by C. - pub(crate) unsafe fn from_arg_ref<'a>(tcstring: *mut TCReplica) -> &'a mut Self { - debug_assert!(!tcstring.is_null()); - &mut *tcstring + pub(crate) unsafe fn from_arg_ref<'a>(tcreplica: *mut TCReplica) -> &'a mut Self { + debug_assert!(!tcreplica.is_null()); + &mut *tcreplica + } + + /// Convert this to a return value for handing off to C. + pub(crate) fn return_val(self) -> *mut TCReplica { + Box::into_raw(Box::new(self)) + } +} + +impl From for TCReplica { + fn from(rep: Replica) -> TCReplica { + TCReplica { + inner: RefCell::new(rep), + error: None, + } } } @@ -29,17 +43,18 @@ fn err_to_tcstring(e: impl std::string::ToString) -> TCString<'static> { TCString::from(e.to_string()) } -/// Utility function to allow using `?` notation to return an error value. +/// Utility function to allow using `?` notation to return an error value. This makes +/// a mutable borrow, because most Replica methods require a `&mut`. fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T where - F: FnOnce(&mut Replica) -> anyhow::Result, + F: FnOnce(RefMut<'_, Replica>) -> anyhow::Result, { // SAFETY: // - rep is not null (promised by caller) // - rep outlives 'a (promised by caller) let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; rep.error = None; - match f(&mut rep.inner) { + match f(rep.inner.borrow_mut()) { Ok(v) => v, Err(e) => { rep.error = Some(err_to_tcstring(e)); @@ -55,10 +70,7 @@ pub extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { let storage = StorageConfig::InMemory .into_storage() .expect("in-memory always succeeds"); - Box::into_raw(Box::new(TCReplica { - inner: Replica::new(storage), - error: None, - })) + TCReplica::from(Replica::new(storage)).return_val() } /// Create a new TCReplica with an on-disk database having the given filename. The filename must @@ -90,10 +102,7 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( } }; - Box::into_raw(Box::new(TCReplica { - inner: Replica::new(storage), - error: None, - })) + TCReplica::from(Replica::new(storage)).return_val() } // TODO: tc_replica_all_tasks @@ -108,10 +117,10 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, uuid: TCUuid) -> *mut TCTask { wrap( rep, - |rep| { + |mut rep| { let uuid: Uuid = uuid.into(); if let Some(task) = rep.get_task(uuid)? { - Ok(TCTask::return_val(task)) + Ok(TCTask::from(task).return_val()) } else { Ok(std::ptr::null_mut()) } @@ -137,9 +146,9 @@ pub extern "C" fn tc_replica_new_task( let description = unsafe { TCString::from_arg(description) }; wrap( rep, - |rep| { + |mut rep| { let task = rep.new_task(status.into(), description.as_str()?.to_string())?; - Ok(TCTask::return_val(task)) + Ok(TCTask::from(task).return_val()) }, std::ptr::null_mut(), ) @@ -155,10 +164,10 @@ pub extern "C" fn tc_replica_import_task_with_uuid( ) -> *mut TCTask { wrap( rep, - |rep| { + |mut rep| { let uuid: Uuid = uuid.into(); let task = rep.import_task_with_uuid(uuid)?; - Ok(TCTask::return_val(task)) + Ok(TCTask::from(task).return_val()) }, std::ptr::null_mut(), ) @@ -174,7 +183,7 @@ pub extern "C" fn tc_replica_import_task_with_uuid( pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica) -> TCResult { wrap( rep, - |rep| { + |mut rep| { Ok(if rep.undo()? { TCResult::True } else { @@ -208,7 +217,5 @@ pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { drop(unsafe { Box::from_raw(rep) }); } -/* - * - tc_replica_rebuild_working_set - * - tc_replica_add_undo_point - */ +// TODO: tc_replica_rebuild_working_set +// TODO: tc_replica_add_undo_point diff --git a/lib/src/task.rs b/lib/src/task.rs index 21b9d177a..ed27acc81 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -17,14 +17,20 @@ impl TCTask { /// The pointer must not be NULL. It is the caller's responsibility to ensure that the /// lifetime assigned to the reference and the lifetime of the TCTask itself do not outlive /// the lifetime promised by C. - pub(crate) unsafe fn from_arg_ref<'a>(tcstring: *const TCTask) -> &'a Self { - debug_assert!(!tcstring.is_null()); - &*tcstring + pub(crate) unsafe fn from_arg_ref<'a>(tctask: *const TCTask) -> &'a Self { + debug_assert!(!tctask.is_null()); + &*tctask } - /// Convert this to a return value for handing off to C. - pub(crate) fn return_val(task: Task) -> *mut TCTask { - Box::into_raw(Box::new(TCTask { inner: task })) + /// Convert a TCTask to a return value for handing off to C. + pub(crate) fn return_val(self) -> *mut TCTask { + Box::into_raw(Box::new(self)) + } +} + +impl From for TCTask { + fn from(task: Task) -> TCTask { + TCTask { inner: task } } } @@ -49,10 +55,8 @@ pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { task.get_status().into() } -/* TODO - * into_mut - * get_taskmap - */ +// TODO: into_mut +// TODO: get_taskmap /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). @@ -66,19 +70,17 @@ pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCStr descr.return_val() } -/* TODO - * get_wait - * is_waiting - * is_active - * has_tag - * get_tags - * get_annotations - * get_uda - * get_udas - * get_legacy_uda - * get_legacy_udas - * get_modified - */ +// TODO: :get_wait +// TODO: :is_waiting +// TODO: :is_active +// TODO: :has_tag +// TODO: :get_tags +// TODO: :get_annotations +// TODO: :get_uda +// TODO: :get_udas +// TODO: :get_legacy_uda +// TODO: :get_legacy_udas +// TODO: :get_modified /// 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. diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 9c7c030b1..67250584c 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -1,6 +1,5 @@ #include #include -#define TC_UUID_STRING_BYTES 36 /** * Length, in bytes, of the string representation of a UUID (without NUL terminator) From 452ae2074f3634fe19727923f6ac6884e50c8061 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 29 Jan 2022 03:08:45 +0000 Subject: [PATCH 456/548] implement task mutability --- integration-tests/src/bindings_tests/task.c | 59 ++++++ lib/src/replica.rs | 52 ++++- lib/src/task.rs | 220 +++++++++++++++++--- lib/src/taskmut.rs | 67 ++++++ lib/taskchampion.h | 52 ++++- 5 files changed, 411 insertions(+), 39 deletions(-) create mode 100644 lib/src/taskmut.rs diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index b0bbc4117..b6f323583 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -26,9 +26,68 @@ static void test_task_creation(void) { tc_replica_free(rep); } +// updating status on a task works +static void test_task_get_set_status(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + + tc_task_to_mut(task, rep); + TEST_ASSERT_TRUE(tc_task_set_status(task, TC_STATUS_DELETED)); + TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while mut + tc_task_to_immut(task, rep); + TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while immut + + tc_task_free(task); + + tc_replica_free(rep); +} + +// updating description on a task works +static void test_task_get_set_description(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + TCString *desc; + + tc_task_to_mut(task, rep); + tc_task_set_description(task, tc_string_borrow("updated")); + + TEST_ASSERT_TRUE(desc = tc_task_get_description(task)); + TEST_ASSERT_NOT_NULL(desc); + TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(desc)); + tc_string_free(desc); + + tc_task_to_immut(task, rep); + + desc = tc_task_get_description(task); + TEST_ASSERT_NOT_NULL(desc); + TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(desc)); + tc_string_free(desc); + + tc_task_free(task); + + tc_replica_free(rep); +} + int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. RUN_TEST(test_task_creation); + RUN_TEST(test_task_get_set_status); + RUN_TEST(test_task_get_set_description); return UNITY_END(); } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 53e5d84c7..d9c28eb0b 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,5 +1,4 @@ use crate::{result::TCResult, status::TCStatus, string::TCString, task::TCTask, uuid::TCUuid}; -use std::cell::{RefCell, RefMut}; use taskchampion::{Replica, StorageConfig, Uuid}; /// A replica represents an instance of a user's task data, providing an easy interface @@ -7,7 +6,13 @@ use taskchampion::{Replica, StorageConfig, Uuid}; /// /// TCReplicas are not threadsafe. pub struct TCReplica { - inner: RefCell, + /// The wrapped Replica + inner: Replica, + + /// If true, this replica has an outstanding &mut (for a TaskMut) + mut_borrowed: bool, + + /// The error from the most recent operation, if any error: Option>, } @@ -24,16 +29,36 @@ impl TCReplica { &mut *tcreplica } + // TODO: from_arg_owned, use in drop + /// Convert this to a return value for handing off to C. pub(crate) fn return_val(self) -> *mut TCReplica { Box::into_raw(Box::new(self)) } + + /// Mutably borrow the inner Replica + pub(crate) fn borrow_mut(&mut self) -> &mut Replica { + if self.mut_borrowed { + panic!("replica is already borrowed"); + } + self.mut_borrowed = true; + &mut self.inner + } + + /// Release the borrow made by [`borrow_mut`] + pub(crate) fn release_borrow(&mut self) { + if !self.mut_borrowed { + panic!("replica is not borrowed"); + } + self.mut_borrowed = false; + } } impl From for TCReplica { fn from(rep: Replica) -> TCReplica { TCReplica { - inner: RefCell::new(rep), + inner: rep, + mut_borrowed: false, error: None, } } @@ -47,14 +72,17 @@ fn err_to_tcstring(e: impl std::string::ToString) -> TCString<'static> { /// a mutable borrow, because most Replica methods require a `&mut`. fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T where - F: FnOnce(RefMut<'_, Replica>) -> anyhow::Result, + F: FnOnce(&mut Replica) -> anyhow::Result, { // SAFETY: // - rep is not null (promised by caller) // - rep outlives 'a (promised by caller) let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; + if rep.mut_borrowed { + panic!("replica is borrowed and cannot be used"); + } rep.error = None; - match f(rep.inner.borrow_mut()) { + match f(&mut rep.inner) { Ok(v) => v, Err(e) => { rep.error = Some(err_to_tcstring(e)); @@ -117,7 +145,7 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, uuid: TCUuid) -> *mut TCTask { wrap( rep, - |mut rep| { + |rep| { let uuid: Uuid = uuid.into(); if let Some(task) = rep.get_task(uuid)? { Ok(TCTask::from(task).return_val()) @@ -146,7 +174,7 @@ pub extern "C" fn tc_replica_new_task( let description = unsafe { TCString::from_arg(description) }; wrap( rep, - |mut rep| { + |rep| { let task = rep.new_task(status.into(), description.as_str()?.to_string())?; Ok(TCTask::from(task).return_val()) }, @@ -164,7 +192,7 @@ pub extern "C" fn tc_replica_import_task_with_uuid( ) -> *mut TCTask { wrap( rep, - |mut rep| { + |rep| { let uuid: Uuid = uuid.into(); let task = rep.import_task_with_uuid(uuid)?; Ok(TCTask::from(task).return_val()) @@ -183,7 +211,7 @@ pub extern "C" fn tc_replica_import_task_with_uuid( pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica) -> TCResult { wrap( rep, - |mut rep| { + |rep| { Ok(if rep.undo()? { TCResult::True } else { @@ -213,7 +241,11 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'st /// Free a TCReplica. #[no_mangle] pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { - debug_assert!(!rep.is_null()); + let replica: &mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; + if replica.mut_borrowed { + panic!("replica is borrowed and cannot be freed"); + } + drop(replica); drop(unsafe { Box::from_raw(rep) }); } diff --git a/lib/src/task.rs b/lib/src/task.rs index ed27acc81..ac65b22eb 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,12 +1,21 @@ -use crate::{status::TCStatus, string::TCString, uuid::TCUuid}; -use taskchampion::Task; +use crate::{replica::TCReplica, status::TCStatus, string::TCString, uuid::TCUuid}; +use std::ops::Deref; +use taskchampion::{Task, TaskMut}; /// 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. +/// /// A task carries no reference to the replica that created it, and can /// be used until it is freed or converted to a TaskMut. -pub struct TCTask { - inner: Task, +pub enum TCTask { + Immutable(Task), + Mutable(TaskMut<'static>), + + /// A transitional state for a TCTask as it goes from mutable to immutable. + Invalid, } impl TCTask { @@ -22,55 +31,155 @@ impl TCTask { &*tctask } + /// Borrow a TCTask from C as an argument, allowing mutation. + /// + /// # Safety + /// + /// The pointer must not be NULL. It is the caller's responsibility to ensure that the + /// lifetime assigned to the reference and the lifetime of the TCTask itself do not outlive + /// the lifetime promised by C. + pub(crate) unsafe fn from_arg_ref_mut<'a>(tctask: *mut TCTask) -> &'a mut Self { + debug_assert!(!tctask.is_null()); + &mut *tctask + } + + // TODO: from_arg_owned, use in drop + /// Convert a TCTask to a return value for handing off to C. pub(crate) fn return_val(self) -> *mut TCTask { Box::into_raw(Box::new(self)) } + + /// Make an immutable TCTask into a mutable TCTask. Does nothing if the task + /// is already mutable. + fn to_mut(&mut self, tcreplica: &'static mut TCReplica) { + *self = match std::mem::replace(self, TCTask::Invalid) { + TCTask::Immutable(task) => { + let rep_ref = tcreplica.borrow_mut(); + TCTask::Mutable(task.into_mut(rep_ref)) + } + TCTask::Mutable(task) => TCTask::Mutable(task), + TCTask::Invalid => unreachable!(), + } + } + + /// Make an mutable TCTask into a immutable TCTask. Does nothing if the task + /// is already immutable. + fn to_immut(&mut self, tcreplica: &mut TCReplica) { + *self = match std::mem::replace(self, TCTask::Invalid) { + TCTask::Immutable(task) => TCTask::Immutable(task), + TCTask::Mutable(task) => { + tcreplica.release_borrow(); + TCTask::Immutable(task.into_immut()) + } + TCTask::Invalid => unreachable!(), + } + } } impl From for TCTask { fn from(task: Task) -> TCTask { - TCTask { inner: task } + TCTask::Immutable(task) } } +/// Utility function to get a shared reference to the underlying Task. +fn wrap<'a, T, F>(task: *const TCTask, f: F) -> T +where + F: FnOnce(&Task) -> T, +{ + // SAFETY: + // - task is not null (promised by caller) + // - task outlives 'a (promised by caller) + let tctask: &'a TCTask = unsafe { TCTask::from_arg_ref(task) }; + let task: &'a Task = match tctask { + TCTask::Immutable(t) => t, + TCTask::Mutable(t) => t.deref(), + TCTask::Invalid => unreachable!(), + }; + f(task) +} + +/// Utility function to get a mutable reference to the underlying Task. The +/// TCTask must be mutable. +fn wrap_mut<'a, T, F>(task: *mut TCTask, f: F) -> T +where + F: FnOnce(&mut TaskMut) -> anyhow::Result, +{ + // SAFETY: + // - task is not null (promised by caller) + // - task outlives 'a (promised by caller) + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let task: &'a mut TaskMut = match tctask { + TCTask::Immutable(_) => panic!("Task is immutable"), + TCTask::Mutable(ref mut t) => t, + TCTask::Invalid => unreachable!(), + }; + // TODO: add TCTask error handling, like replica + f(task).unwrap() +} + +/// Convert an immutable task into a mutable task. +/// +/// The task is modified in-place, and becomes mutable. +/// +/// 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) { ... } +/// ``` +#[no_mangle] +pub extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, rep: *mut TCReplica) { + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let tcreplica: &'static mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; + tctask.to_mut(tcreplica); +} + +/// Convert a mutable task into an immutable task. +/// +/// The task is modified in-place, and becomes immutable. +/// +/// The replica may be used freely after this call. +#[no_mangle] +pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask, rep: *mut TCReplica) { + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let tcreplica: &'static mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; + tctask.to_immut(tcreplica); +} + /// Get a task's UUID. #[no_mangle] -pub extern "C" fn tc_task_get_uuid<'a>(task: *const TCTask) -> TCUuid { - // SAFETY: - // - task is not NULL (promised by caller) - // - task's lifetime exceeds this function (promised by caller) - let task: &'a Task = &unsafe { TCTask::from_arg_ref(task) }.inner; - let uuid = task.get_uuid(); - uuid.into() +pub extern "C" fn tc_task_get_uuid(task: *const TCTask) -> TCUuid { + wrap(task, |task| task.get_uuid().into()) } /// Get a task's status. #[no_mangle] pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { - // SAFETY: - // - task is not NULL (promised by caller) - // - task's lifetime exceeds this function (promised by caller) - let task: &'a Task = &unsafe { TCTask::from_arg_ref(task) }.inner; - task.get_status().into() + wrap(task, |task| task.get_status().into()) } -// TODO: into_mut // TODO: get_taskmap /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). #[no_mangle] pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCString<'static> { - // SAFETY: - // - task is not NULL (promised by caller) - // - task's lifetime exceeds this function (promised by caller) - let task: &'a Task = &unsafe { TCTask::from_arg_ref(task) }.inner; - let descr: TCString = task.get_description().into(); - descr.return_val() + wrap(task, |task| { + let descr: TCString = task.get_description().into(); + descr.return_val() + }) } +// TODO: :get_entry // TODO: :get_wait +// TODO: :get_modified // TODO: :is_waiting // TODO: :is_active // TODO: :has_tag @@ -82,11 +191,68 @@ pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCStr // TODO: :get_legacy_udas // TODO: :get_modified -/// 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. +/// Set a mutable task's status. +/// +/// Returns false on error. +#[no_mangle] +pub extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> bool { + wrap_mut(task, |task| { + task.set_status(status.into())?; + Ok(true) + }) +} + +/// Set a mutable task's description. +/// +/// Returns false on error. +#[no_mangle] +pub extern "C" fn tc_task_set_description<'a>( + task: *mut TCTask, + description: *mut TCString, +) -> bool { + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - caller is exclusive owner of tcstring (implicitly promised by caller) + let description = unsafe { TCString::from_arg(description) }; + wrap_mut(task, |task| { + task.set_description(description.as_str()?.to_string())?; + Ok(true) + }) +} + +// TODO: tc_task_set_description +// TODO: tc_task_set_entry +// TODO: tc_task_set_wait +// TODO: tc_task_set_modified +// TODO: tc_task_start +// TODO: tc_task_stop +// TODO: tc_task_done +// TODO: tc_task_delete +// TODO: tc_task_add_tag +// TODO: tc_task_remove_tag +// TODO: tc_task_add_annotation +// TODO: tc_task_remove_annotation +// TODO: tc_task_set_uda +// TODO: tc_task_remove_uda +// TODO: tc_task_set_legacy_uda +// TODO: tc_task_remove_legacy_uda + +/// Free a task. The given task must not be NULL and must be immutable. The task must not be used +/// after this function returns, and must not be freed more than once. +/// +/// The restriction that the task must be immutable may be lifted (TODO) #[no_mangle] pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) { - debug_assert!(!task.is_null()); + // convert the task to immutable before freeing + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + if !matches!(tctask, TCTask::Immutable(_)) { + // this limit is in place because we require the caller to supply a pointer + // to the replica to make a task immutable, and no such pointer is available + // here. + panic!("Task must be immutable when freed"); + } + drop(tctask); + // SAFETY: // - task is not NULL (promised by caller) // - task's lifetime exceeds the drop (promised by caller) diff --git a/lib/src/taskmut.rs b/lib/src/taskmut.rs new file mode 100644 index 000000000..311647e54 --- /dev/null +++ b/lib/src/taskmut.rs @@ -0,0 +1,67 @@ +use crate::{status::TCStatus, string::TCString, uuid::TCUuid}; +use taskchampion::{TaskMut, Replica}; +use std::cell::RefMut; + +/// A mutable task. +/// +/// A mutable task carries an exclusive reference to the replica, +/// meaning that no other replica operations can be carried out while +/// the mutable task still exists. +pub struct TCTaskMut { + inner: TaskMut<'static>, + replica: RefMut, +} + +impl TCTaskMut { + /// Borrow a TCTaskMut from C as an argument. + /// + /// # Safety + /// + /// The pointer must not be NULL. It is the caller's responsibility to ensure that the + /// lifetime assigned to the reference and the lifetime of the TCTaskMut itself do not outlive + /// the lifetime promised by C. + pub(crate) unsafe fn from_arg_ref<'a>(tctask: *mut TCTaskMut) -> &'a mut Self { + debug_assert!(!tctask.is_null()); + &mut *tctask + } + + /// Convert this to a return value for handing off to C. + pub(crate) fn return_val(self) -> *mut TCTaskMut { + Box::into_raw(Box::new(self)) + } + + /// Create a new TCTaskMut, given a RefMut to the replica. + pub(crate) fn from_immut(task: Task, rep: RefMut) -> Self { + // SAFETY: + // This ref will be embedded in the TaskMut, and we will hang onto + // the RefMut for that duration, guaranteeing no other mutable borrows. + let rep_ref: &'static mut = unsafe { rep.deref_mut() } + let task_mut = task.into_mut(rep_ref); + TCTaskMut { + inner: task_mut, + replica: rep, + } + } +} + +impl From for TCTaskMut { + fn from(rep: Replica) -> TCReplica { + TCReplica { + inner: RefCell::new(rep), + error: None, + } + } +} + + +/// 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. +#[no_mangle] +pub extern "C" fn tc_task_mut_free(task: *mut TCTaskMut) { + debug_assert!(!task.is_null()); + // SAFETY: + // - task is not NULL (promised by caller) + // - task's lifetime exceeds the drop (promised by caller) + // - task does not outlive this function (promised by caller) + drop(unsafe { Box::from_raw(task) }); +} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 67250584c..f632938d5 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -61,6 +61,10 @@ typedef struct TCString TCString; /** * 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. + * * A task carries no reference to the replica that created it, and can * be used until it is freed or converted to a TaskMut. */ @@ -198,6 +202,34 @@ const char *tc_string_content_with_len(struct TCString *tcstring, size_t *len_ou */ void tc_string_free(struct TCString *tcstring); +/** + * Convert an immutable task into a mutable task. + * + * The task is modified in-place, and becomes mutable. + * + * 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 *rep); + +/** + * Convert a mutable task into an immutable task. + * + * The task is modified in-place, and becomes immutable. + * + * The replica may be used freely after this call. + */ +void tc_task_to_immut(struct TCTask *task, struct TCReplica *rep); + /** * Get a task's UUID. */ @@ -215,8 +247,24 @@ enum TCStatus tc_task_get_status(const struct TCTask *task); struct TCString *tc_task_get_description(const 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. + * Set a mutable task's status. + * + * Returns false on error. + */ +bool tc_task_set_status(struct TCTask *task, enum TCStatus status); + +/** + * Set a mutable task's description. + * + * Returns false on error. + */ +bool tc_task_set_description(struct TCTask *task, struct TCString *description); + +/** + * Free a task. The given task must not be NULL and must be immutable. The task must not be used + * after this function returns, and must not be freed more than once. + * + * The restriction that the task must be immutable may be lifted (TODO) */ void tc_task_free(struct TCTask *task); From 364ca57736fa3e7051c61eefcb003a3236c8ab04 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 30 Jan 2022 23:42:52 +0000 Subject: [PATCH 457/548] Slightly more ergonomic task mutation --- integration-tests/src/bindings_tests/task.c | 4 +- lib/src/task.rs | 73 ++++++++++++++------- lib/src/taskmut.rs | 67 ------------------- lib/taskchampion.h | 15 +++-- 4 files changed, 61 insertions(+), 98 deletions(-) delete mode 100644 lib/src/taskmut.rs diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index b6f323583..b4173a1eb 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -42,7 +42,7 @@ static void test_task_get_set_status(void) { tc_task_to_mut(task, rep); TEST_ASSERT_TRUE(tc_task_set_status(task, TC_STATUS_DELETED)); TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while mut - tc_task_to_immut(task, rep); + tc_task_to_immut(task); TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while immut tc_task_free(task); @@ -71,7 +71,7 @@ static void test_task_get_set_description(void) { TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(desc)); tc_string_free(desc); - tc_task_to_immut(task, rep); + tc_task_to_immut(task); desc = tc_task_get_description(task); TEST_ASSERT_NOT_NULL(desc); diff --git a/lib/src/task.rs b/lib/src/task.rs index ac65b22eb..f929416d9 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -11,10 +11,16 @@ use taskchampion::{Task, TaskMut}; /// A task carries no reference to the replica that created it, and can /// be used until it is freed or converted to a TaskMut. pub enum TCTask { + /// A regular, immutable task Immutable(Task), - Mutable(TaskMut<'static>), - /// A transitional state for a TCTask as it goes from mutable to immutable. + /// A mutable task, together with the replica to which it holds an exclusive + /// reference. + Mutable(TaskMut<'static>, *mut TCReplica), + + /// A transitional state for a TCTask as it goes from mutable to immutable and back. A task + /// can only be in this state outside of [`to_mut`] and [`to_immut`] if a panic occurs during + /// one of those methods. Invalid, } @@ -52,24 +58,38 @@ impl TCTask { /// Make an immutable TCTask into a mutable TCTask. Does nothing if the task /// is already mutable. - fn to_mut(&mut self, tcreplica: &'static mut TCReplica) { + /// + /// # Safety + /// + /// The tcreplica pointer must not be NULL, and the replica it points to must not + /// be freed before TCTask.to_immut completes. + unsafe fn to_mut(&mut self, tcreplica: *mut TCReplica) { *self = match std::mem::replace(self, TCTask::Invalid) { TCTask::Immutable(task) => { - let rep_ref = tcreplica.borrow_mut(); - TCTask::Mutable(task.into_mut(rep_ref)) + // SAFETY: + // - tcreplica is not null (promised by caller) + // - tcreplica outlives the pointer in this variant (promised by caller) + let tcreplica_ref: &mut TCReplica = TCReplica::from_arg_ref(tcreplica); + let rep_ref = tcreplica_ref.borrow_mut(); + TCTask::Mutable(task.into_mut(rep_ref), tcreplica) } - TCTask::Mutable(task) => TCTask::Mutable(task), + TCTask::Mutable(task, tcreplica) => TCTask::Mutable(task, tcreplica), TCTask::Invalid => unreachable!(), } } /// Make an mutable TCTask into a immutable TCTask. Does nothing if the task /// is already immutable. - fn to_immut(&mut self, tcreplica: &mut TCReplica) { + fn to_immut(&mut self) { *self = match std::mem::replace(self, TCTask::Invalid) { TCTask::Immutable(task) => TCTask::Immutable(task), - TCTask::Mutable(task) => { - tcreplica.release_borrow(); + TCTask::Mutable(task, tcreplica) => { + // SAFETY: + // - tcreplica is not null (promised by caller of to_mut, which created this + // variant) + // - tcreplica is still alive (promised by caller of to_mut) + let tcreplica_ref: &mut TCReplica = unsafe { TCReplica::from_arg_ref(tcreplica) }; + tcreplica_ref.release_borrow(); TCTask::Immutable(task.into_immut()) } TCTask::Invalid => unreachable!(), @@ -94,7 +114,7 @@ where let tctask: &'a TCTask = unsafe { TCTask::from_arg_ref(task) }; let task: &'a Task = match tctask { TCTask::Immutable(t) => t, - TCTask::Mutable(t) => t.deref(), + TCTask::Mutable(t, _) => t.deref(), TCTask::Invalid => unreachable!(), }; f(task) @@ -112,7 +132,7 @@ where let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; let task: &'a mut TaskMut = match tctask { TCTask::Immutable(_) => panic!("Task is immutable"), - TCTask::Mutable(ref mut t) => t, + TCTask::Mutable(ref mut t, _) => t, TCTask::Invalid => unreachable!(), }; // TODO: add TCTask error handling, like replica @@ -121,10 +141,11 @@ where /// Convert an immutable task into a mutable task. /// -/// The task is modified in-place, and becomes mutable. +/// The task must not be NULL. It is modified in-place, and becomes mutable. /// -/// 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. +/// 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`: /// @@ -135,22 +156,30 @@ where /// if (!success) { ... } /// ``` #[no_mangle] -pub extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, rep: *mut TCReplica) { +pub extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut TCReplica) { + // SAFETY: + // - task is not null (promised by caller) + // - task outlives 'a (promised by caller) let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; - let tcreplica: &'static mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; - tctask.to_mut(tcreplica); + // 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 is modified in-place, and becomes immutable. +/// The task must not be NULL. It is modified in-place, and becomes immutable. /// -/// The replica may be used freely after this call. +/// The replica passed to `tc_task_to_mut` may be used freely after this call. #[no_mangle] -pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask, rep: *mut TCReplica) { +pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { + // SAFETY: + // - task is not null (promised by caller) + // - task outlives 'a (promised by caller) let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; - let tcreplica: &'static mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; - tctask.to_immut(tcreplica); + tctask.to_immut(); } /// Get a task's UUID. diff --git a/lib/src/taskmut.rs b/lib/src/taskmut.rs deleted file mode 100644 index 311647e54..000000000 --- a/lib/src/taskmut.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::{status::TCStatus, string::TCString, uuid::TCUuid}; -use taskchampion::{TaskMut, Replica}; -use std::cell::RefMut; - -/// A mutable task. -/// -/// A mutable task carries an exclusive reference to the replica, -/// meaning that no other replica operations can be carried out while -/// the mutable task still exists. -pub struct TCTaskMut { - inner: TaskMut<'static>, - replica: RefMut, -} - -impl TCTaskMut { - /// Borrow a TCTaskMut from C as an argument. - /// - /// # Safety - /// - /// The pointer must not be NULL. It is the caller's responsibility to ensure that the - /// lifetime assigned to the reference and the lifetime of the TCTaskMut itself do not outlive - /// the lifetime promised by C. - pub(crate) unsafe fn from_arg_ref<'a>(tctask: *mut TCTaskMut) -> &'a mut Self { - debug_assert!(!tctask.is_null()); - &mut *tctask - } - - /// Convert this to a return value for handing off to C. - pub(crate) fn return_val(self) -> *mut TCTaskMut { - Box::into_raw(Box::new(self)) - } - - /// Create a new TCTaskMut, given a RefMut to the replica. - pub(crate) fn from_immut(task: Task, rep: RefMut) -> Self { - // SAFETY: - // This ref will be embedded in the TaskMut, and we will hang onto - // the RefMut for that duration, guaranteeing no other mutable borrows. - let rep_ref: &'static mut = unsafe { rep.deref_mut() } - let task_mut = task.into_mut(rep_ref); - TCTaskMut { - inner: task_mut, - replica: rep, - } - } -} - -impl From for TCTaskMut { - fn from(rep: Replica) -> TCReplica { - TCReplica { - inner: RefCell::new(rep), - error: None, - } - } -} - - -/// 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. -#[no_mangle] -pub extern "C" fn tc_task_mut_free(task: *mut TCTaskMut) { - debug_assert!(!task.is_null()); - // SAFETY: - // - task is not NULL (promised by caller) - // - task's lifetime exceeds the drop (promised by caller) - // - task does not outlive this function (promised by caller) - drop(unsafe { Box::from_raw(task) }); -} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index f632938d5..18aa00faf 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -205,10 +205,11 @@ void tc_string_free(struct TCString *tcstring); /** * Convert an immutable task into a mutable task. * - * The task is modified in-place, and becomes mutable. + * The task must not be NULL. It is modified in-place, and becomes mutable. * - * 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. + * 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`: * @@ -219,16 +220,16 @@ void tc_string_free(struct TCString *tcstring); * if (!success) { ... } * ``` */ -void tc_task_to_mut(struct TCTask *task, struct TCReplica *rep); +void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica); /** * Convert a mutable task into an immutable task. * - * The task is modified in-place, and becomes immutable. + * The task must not be NULL. It is modified in-place, and becomes immutable. * - * The replica may be used freely after this call. + * The replica passed to `tc_task_to_mut` may be used freely after this call. */ -void tc_task_to_immut(struct TCTask *task, struct TCReplica *rep); +void tc_task_to_immut(struct TCTask *task); /** * Get a task's UUID. From d24319179c64455e61f2973f1746e947548fc566 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 30 Jan 2022 23:53:12 +0000 Subject: [PATCH 458/548] TCFoo::from_arg to take from a pointer --- lib/src/replica.rs | 19 +++++++++++++++---- lib/src/task.rs | 22 +++++++++++++--------- lib/taskchampion.h | 3 ++- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/lib/src/replica.rs b/lib/src/replica.rs index d9c28eb0b..a5643f045 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -29,7 +29,15 @@ impl TCReplica { &mut *tcreplica } - // TODO: from_arg_owned, use in drop + /// Take a TCReplica from C as an argument. + /// + /// # Safety + /// + /// The pointer must not be NULL. The pointer becomes invalid before this function returns. + pub(crate) unsafe fn from_arg(tcreplica: *mut TCReplica) -> Self { + debug_assert!(!tcreplica.is_null()); + *Box::from_raw(tcreplica) + } /// Convert this to a return value for handing off to C. pub(crate) fn return_val(self) -> *mut TCReplica { @@ -238,15 +246,18 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'st } } -/// Free a TCReplica. +/// Free a replica. The replica may not be used after this function returns and must not be freed +/// more than once. #[no_mangle] pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { - let replica: &mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; + // SAFETY: + // - rep is not NULL + // - caller will not use the TCReplica after this + let replica = unsafe { TCReplica::from_arg(rep) }; if replica.mut_borrowed { panic!("replica is borrowed and cannot be freed"); } drop(replica); - drop(unsafe { Box::from_raw(rep) }); } // TODO: tc_replica_rebuild_working_set diff --git a/lib/src/task.rs b/lib/src/task.rs index f929416d9..558843b17 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -49,7 +49,15 @@ impl TCTask { &mut *tctask } - // TODO: from_arg_owned, use in drop + /// Take a TCTask from C as an argument. + /// + /// # Safety + /// + /// The pointer must not be NULL. The pointer becomes invalid before this function returns. + pub(crate) unsafe fn from_arg<'a>(tctask: *mut TCTask) -> Self { + debug_assert!(!tctask.is_null()); + *Box::from_raw(tctask) + } /// Convert a TCTask to a return value for handing off to C. pub(crate) fn return_val(self) -> *mut TCTask { @@ -272,8 +280,10 @@ pub extern "C" fn tc_task_set_description<'a>( /// The restriction that the task must be immutable may be lifted (TODO) #[no_mangle] pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) { - // convert the task to immutable before freeing - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + // SAFETY: + // - rep is not NULL + // - caller will not use the TCTask after this + let tctask = unsafe { TCTask::from_arg(task) }; if !matches!(tctask, TCTask::Immutable(_)) { // this limit is in place because we require the caller to supply a pointer // to the replica to make a task immutable, and no such pointer is available @@ -281,10 +291,4 @@ pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) { panic!("Task must be immutable when freed"); } drop(tctask); - - // SAFETY: - // - task is not NULL (promised by caller) - // - task's lifetime exceeds the drop (promised by caller) - // - task does not outlive this function (promised by caller) - drop(unsafe { Box::from_raw(task) }); } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 18aa00faf..cda313171 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -138,7 +138,8 @@ enum TCResult tc_replica_undo(struct TCReplica *rep); struct TCString *tc_replica_error(struct TCReplica *rep); /** - * Free a TCReplica. + * 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); From 8bd9605b252bdc728659f79bbcc779deef0e4f3e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 31 Jan 2022 00:04:58 +0000 Subject: [PATCH 459/548] support starting and stopping tasks --- integration-tests/src/bindings_tests/task.c | 55 ++++++++++++++ lib/src/replica.rs | 4 +- lib/src/task.rs | 83 ++++++++++++++------- lib/taskchampion.h | 27 ++++++- 4 files changed, 136 insertions(+), 33 deletions(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index b4173a1eb..b75320df3 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -26,6 +26,34 @@ static void test_task_creation(void) { tc_replica_free(rep); } +// freeing a mutable task works, marking it immutable +static void test_task_free_mutable_task(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + TCUuid uuid = tc_task_get_uuid(task); + + tc_task_to_mut(task, rep); + TEST_ASSERT_TRUE(tc_task_set_status(task, TC_STATUS_DELETED)); + TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); + + tc_task_free(task); // implicitly converts to immut + + task = tc_replica_get_task(rep, uuid); + TEST_ASSERT_NOT_NULL(task); + TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); + tc_task_free(task); + + tc_replica_free(rep); +} + // updating status on a task works static void test_task_get_set_status(void) { TCReplica *rep = tc_replica_new_in_memory(); @@ -83,11 +111,38 @@ static void test_task_get_set_description(void) { tc_replica_free(rep); } +// starting and stopping a task works, as seen by tc_task_is_active +static void test_task_start_stop_is_active(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + TEST_ASSERT_FALSE(tc_task_is_active(task)); + + tc_task_to_mut(task, rep); + + TEST_ASSERT_FALSE(tc_task_is_active(task)); + tc_task_start(task); + TEST_ASSERT_TRUE(tc_task_is_active(task)); + tc_task_stop(task); + TEST_ASSERT_FALSE(tc_task_is_active(task)); + + tc_task_free(task); + tc_replica_free(rep); +} + int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. RUN_TEST(test_task_creation); + RUN_TEST(test_task_free_mutable_task); RUN_TEST(test_task_get_set_status); RUN_TEST(test_task_get_set_description); + RUN_TEST(test_task_start_stop_is_active); return UNITY_END(); } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index a5643f045..3dd1f0df9 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -251,8 +251,8 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'st #[no_mangle] pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { // SAFETY: - // - rep is not NULL - // - caller will not use the TCReplica after this + // - rep is not NULL (promised by caller) + // - caller will not use the TCReplica after this (promised by caller) let replica = unsafe { TCReplica::from_arg(rep) }; if replica.mut_borrowed { panic!("replica is borrowed and cannot be freed"); diff --git a/lib/src/task.rs b/lib/src/task.rs index 558843b17..14aa82746 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -10,6 +10,8 @@ use taskchampion::{Task, TaskMut}; /// /// A task carries no reference to the replica that created it, and can /// be used until it is freed or converted to a TaskMut. +/// +/// All `tc_task_..` functions taking a task as an argument require that it not be NULL. pub enum TCTask { /// A regular, immutable task Immutable(Task), @@ -202,7 +204,7 @@ pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { wrap(task, |task| task.get_status().into()) } -// TODO: get_taskmap +// TODO: tc_task_get_taskmap (?? then we have to wrap a map..) /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). @@ -214,19 +216,25 @@ pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCStr }) } -// TODO: :get_entry -// TODO: :get_wait -// TODO: :get_modified -// TODO: :is_waiting -// TODO: :is_active -// TODO: :has_tag -// TODO: :get_tags -// TODO: :get_annotations -// TODO: :get_uda -// TODO: :get_udas -// TODO: :get_legacy_uda -// TODO: :get_legacy_udas -// TODO: :get_modified +// TODO: tc_task_get_entry +// TODO: tc_task_get_wait +// TODO: tc_task_get_modified +// TODO: tc_task_is_waiting + +/// Check if a task is active (started and not stopped). +#[no_mangle] +pub extern "C" fn tc_task_is_active<'a>(task: *const TCTask) -> bool { + wrap(task, |task| task.is_active()) +} + +// TODO: tc_task_has_tag +// TODO: tc_task_get_tags +// TODO: tc_task_get_annotations +// TODO: tc_task_get_uda +// TODO: tc_task_get_udas +// TODO: tc_task_get_legacy_uda +// TODO: tc_task_get_legacy_udas +// TODO: tc_task_get_modified /// Set a mutable task's status. /// @@ -261,8 +269,29 @@ pub extern "C" fn tc_task_set_description<'a>( // TODO: tc_task_set_entry // TODO: tc_task_set_wait // TODO: tc_task_set_modified -// TODO: tc_task_start -// TODO: tc_task_stop + +/// Start a task. +/// +/// TODO: error +#[no_mangle] +pub extern "C" fn tc_task_start<'a>(task: *mut TCTask) { + wrap_mut(task, |task| { + task.start()?; + Ok(()) + }) +} + +/// Stop a task. +/// +/// TODO: error +#[no_mangle] +pub extern "C" fn tc_task_stop<'a>(task: *mut TCTask) { + wrap_mut(task, |task| { + task.stop()?; + Ok(()) + }) +} + // TODO: tc_task_done // TODO: tc_task_delete // TODO: tc_task_add_tag @@ -274,21 +303,19 @@ pub extern "C" fn tc_task_set_description<'a>( // TODO: tc_task_set_legacy_uda // TODO: tc_task_remove_legacy_uda -/// Free a task. The given task must not be NULL and must be immutable. The task must not be used -/// after this function returns, and must not be freed more than once. +/// 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. /// -/// The restriction that the task must be immutable may be lifted (TODO) +/// If the task is currently mutable, it will first be made immutable. #[no_mangle] pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) { // SAFETY: - // - rep is not NULL - // - caller will not use the TCTask after this - let tctask = unsafe { TCTask::from_arg(task) }; - if !matches!(tctask, TCTask::Immutable(_)) { - // this limit is in place because we require the caller to supply a pointer - // to the replica to make a task immutable, and no such pointer is available - // here. - panic!("Task must be immutable when freed"); - } + // - rep is not NULL (promised by caller) + // - caller will not use the TCTask after this (promised by caller) + let mut tctask = unsafe { TCTask::from_arg(task) }; + + // convert to immut if it was mutable + tctask.to_immut(); + drop(tctask); } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index cda313171..65de06667 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -67,6 +67,8 @@ typedef struct TCString TCString; * * A task carries no reference to the replica that created it, and can * be used until it is freed or converted to a TaskMut. + * + * All `tc_task_..` functions taking a task as an argument require that it not be NULL. */ typedef struct TCTask TCTask; @@ -248,6 +250,11 @@ enum TCStatus tc_task_get_status(const struct TCTask *task); */ struct TCString *tc_task_get_description(const struct TCTask *task); +/** + * Check if a task is active (started and not stopped). + */ +bool tc_task_is_active(const struct TCTask *task); + /** * Set a mutable task's status. * @@ -263,10 +270,24 @@ bool tc_task_set_status(struct TCTask *task, enum TCStatus status); bool tc_task_set_description(struct TCTask *task, struct TCString *description); /** - * Free a task. The given task must not be NULL and must be immutable. The task must not be used - * after this function returns, and must not be freed more than once. + * Start a task. * - * The restriction that the task must be immutable may be lifted (TODO) + * TODO: error + */ +void tc_task_start(struct TCTask *task); + +/** + * Stop a task. + * + * TODO: error + */ +void tc_task_stop(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); From ef0bb2ced46cb6b2c831380a0497d3d3a80ecb67 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 31 Jan 2022 19:34:21 +0000 Subject: [PATCH 460/548] allow task setters to return error values --- integration-tests/src/bindings_tests/task.c | 10 +-- lib/src/result.rs | 7 +- lib/src/task.rs | 85 +++++++++++++-------- lib/taskchampion.h | 22 +++--- 4 files changed, 75 insertions(+), 49 deletions(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index b75320df3..0a80bfaaf 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -41,7 +41,7 @@ static void test_task_free_mutable_task(void) { TCUuid uuid = tc_task_get_uuid(task); tc_task_to_mut(task, rep); - TEST_ASSERT_TRUE(tc_task_set_status(task, TC_STATUS_DELETED)); + TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_set_status(task, TC_STATUS_DELETED)); TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); tc_task_free(task); // implicitly converts to immut @@ -68,7 +68,7 @@ static void test_task_get_set_status(void) { TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); tc_task_to_mut(task, rep); - TEST_ASSERT_TRUE(tc_task_set_status(task, TC_STATUS_DELETED)); + TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_set_status(task, TC_STATUS_DELETED)); TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while mut tc_task_to_immut(task); TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while immut @@ -92,7 +92,7 @@ static void test_task_get_set_description(void) { TCString *desc; tc_task_to_mut(task, rep); - tc_task_set_description(task, tc_string_borrow("updated")); + TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_set_description(task, tc_string_borrow("updated"))); TEST_ASSERT_TRUE(desc = tc_task_get_description(task)); TEST_ASSERT_NOT_NULL(desc); @@ -127,9 +127,9 @@ static void test_task_start_stop_is_active(void) { tc_task_to_mut(task, rep); TEST_ASSERT_FALSE(tc_task_is_active(task)); - tc_task_start(task); + TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_start(task)); TEST_ASSERT_TRUE(tc_task_is_active(task)); - tc_task_stop(task); + TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_stop(task)); TEST_ASSERT_FALSE(tc_task_is_active(task)); tc_task_free(task); diff --git a/lib/src/result.rs b/lib/src/result.rs index e807e4690..12f5e3e6a 100644 --- a/lib/src/result.rs +++ b/lib/src/result.rs @@ -1,10 +1,11 @@ +// TODO: make true = 1, false = 0, error = -1 /// A result combines a boolean success value with /// an error response. It is equivalent to `Result`. /// cbindgen:prefix-with-name /// cbindgen:rename-all=ScreamingSnakeCase #[repr(C)] pub enum TCResult { - True, - False, - Error, + Error = -1, + False = 0, + True = 1, } diff --git a/lib/src/task.rs b/lib/src/task.rs index 14aa82746..531f33173 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,4 +1,6 @@ -use crate::{replica::TCReplica, status::TCStatus, string::TCString, uuid::TCUuid}; +use crate::{ + replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, uuid::TCUuid, +}; use std::ops::Deref; use taskchampion::{Task, TaskMut}; @@ -113,7 +115,8 @@ impl From for TCTask { } } -/// Utility function to get a shared reference to the underlying Task. +/// Utility function to get a shared reference to the underlying Task. All Task getters +/// are error-free, so this does not handle errors. fn wrap<'a, T, F>(task: *const TCTask, f: F) -> T where F: FnOnce(&Task) -> T, @@ -131,8 +134,9 @@ where } /// Utility function to get a mutable reference to the underlying Task. The -/// TCTask must be mutable. -fn wrap_mut<'a, T, F>(task: *mut TCTask, f: F) -> T +/// TCTask must be mutable. The inner function may use `?` syntax to return an +/// error, which will be represented with the `err_value` returned to C. +fn wrap_mut<'a, T, F>(task: *mut TCTask, f: F, err_value: T) -> T where F: FnOnce(&mut TaskMut) -> anyhow::Result, { @@ -145,8 +149,13 @@ where TCTask::Mutable(ref mut t, _) => t, TCTask::Invalid => unreachable!(), }; - // TODO: add TCTask error handling, like replica - f(task).unwrap() + match f(task) { + Ok(rv) => rv, + Err(e) => { + // TODO: add TCTask error handling, like replica + err_value + } + } } /// Convert an immutable task into a mutable task. @@ -238,31 +247,39 @@ pub extern "C" fn tc_task_is_active<'a>(task: *const TCTask) -> bool { /// Set a mutable task's status. /// -/// Returns false on error. +/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. #[no_mangle] -pub extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> bool { - wrap_mut(task, |task| { - task.set_status(status.into())?; - Ok(true) - }) +pub extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> TCResult { + wrap_mut( + task, + |task| { + task.set_status(status.into())?; + Ok(TCResult::True) + }, + TCResult::Error, + ) } /// Set a mutable task's description. /// -/// Returns false on error. +/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. #[no_mangle] pub extern "C" fn tc_task_set_description<'a>( task: *mut TCTask, description: *mut TCString, -) -> bool { +) -> TCResult { // SAFETY: // - tcstring is not NULL (promised by caller) // - caller is exclusive owner of tcstring (implicitly promised by caller) let description = unsafe { TCString::from_arg(description) }; - wrap_mut(task, |task| { - task.set_description(description.as_str()?.to_string())?; - Ok(true) - }) + wrap_mut( + task, + |task| { + task.set_description(description.as_str()?.to_string())?; + Ok(TCResult::True) + }, + TCResult::Error, + ) } // TODO: tc_task_set_description @@ -272,24 +289,32 @@ pub extern "C" fn tc_task_set_description<'a>( /// Start a task. /// -/// TODO: error +/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. #[no_mangle] -pub extern "C" fn tc_task_start<'a>(task: *mut TCTask) { - wrap_mut(task, |task| { - task.start()?; - Ok(()) - }) +pub extern "C" fn tc_task_start<'a>(task: *mut TCTask) -> TCResult { + wrap_mut( + task, + |task| { + task.start()?; + Ok(TCResult::True) + }, + TCResult::Error, + ) } /// Stop a task. /// -/// TODO: error +/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. #[no_mangle] -pub extern "C" fn tc_task_stop<'a>(task: *mut TCTask) { - wrap_mut(task, |task| { - task.stop()?; - Ok(()) - }) +pub extern "C" fn tc_task_stop<'a>(task: *mut TCTask) -> TCResult { + wrap_mut( + task, + |task| { + task.stop()?; + Ok(TCResult::True) + }, + TCResult::Error, + ) } // TODO: tc_task_done diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 65de06667..8330a1dbe 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -11,9 +11,9 @@ * an error response. It is equivalent to `Result`. */ typedef enum TCResult { - TC_RESULT_TRUE, - TC_RESULT_FALSE, - TC_RESULT_ERROR, + TC_RESULT_ERROR = -1, + TC_RESULT_FALSE = 0, + TC_RESULT_TRUE = 1, } TCResult; /** @@ -258,30 +258,30 @@ bool tc_task_is_active(const struct TCTask *task); /** * Set a mutable task's status. * - * Returns false on error. + * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. */ -bool tc_task_set_status(struct TCTask *task, enum TCStatus status); +enum TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); /** * Set a mutable task's description. * - * Returns false on error. + * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. */ -bool tc_task_set_description(struct TCTask *task, struct TCString *description); +enum TCResult tc_task_set_description(struct TCTask *task, struct TCString *description); /** * Start a task. * - * TODO: error + * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. */ -void tc_task_start(struct TCTask *task); +enum TCResult tc_task_start(struct TCTask *task); /** * Stop a task. * - * TODO: error + * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. */ -void tc_task_stop(struct TCTask *task); +enum TCResult tc_task_stop(struct TCTask *task); /** * Free a task. The given task must not be NULL. The task must not be used after this function From ce45c1004c678dff7c03619956248216e87f007d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 31 Jan 2022 19:44:00 +0000 Subject: [PATCH 461/548] add tc_task_add_tag and check errors --- integration-tests/src/bindings_tests/task.c | 30 +++++++++++++++++++++ lib/src/task.rs | 27 ++++++++++++++++--- lib/taskchampion.h | 7 +++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 0a80bfaaf..bceb657b0 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -136,6 +136,35 @@ static void test_task_start_stop_is_active(void) { tc_replica_free(rep); } +// adding tags to a task works, and invalid tags are rejected +static void task_task_add_tag(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + tc_task_to_mut(task, rep); + + TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_add_tag(task, tc_string_borrow("next"))); + + // invalid - synthetic tag + TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("PENDING"))); + // invald - not a valid tag string + TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("my tag"))); + // invald - not utf-8 + TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("\xf0\x28\x8c\x28"))); + + // TODO: check error messages + // TODO: test getting the tag + + tc_task_free(task); + tc_replica_free(rep); +} + int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. @@ -144,5 +173,6 @@ int task_tests(void) { RUN_TEST(test_task_get_set_status); RUN_TEST(test_task_get_set_description); RUN_TEST(test_task_start_stop_is_active); + RUN_TEST(task_task_add_tag); return UNITY_END(); } diff --git a/lib/src/task.rs b/lib/src/task.rs index 531f33173..365bd88af 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -2,7 +2,8 @@ use crate::{ replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, uuid::TCUuid, }; use std::ops::Deref; -use taskchampion::{Task, TaskMut}; +use std::str::FromStr; +use taskchampion::{Tag, Task, TaskMut}; /// A task, as publicly exposed by this library. /// @@ -282,7 +283,6 @@ pub extern "C" fn tc_task_set_description<'a>( ) } -// TODO: tc_task_set_description // TODO: tc_task_set_entry // TODO: tc_task_set_wait // TODO: tc_task_set_modified @@ -319,7 +319,28 @@ pub extern "C" fn tc_task_stop<'a>(task: *mut TCTask) -> TCResult { // TODO: tc_task_done // TODO: tc_task_delete -// TODO: tc_task_add_tag + +/// Add a tag to a mutable task. +/// +/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. +#[no_mangle] +pub extern "C" fn tc_task_add_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> TCResult { + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - caller is exclusive owner of tcstring (implicitly promised by caller) + let tcstring = unsafe { TCString::from_arg(tag) }; + wrap_mut( + task, + |task| { + let tagstr = tcstring.as_str()?; + let tag = Tag::from_str(tagstr)?; + task.add_tag(&tag)?; + Ok(TCResult::True) + }, + TCResult::Error, + ) +} + // TODO: tc_task_remove_tag // TODO: tc_task_add_annotation // TODO: tc_task_remove_annotation diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 8330a1dbe..d2853484e 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -283,6 +283,13 @@ enum TCResult tc_task_start(struct TCTask *task); */ enum TCResult tc_task_stop(struct TCTask *task); +/** + * Add a tag to a mutable task. + * + * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. + */ +enum TCResult tc_task_add_tag(struct TCTask *task, struct TCString *tag); + /** * 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. From 2dc93580859ffac2d7488e3078861fa8c1958507 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 31 Jan 2022 19:57:05 +0000 Subject: [PATCH 462/548] add warn(unsafe_op_in_unsafe_fn) --- lib/src/lib.rs | 1 + lib/src/replica.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 649febbd9..e1c3564ef 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,3 +1,4 @@ +#[warn(unsafe_op_in_unsafe_fn)] pub mod replica; pub mod result; pub mod status; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 3dd1f0df9..7ffb0bf15 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -26,17 +26,20 @@ impl TCReplica { /// the lifetime promised by C. pub(crate) unsafe fn from_arg_ref<'a>(tcreplica: *mut TCReplica) -> &'a mut Self { debug_assert!(!tcreplica.is_null()); - &mut *tcreplica + // SAFETY: see doc comment + unsafe { &mut *tcreplica } } /// Take a TCReplica from C as an argument. /// /// # Safety /// - /// The pointer must not be NULL. The pointer becomes invalid before this function returns. + /// The pointer must not be NULL and must point to a valid replica. The pointer becomes + /// invalid before this function returns and must not be used afterward. pub(crate) unsafe fn from_arg(tcreplica: *mut TCReplica) -> Self { debug_assert!(!tcreplica.is_null()); - *Box::from_raw(tcreplica) + // SAFETY: see doc comment + unsafe { *Box::from_raw(tcreplica) } } /// Convert this to a return value for handing off to C. From b675cef99cd95015584728817924c1eba3020e2a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 1 Feb 2022 00:35:02 +0000 Subject: [PATCH 463/548] add error handling for tasks --- integration-tests/src/bindings_tests/task.c | 13 ++- lib/src/lib.rs | 3 + lib/src/replica.rs | 5 +- lib/src/task.rs | 104 ++++++++++++-------- lib/src/util.rs | 5 + lib/taskchampion.h | 15 ++- 6 files changed, 93 insertions(+), 52 deletions(-) create mode 100644 lib/src/util.rs diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index bceb657b0..94024bc76 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -150,15 +150,26 @@ static void task_task_add_tag(void) { tc_task_to_mut(task, rep); TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_add_tag(task, tc_string_borrow("next"))); + TEST_ASSERT_NULL(tc_task_error(task)); // invalid - synthetic tag TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("PENDING"))); + TCString *err = tc_task_error(task); + TEST_ASSERT_NOT_NULL(err); + tc_string_free(err); + // invald - not a valid tag string TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("my tag"))); + err = tc_task_error(task); + TEST_ASSERT_NOT_NULL(err); + tc_string_free(err); + // invald - not utf-8 TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("\xf0\x28\x8c\x28"))); + err = tc_task_error(task); + TEST_ASSERT_NOT_NULL(err); + tc_string_free(err); - // TODO: check error messages // TODO: test getting the tag tc_task_free(task); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index e1c3564ef..c011ed36e 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,3 +1,6 @@ +mod util; + +// TODO: #![..] #[warn(unsafe_op_in_unsafe_fn)] pub mod replica; pub mod result; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 7ffb0bf15..7ec619495 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,3 +1,4 @@ +use crate::util::err_to_tcstring; use crate::{result::TCResult, status::TCStatus, string::TCString, task::TCTask, uuid::TCUuid}; use taskchampion::{Replica, StorageConfig, Uuid}; @@ -75,10 +76,6 @@ impl From for TCReplica { } } -fn err_to_tcstring(e: impl std::string::ToString) -> TCString<'static> { - TCString::from(e.to_string()) -} - /// Utility function to allow using `?` notation to return an error value. This makes /// a mutable borrow, because most Replica methods require a `&mut`. fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T diff --git a/lib/src/task.rs b/lib/src/task.rs index 365bd88af..870fc0f91 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,3 +1,4 @@ +use crate::util::err_to_tcstring; use crate::{ replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, uuid::TCUuid, }; @@ -15,7 +16,15 @@ use taskchampion::{Tag, Task, TaskMut}; /// be used until it is freed or converted to a TaskMut. /// /// All `tc_task_..` functions taking a task as an argument require that it not be NULL. -pub enum TCTask { +pub struct TCTask { + /// The wrapped Task or TaskMut + inner: Inner, + + /// The error from the most recent operation, if any + error: Option>, +} + +enum Inner { /// A regular, immutable task Immutable(Task), @@ -37,19 +46,7 @@ impl TCTask { /// The pointer must not be NULL. It is the caller's responsibility to ensure that the /// lifetime assigned to the reference and the lifetime of the TCTask itself do not outlive /// the lifetime promised by C. - pub(crate) unsafe fn from_arg_ref<'a>(tctask: *const TCTask) -> &'a Self { - debug_assert!(!tctask.is_null()); - &*tctask - } - - /// Borrow a TCTask from C as an argument, allowing mutation. - /// - /// # Safety - /// - /// The pointer must not be NULL. It is the caller's responsibility to ensure that the - /// lifetime assigned to the reference and the lifetime of the TCTask itself do not outlive - /// the lifetime promised by C. - pub(crate) unsafe fn from_arg_ref_mut<'a>(tctask: *mut TCTask) -> &'a mut Self { + pub(crate) unsafe fn from_arg_ref<'a>(tctask: *mut TCTask) -> &'a mut Self { debug_assert!(!tctask.is_null()); &mut *tctask } @@ -77,60 +74,64 @@ impl TCTask { /// The tcreplica pointer must not be NULL, and the replica it points to must not /// be freed before TCTask.to_immut completes. unsafe fn to_mut(&mut self, tcreplica: *mut TCReplica) { - *self = match std::mem::replace(self, TCTask::Invalid) { - TCTask::Immutable(task) => { + self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) { + Inner::Immutable(task) => { // SAFETY: // - tcreplica is not null (promised by caller) // - tcreplica outlives the pointer in this variant (promised by caller) let tcreplica_ref: &mut TCReplica = TCReplica::from_arg_ref(tcreplica); let rep_ref = tcreplica_ref.borrow_mut(); - TCTask::Mutable(task.into_mut(rep_ref), tcreplica) + Inner::Mutable(task.into_mut(rep_ref), tcreplica) } - TCTask::Mutable(task, tcreplica) => TCTask::Mutable(task, tcreplica), - TCTask::Invalid => unreachable!(), + Inner::Mutable(task, tcreplica) => Inner::Mutable(task, tcreplica), + Inner::Invalid => unreachable!(), } } /// Make an mutable TCTask into a immutable TCTask. Does nothing if the task /// is already immutable. fn to_immut(&mut self) { - *self = match std::mem::replace(self, TCTask::Invalid) { - TCTask::Immutable(task) => TCTask::Immutable(task), - TCTask::Mutable(task, tcreplica) => { + self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) { + Inner::Immutable(task) => Inner::Immutable(task), + Inner::Mutable(task, tcreplica) => { // SAFETY: // - tcreplica is not null (promised by caller of to_mut, which created this // variant) // - tcreplica is still alive (promised by caller of to_mut) let tcreplica_ref: &mut TCReplica = unsafe { TCReplica::from_arg_ref(tcreplica) }; tcreplica_ref.release_borrow(); - TCTask::Immutable(task.into_immut()) + Inner::Immutable(task.into_immut()) } - TCTask::Invalid => unreachable!(), + Inner::Invalid => unreachable!(), } } } impl From for TCTask { fn from(task: Task) -> TCTask { - TCTask::Immutable(task) + TCTask { + inner: Inner::Immutable(task), + error: None, + } } } /// Utility function to get a shared reference to the underlying Task. All Task getters /// are error-free, so this does not handle errors. -fn wrap<'a, T, F>(task: *const TCTask, f: F) -> T +fn wrap<'a, T, F>(task: *mut TCTask, f: F) -> T where F: FnOnce(&Task) -> T, { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a TCTask = unsafe { TCTask::from_arg_ref(task) }; - let task: &'a Task = match tctask { - TCTask::Immutable(t) => t, - TCTask::Mutable(t, _) => t.deref(), - TCTask::Invalid => unreachable!(), + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; + let task: &'a Task = match &tctask.inner { + Inner::Immutable(t) => t, + Inner::Mutable(t, _) => t.deref(), + Inner::Invalid => unreachable!(), }; + tctask.error = None; f(task) } @@ -144,16 +145,17 @@ where // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; - let task: &'a mut TaskMut = match tctask { - TCTask::Immutable(_) => panic!("Task is immutable"), - TCTask::Mutable(ref mut t, _) => t, - TCTask::Invalid => unreachable!(), + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; + let task: &'a mut TaskMut = match tctask.inner { + Inner::Immutable(_) => panic!("Task is immutable"), + Inner::Mutable(ref mut t, _) => t, + Inner::Invalid => unreachable!(), }; + tctask.error = None; match f(task) { Ok(rv) => rv, Err(e) => { - // TODO: add TCTask error handling, like replica + tctask.error = Some(err_to_tcstring(e)); err_value } } @@ -180,7 +182,7 @@ pub extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut TCReplic // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref(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, @@ -198,19 +200,19 @@ pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; tctask.to_immut(); } /// Get a task's UUID. #[no_mangle] -pub extern "C" fn tc_task_get_uuid(task: *const TCTask) -> TCUuid { +pub extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { wrap(task, |task| task.get_uuid().into()) } /// Get a task's status. #[no_mangle] -pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { +pub extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { wrap(task, |task| task.get_status().into()) } @@ -219,7 +221,7 @@ pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). #[no_mangle] -pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCString<'static> { +pub extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCString<'static> { wrap(task, |task| { let descr: TCString = task.get_description().into(); descr.return_val() @@ -233,7 +235,7 @@ pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCStr /// Check if a task is active (started and not stopped). #[no_mangle] -pub extern "C" fn tc_task_is_active<'a>(task: *const TCTask) -> bool { +pub extern "C" fn tc_task_is_active<'a>(task: *mut TCTask) -> bool { wrap(task, |task| task.is_active()) } @@ -349,6 +351,22 @@ pub extern "C" fn tc_task_add_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> // TODO: tc_task_set_legacy_uda // TODO: tc_task_remove_legacy_uda +/// Get the latest error for a task, or NULL 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. +#[no_mangle] +pub extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<'static> { + // SAFETY: + // - task is not null (promised by caller) + // - task outlives 'a (promised by caller) + let task: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; + if let Some(tcstring) = task.error.take() { + tcstring.return_val() + } else { + std::ptr::null_mut() + } +} + /// 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. /// diff --git a/lib/src/util.rs b/lib/src/util.rs new file mode 100644 index 000000000..0709d2640 --- /dev/null +++ b/lib/src/util.rs @@ -0,0 +1,5 @@ +use crate::string::TCString; + +pub(crate) fn err_to_tcstring(e: impl std::string::ToString) -> TCString<'static> { + TCString::from(e.to_string()) +} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index d2853484e..33a710068 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -237,23 +237,23 @@ void tc_task_to_immut(struct TCTask *task); /** * Get a task's UUID. */ -struct TCUuid tc_task_get_uuid(const struct TCTask *task); +struct TCUuid tc_task_get_uuid(struct TCTask *task); /** * Get a task's status. */ -enum TCStatus tc_task_get_status(const struct TCTask *task); +enum TCStatus tc_task_get_status(struct TCTask *task); /** * Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it * contains embedded NUL characters). */ -struct TCString *tc_task_get_description(const struct TCTask *task); +struct TCString *tc_task_get_description(struct TCTask *task); /** * Check if a task is active (started and not stopped). */ -bool tc_task_is_active(const struct TCTask *task); +bool tc_task_is_active(struct TCTask *task); /** * Set a mutable task's status. @@ -290,6 +290,13 @@ enum TCResult tc_task_stop(struct TCTask *task); */ enum TCResult tc_task_add_tag(struct TCTask *task, struct TCString *tag); +/** + * Get the latest error for a task, or NULL 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. From 22a6857c1bbc041e5b491ca82341bb6f84358d6d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 1 Feb 2022 00:46:04 +0000 Subject: [PATCH 464/548] simplify TCResult to just two values --- .../src/bindings_tests/replica.c | 17 +++++- integration-tests/src/bindings_tests/task.c | 12 ++--- lib/src/replica.rs | 22 +++++--- lib/src/result.rs | 10 ++-- lib/src/task.rs | 25 ++++----- lib/taskchampion.h | 52 ++++++++++--------- 6 files changed, 77 insertions(+), 61 deletions(-) diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 756c9acfe..bf3cdaa43 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -23,8 +23,20 @@ static void test_replica_creation_disk(void) { static void test_replica_undo_empty(void) { TCReplica *rep = tc_replica_new_in_memory(); TEST_ASSERT_NULL(tc_replica_error(rep)); - int rv = tc_replica_undo(rep); - TEST_ASSERT_EQUAL(TC_RESULT_FALSE, rv); + int undone; + int rv = tc_replica_undo(rep, &undone); + TEST_ASSERT_EQUAL(TC_RESULT_OK, rv); + TEST_ASSERT_EQUAL(0, undone); + TEST_ASSERT_NULL(tc_replica_error(rep)); + tc_replica_free(rep); +} + +// When tc_replica_undo is passed NULL for undone_out, it still succeeds +static void test_replica_undo_empty_null_undone_out(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + int rv = tc_replica_undo(rep, NULL); + TEST_ASSERT_EQUAL(TC_RESULT_OK, rv); TEST_ASSERT_NULL(tc_replica_error(rep)); tc_replica_free(rep); } @@ -110,6 +122,7 @@ int replica_tests(void) { RUN_TEST(test_replica_creation); RUN_TEST(test_replica_creation_disk); RUN_TEST(test_replica_undo_empty); + RUN_TEST(test_replica_undo_empty_null_undone_out); RUN_TEST(test_replica_task_creation); RUN_TEST(test_replica_task_import); RUN_TEST(test_replica_get_task_not_found); diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 94024bc76..9ca537760 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -41,7 +41,7 @@ static void test_task_free_mutable_task(void) { TCUuid uuid = tc_task_get_uuid(task); tc_task_to_mut(task, rep); - TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_set_status(task, TC_STATUS_DELETED)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_status(task, TC_STATUS_DELETED)); TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); tc_task_free(task); // implicitly converts to immut @@ -68,7 +68,7 @@ static void test_task_get_set_status(void) { TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); tc_task_to_mut(task, rep); - TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_set_status(task, TC_STATUS_DELETED)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_status(task, TC_STATUS_DELETED)); TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while mut tc_task_to_immut(task); TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while immut @@ -92,7 +92,7 @@ static void test_task_get_set_description(void) { TCString *desc; tc_task_to_mut(task, rep); - TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_set_description(task, tc_string_borrow("updated"))); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_description(task, tc_string_borrow("updated"))); TEST_ASSERT_TRUE(desc = tc_task_get_description(task)); TEST_ASSERT_NOT_NULL(desc); @@ -127,9 +127,9 @@ static void test_task_start_stop_is_active(void) { tc_task_to_mut(task, rep); TEST_ASSERT_FALSE(tc_task_is_active(task)); - TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_start(task)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_start(task)); TEST_ASSERT_TRUE(tc_task_is_active(task)); - TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_stop(task)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_stop(task)); TEST_ASSERT_FALSE(tc_task_is_active(task)); tc_task_free(task); @@ -149,7 +149,7 @@ static void task_task_add_tag(void) { tc_task_to_mut(task, rep); - TEST_ASSERT_EQUAL(TC_RESULT_TRUE, tc_task_add_tag(task, tc_string_borrow("next"))); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); TEST_ASSERT_NULL(tc_task_error(task)); // invalid - synthetic tag diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 7ec619495..0d2a5af4b 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -6,6 +6,9 @@ use taskchampion::{Replica, StorageConfig, Uuid}; /// for querying and modifying that data. /// /// TCReplicas are not threadsafe. +/// +/// When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then +/// `tc_replica_error` will return the error message. pub struct TCReplica { /// The wrapped Replica inner: Replica, @@ -213,18 +216,21 @@ pub extern "C" fn tc_replica_import_task_with_uuid( /// Undo local operations until the most recent UndoPoint. /// -/// Returns TC_RESULT_TRUE if an undo occurred, TC_RESULT_FALSE if there are no operations -/// to be undone, or TC_RESULT_ERROR on error. +/// 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. #[no_mangle] -pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica) -> TCResult { +pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica, undone_out: *mut i32) -> TCResult { wrap( rep, |rep| { - Ok(if rep.undo()? { - TCResult::True - } else { - TCResult::False - }) + let undone = if rep.undo()? { 1 } else { 0 }; + if !undone_out.is_null() { + // SAFETY: + // - undone_out is not NULL (just checked) + // - undone_out is properly aligned (implicitly promised by caller) + unsafe { *undone_out = undone }; + } + Ok(TCResult::Ok) }, TCResult::Error, ) diff --git a/lib/src/result.rs b/lib/src/result.rs index 12f5e3e6a..a7d53ea8d 100644 --- a/lib/src/result.rs +++ b/lib/src/result.rs @@ -1,11 +1,9 @@ -// TODO: make true = 1, false = 0, error = -1 -/// A result combines a boolean success value with -/// an error response. It is equivalent to `Result`. +/// 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 -#[repr(C)] +#[repr(i32)] pub enum TCResult { Error = -1, - False = 0, - True = 1, + Ok = 0, } diff --git a/lib/src/task.rs b/lib/src/task.rs index 870fc0f91..ab79079a3 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -16,6 +16,11 @@ use taskchampion::{Tag, Task, TaskMut}; /// be used until it is freed or converted to a TaskMut. /// /// 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. +/// +/// TCTasks are not threadsafe. pub struct TCTask { /// The wrapped Task or TaskMut inner: Inner, @@ -249,23 +254,19 @@ pub extern "C" fn tc_task_is_active<'a>(task: *mut TCTask) -> bool { // TODO: tc_task_get_modified /// Set a mutable task's status. -/// -/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. #[no_mangle] pub extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> TCResult { wrap_mut( task, |task| { task.set_status(status.into())?; - Ok(TCResult::True) + Ok(TCResult::Ok) }, TCResult::Error, ) } /// Set a mutable task's description. -/// -/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. #[no_mangle] pub extern "C" fn tc_task_set_description<'a>( task: *mut TCTask, @@ -279,7 +280,7 @@ pub extern "C" fn tc_task_set_description<'a>( task, |task| { task.set_description(description.as_str()?.to_string())?; - Ok(TCResult::True) + Ok(TCResult::Ok) }, TCResult::Error, ) @@ -290,30 +291,26 @@ pub extern "C" fn tc_task_set_description<'a>( // TODO: tc_task_set_modified /// Start a task. -/// -/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. #[no_mangle] pub extern "C" fn tc_task_start<'a>(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { task.start()?; - Ok(TCResult::True) + Ok(TCResult::Ok) }, TCResult::Error, ) } /// Stop a task. -/// -/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. #[no_mangle] pub extern "C" fn tc_task_stop<'a>(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { task.stop()?; - Ok(TCResult::True) + Ok(TCResult::Ok) }, TCResult::Error, ) @@ -323,8 +320,6 @@ pub extern "C" fn tc_task_stop<'a>(task: *mut TCTask) -> TCResult { // TODO: tc_task_delete /// Add a tag to a mutable task. -/// -/// Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. #[no_mangle] pub extern "C" fn tc_task_add_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> TCResult { // SAFETY: @@ -337,7 +332,7 @@ pub extern "C" fn tc_task_add_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> let tagstr = tcstring.as_str()?; let tag = Tag::from_str(tagstr)?; task.add_tag(&tag)?; - Ok(TCResult::True) + Ok(TCResult::Ok) }, TCResult::Error, ) diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 33a710068..e2c3cef79 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -7,14 +7,20 @@ #define TC_UUID_STRING_BYTES 36 /** - * A result combines a boolean success value with - * an error response. It is equivalent to `Result`. + * 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. */ -typedef enum TCResult { +enum TCResult +#ifdef __cplusplus + : int32_t +#endif // __cplusplus + { TC_RESULT_ERROR = -1, - TC_RESULT_FALSE = 0, - TC_RESULT_TRUE = 1, -} TCResult; + TC_RESULT_OK = 0, +}; +#ifndef __cplusplus +typedef int32_t TCResult; +#endif // __cplusplus /** * The status of a task, as defined by the task data model. @@ -35,6 +41,9 @@ typedef enum TCStatus { * for querying and modifying that data. * * TCReplicas are not threadsafe. + * + * When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then + * `tc_replica_error` will return the error message. */ typedef struct TCReplica TCReplica; @@ -69,6 +78,11 @@ typedef struct TCString TCString; * be used until it is freed or converted to a TaskMut. * * 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. + * + * TCTasks are not threadsafe. */ typedef struct TCTask TCTask; @@ -127,10 +141,10 @@ struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TC /** * Undo local operations until the most recent UndoPoint. * - * Returns TC_RESULT_TRUE if an undo occurred, TC_RESULT_FALSE if there are no operations - * to be undone, or TC_RESULT_ERROR on error. + * 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. */ -enum TCResult tc_replica_undo(struct TCReplica *rep); +TCResult tc_replica_undo(struct TCReplica *rep, int32_t *undone_out); /** * Get the latest error for a replica, or NULL if the last operation succeeded. Subsequent calls @@ -257,38 +271,28 @@ bool tc_task_is_active(struct TCTask *task); /** * Set a mutable task's status. - * - * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. */ -enum TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); +TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); /** * Set a mutable task's description. - * - * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. */ -enum TCResult tc_task_set_description(struct TCTask *task, struct TCString *description); +TCResult tc_task_set_description(struct TCTask *task, struct TCString *description); /** * Start a task. - * - * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. */ -enum TCResult tc_task_start(struct TCTask *task); +TCResult tc_task_start(struct TCTask *task); /** * Stop a task. - * - * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. */ -enum TCResult tc_task_stop(struct TCTask *task); +TCResult tc_task_stop(struct TCTask *task); /** * Add a tag to a mutable task. - * - * Returns TC_RESULT_TRUE on success and TC_RESULT_ERROR on failure. */ -enum TCResult tc_task_add_tag(struct TCTask *task, struct TCString *tag); +TCResult tc_task_add_tag(struct TCTask *task, struct TCString *tag); /** * Get the latest error for a task, or NULL if the last operation succeeded. Subsequent calls From 03ffb6ce83194aec3535b0a108db0fbb77430f84 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 1 Feb 2022 00:48:49 +0000 Subject: [PATCH 465/548] limit unsafe regions --- lib/src/lib.rs | 3 +-- lib/src/string.rs | 6 ++++-- lib/src/task.rs | 8 +++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index c011ed36e..7706f8d5c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,7 +1,6 @@ +#![warn(unsafe_op_in_unsafe_fn)] mod util; -// TODO: #![..] -#[warn(unsafe_op_in_unsafe_fn)] pub mod replica; pub mod result; pub mod status; diff --git a/lib/src/string.rs b/lib/src/string.rs index df0eebddb..163c2caf8 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -52,7 +52,8 @@ impl<'a> TCString<'a> { /// the lifetime promised by C. pub(crate) unsafe fn from_arg(tcstring: *mut TCString<'a>) -> Self { debug_assert!(!tcstring.is_null()); - *(Box::from_raw(tcstring)) + // SAFETY: see docstring + unsafe { *(Box::from_raw(tcstring)) } } /// Borrow a TCString from C as an argument. @@ -64,7 +65,8 @@ impl<'a> TCString<'a> { /// the lifetime promised by C. pub(crate) unsafe fn from_arg_ref(tcstring: *mut TCString<'a>) -> &'a mut Self { debug_assert!(!tcstring.is_null()); - &mut *tcstring + // SAFETY: see docstring + unsafe { &mut *tcstring } } /// Get a regular Rust &str for this value. diff --git a/lib/src/task.rs b/lib/src/task.rs index ab79079a3..841ae83f5 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -53,7 +53,8 @@ impl TCTask { /// the lifetime promised by C. pub(crate) unsafe fn from_arg_ref<'a>(tctask: *mut TCTask) -> &'a mut Self { debug_assert!(!tctask.is_null()); - &mut *tctask + // SAFETY: see docstring + unsafe { &mut *tctask } } /// Take a TCTask from C as an argument. @@ -63,7 +64,8 @@ impl TCTask { /// The pointer must not be NULL. The pointer becomes invalid before this function returns. pub(crate) unsafe fn from_arg<'a>(tctask: *mut TCTask) -> Self { debug_assert!(!tctask.is_null()); - *Box::from_raw(tctask) + // SAFETY: see docstring + unsafe { *Box::from_raw(tctask) } } /// Convert a TCTask to a return value for handing off to C. @@ -84,7 +86,7 @@ impl TCTask { // SAFETY: // - tcreplica is not null (promised by caller) // - tcreplica outlives the pointer in this variant (promised by caller) - let tcreplica_ref: &mut TCReplica = TCReplica::from_arg_ref(tcreplica); + let tcreplica_ref: &mut TCReplica = unsafe { TCReplica::from_arg_ref(tcreplica) }; let rep_ref = tcreplica_ref.borrow_mut(); Inner::Mutable(task.into_mut(rep_ref), tcreplica) } From f2b3e5fd0a6a8fe4497fba03ced1626f9861bf07 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 1 Feb 2022 01:02:49 +0000 Subject: [PATCH 466/548] tc_task_has_tag --- integration-tests/src/bindings_tests/task.c | 6 +++- lib/src/task.rs | 31 +++++++++++++++++++-- lib/taskchampion.h | 6 ++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 9ca537760..7fcb9a683 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -149,9 +149,13 @@ static void task_task_add_tag(void) { tc_task_to_mut(task, rep); + TEST_ASSERT_FALSE(tc_task_has_tag(task, tc_string_borrow("next"))); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); TEST_ASSERT_NULL(tc_task_error(task)); + TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next"))); + // invalid - synthetic tag TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("PENDING"))); TCString *err = tc_task_error(task); @@ -170,7 +174,7 @@ static void task_task_add_tag(void) { TEST_ASSERT_NOT_NULL(err); tc_string_free(err); - // TODO: test getting the tag + TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next"))); tc_task_free(task); tc_replica_free(rep); diff --git a/lib/src/task.rs b/lib/src/task.rs index 841ae83f5..3558ccf20 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -2,6 +2,7 @@ use crate::util::err_to_tcstring; use crate::{ replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, uuid::TCUuid, }; +use std::convert::TryFrom; use std::ops::Deref; use std::str::FromStr; use taskchampion::{Tag, Task, TaskMut}; @@ -168,6 +169,15 @@ where } } +impl TryFrom> for Tag { + type Error = anyhow::Error; + + fn try_from(tcstring: TCString) -> Result { + let tagstr = tcstring.as_str()?; + Tag::from_str(tagstr) + } +} + /// Convert an immutable task into a mutable task. /// /// The task must not be NULL. It is modified in-place, and becomes mutable. @@ -246,7 +256,23 @@ pub extern "C" fn tc_task_is_active<'a>(task: *mut TCTask) -> bool { wrap(task, |task| task.is_active()) } -// TODO: tc_task_has_tag +/// Check if a task has the given tag. If the tag is invalid, this function will simply return +/// false with no error from `tc_task_error`. The given tag must not be NULL. +#[no_mangle] +pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - caller is exclusive owner of tcstring (implicitly promised by caller) + let tcstring = unsafe { TCString::from_arg(tag) }; + wrap(task, |task| { + if let Ok(tag) = Tag::try_from(tcstring) { + task.has_tag(&tag) + } else { + false + } + }) +} + // TODO: tc_task_get_tags // TODO: tc_task_get_annotations // TODO: tc_task_get_uda @@ -331,8 +357,7 @@ pub extern "C" fn tc_task_add_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> wrap_mut( task, |task| { - let tagstr = tcstring.as_str()?; - let tag = Tag::from_str(tagstr)?; + let tag = Tag::try_from(tcstring)?; task.add_tag(&tag)?; Ok(TCResult::Ok) }, diff --git a/lib/taskchampion.h b/lib/taskchampion.h index e2c3cef79..a0b9e46d7 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -269,6 +269,12 @@ struct TCString *tc_task_get_description(struct TCTask *task); */ bool tc_task_is_active(struct TCTask *task); +/** + * Check if a task has the given tag. If the tag is invalid, this function will simply return + * false with no error from `tc_task_error`. The given tag must not be NULL. + */ +bool tc_task_has_tag(struct TCTask *task, struct TCString *tag); + /** * Set a mutable task's status. */ From e5625e1597bc57e4713d542b825da6429ccf2d1b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 1 Feb 2022 02:45:28 +0000 Subject: [PATCH 467/548] entry and wait time support --- Cargo.lock | 1 + integration-tests/src/bindings_tests/task.c | 63 ++++++++++++++++++ lib/Cargo.toml | 3 +- lib/build.rs | 2 +- lib/src/task.rs | 74 +++++++++++++++++++-- lib/taskchampion.h | 33 ++++++++- taskchampion/src/replica.rs | 2 +- taskchampion/src/task/task.rs | 26 +++++++- 8 files changed, 190 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 094b83445..ad2920754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3036,6 +3036,7 @@ version = "0.1.0" dependencies = [ "anyhow", "cbindgen", + "chrono", "libc", "taskchampion", "uuid", diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 7fcb9a683..8b264e41f 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -111,6 +111,67 @@ static void test_task_get_set_description(void) { tc_replica_free(rep); } +// updating entry on a task works +static void test_task_get_set_entry(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + // creation of a task sets entry to current time + TEST_ASSERT_NOT_EQUAL(0, tc_task_get_entry(task)); + + tc_task_to_mut(task, rep); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_entry(task, 1643679997)); + TEST_ASSERT_EQUAL(1643679997, tc_task_get_entry(task)); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_entry(task, 0)); + TEST_ASSERT_EQUAL(0, tc_task_get_entry(task)); + + tc_task_free(task); + + tc_replica_free(rep); +} + +// updating wait on a task works +static void test_task_get_set_wait_and_is_waiting(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + // wait is not set on creation + TEST_ASSERT_EQUAL(0, tc_task_get_wait(task)); + TEST_ASSERT_FALSE(tc_task_is_waiting(task)); + + tc_task_to_mut(task, rep); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 3643679997)); // 2085 + TEST_ASSERT_EQUAL(3643679997, tc_task_get_wait(task)); + TEST_ASSERT_TRUE(tc_task_is_waiting(task)); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 643679997)); // THE PAST! + TEST_ASSERT_EQUAL(643679997, tc_task_get_wait(task)); + TEST_ASSERT_FALSE(tc_task_is_waiting(task)); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 0)); + TEST_ASSERT_EQUAL(0, tc_task_get_wait(task)); + TEST_ASSERT_FALSE(tc_task_is_waiting(task)); + + tc_task_free(task); + + tc_replica_free(rep); +} + // starting and stopping a task works, as seen by tc_task_is_active static void test_task_start_stop_is_active(void) { TCReplica *rep = tc_replica_new_in_memory(); @@ -187,6 +248,8 @@ int task_tests(void) { RUN_TEST(test_task_free_mutable_task); RUN_TEST(test_task_get_set_status); RUN_TEST(test_task_get_set_description); + RUN_TEST(test_task_get_set_entry); + RUN_TEST(test_task_get_set_wait_and_is_waiting); RUN_TEST(test_task_start_stop_is_active); RUN_TEST(task_task_add_tag); return UNITY_END(); diff --git a/lib/Cargo.toml b/lib/Cargo.toml index b8545df90..66b8c6051 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -10,8 +10,9 @@ crate-type = ["cdylib"] [dependencies] libc = "0.2.113" +chrono = "^0.4.10" taskchampion = { path = "../taskchampion" } -uuid = { version = "^0.8.2", features = ["serde", "v4"] } +uuid = { version = "^0.8.2", features = ["v4"] } anyhow = "1.0" [build-dependencies] diff --git a/lib/build.rs b/lib/build.rs index 462b707a4..f8f75d3b5 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -10,7 +10,7 @@ fn main() { .with_config(Config { language: Language::C, cpp_compat: true, - sys_includes: vec!["stdbool.h".into(), "stdint.h".into()], + sys_includes: vec!["stdbool.h".into(), "stdint.h".into(), "time.h".into()], usize_is_size_t: true, no_includes: true, enumeration: EnumConfig { diff --git a/lib/src/task.rs b/lib/src/task.rs index 3558ccf20..03318d1f9 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -2,6 +2,7 @@ use crate::util::err_to_tcstring; use crate::{ replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, uuid::TCUuid, }; +use chrono::{DateTime, TimeZone, Utc}; use std::convert::TryFrom; use std::ops::Deref; use std::str::FromStr; @@ -13,8 +14,9 @@ use taskchampion::{Tag, Task, TaskMut}; /// to make any changes, and doing so requires exclusive access to the replica /// until the task is freed or converted back to immutable mode. /// -/// A task carries no reference to the replica that created it, and can -/// be used until it is freed or converted to a TaskMut. +/// 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. /// @@ -178,6 +180,22 @@ impl TryFrom> for Tag { } } +/// Convert a DateTime to a libc::time_t, or zero if not set. +fn to_time_t(timestamp: Option>) -> libc::time_t { + timestamp + .map(|ts| ts.timestamp() as libc::time_t) + .unwrap_or(0 as libc::time_t) +} + +/// Convert a libc::time_t to Option>, treating time zero as None +fn to_datetime(time: libc::time_t) -> Option> { + if time == 0 { + None + } else { + Some(Utc.timestamp(time as i64, 0)) + } +} + /// Convert an immutable task into a mutable task. /// /// The task must not be NULL. It is modified in-place, and becomes mutable. @@ -245,14 +263,29 @@ pub extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCStrin }) } -// TODO: tc_task_get_entry -// TODO: tc_task_get_wait +/// Get the entry timestamp for a task (when it was created), or 0 if not set. +#[no_mangle] +pub extern "C" fn tc_task_get_entry<'a>(task: *mut TCTask) -> libc::time_t { + wrap(task, |task| to_time_t(task.get_entry())) +} + +/// Get the wait timestamp for a task, or 0 if not set. +#[no_mangle] +pub extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t { + wrap(task, |task| to_time_t(task.get_wait())) +} + // TODO: tc_task_get_modified -// TODO: tc_task_is_waiting + +/// Check if a task is waiting. +#[no_mangle] +pub extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool { + wrap(task, |task| task.is_waiting()) +} /// Check if a task is active (started and not stopped). #[no_mangle] -pub extern "C" fn tc_task_is_active<'a>(task: *mut TCTask) -> bool { +pub extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { wrap(task, |task| task.is_active()) } @@ -314,7 +347,34 @@ pub extern "C" fn tc_task_set_description<'a>( ) } -// TODO: tc_task_set_entry +/// Set a mutable task's entry (creation time). Pass entry=0 to unset +/// the entry field. +#[no_mangle] +pub extern "C" fn tc_task_set_entry<'a>(task: *mut TCTask, entry: libc::time_t) -> TCResult { + wrap_mut( + task, + |task| { + task.set_entry(to_datetime(entry))?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + +/// Set a mutable task's wait (creation time). Pass wait=0 to unset the +/// wait field. +#[no_mangle] +pub extern "C" fn tc_task_set_wait<'a>(task: *mut TCTask, wait: libc::time_t) -> TCResult { + wrap_mut( + task, + |task| { + task.set_wait(to_datetime(wait))?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + // TODO: tc_task_set_wait // TODO: tc_task_set_modified diff --git a/lib/taskchampion.h b/lib/taskchampion.h index a0b9e46d7..f102d03aa 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -1,5 +1,6 @@ #include #include +#include /** * Length, in bytes, of the string representation of a UUID (without NUL terminator) @@ -74,8 +75,9 @@ typedef struct TCString TCString; * to make any changes, and doing so requires exclusive access to the replica * until the task is freed or converted back to immutable mode. * - * A task carries no reference to the replica that created it, and can - * be used until it is freed or converted to a TaskMut. + * 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. * @@ -264,6 +266,21 @@ enum TCStatus tc_task_get_status(struct TCTask *task); */ struct TCString *tc_task_get_description(struct TCTask *task); +/** + * 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); + +/** + * Get the wait timestamp for a task, or 0 if not set. + */ +time_t tc_task_get_wait(struct TCTask *task); + +/** + * Check if a task is waiting. + */ +bool tc_task_is_waiting(struct TCTask *task); + /** * Check if a task is active (started and not stopped). */ @@ -285,6 +302,18 @@ TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); */ 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. + */ +TCResult tc_task_set_entry(struct TCTask *task, time_t entry); + +/** + * Set a mutable task's wait (creation time). Pass wait=0 to unset the + * wait field. + */ +TCResult tc_task_set_wait(struct TCTask *task, time_t wait); + /** * Start a task. */ diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index b85cb84c7..2dd1e4887 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -110,7 +110,7 @@ impl Replica { let mut task = Task::new(uuid, taskmap).into_mut(self); task.set_description(description)?; task.set_status(status)?; - task.set_entry(Utc::now())?; + task.set_entry(Some(Utc::now()))?; trace!("task {} created", uuid); Ok(task.into_immut()) } diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 82bafb7d7..d5517f5b4 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -120,6 +120,10 @@ impl Task { .unwrap_or("") } + pub fn get_entry(&self) -> Option> { + self.get_timestamp(Prop::Entry.as_ref()) + } + pub fn get_priority(&self) -> Priority { self.taskmap .get(Prop::Status.as_ref()) @@ -299,8 +303,8 @@ impl<'r> TaskMut<'r> { self.set_string(Prop::Description.as_ref(), Some(description)) } - pub(crate) fn set_entry(&mut self, entry: DateTime) -> anyhow::Result<()> { - self.set_timestamp(Prop::Entry.as_ref(), Some(entry)) + pub fn set_entry(&mut self, entry: Option>) -> anyhow::Result<()> { + self.set_timestamp(Prop::Entry.as_ref(), entry) } pub fn set_wait(&mut self, wait: Option>) -> anyhow::Result<()> { @@ -526,6 +530,24 @@ mod test { assert!(!task.is_active()); } + #[test] + fn test_entry_not_set() { + let task = Task::new(Uuid::new_v4(), TaskMap::new()); + assert_eq!(task.get_entry(), None); + } + + #[test] + fn test_entry_set() { + let ts = Utc.ymd(1980, 1, 1).and_hms(0, 0, 0); + let task = Task::new( + Uuid::new_v4(), + vec![(String::from("entry"), format!("{}", ts.timestamp()))] + .drain(..) + .collect(), + ); + assert_eq!(task.get_entry(), Some(ts)); + } + #[test] fn test_wait_not_set() { let task = Task::new(Uuid::new_v4(), TaskMap::new()); From 8b160c7ee82fe3e376fae15ff0c64db77fcb7372 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 1 Feb 2022 03:01:09 +0000 Subject: [PATCH 468/548] more task functions --- integration-tests/src/bindings_tests/task.c | 52 +++++++++++++++++ lib/src/task.rs | 63 ++++++++++++++++----- lib/taskchampion.h | 23 +++++++- 3 files changed, 123 insertions(+), 15 deletions(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 8b264e41f..74dcfcda7 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -172,6 +172,33 @@ static void test_task_get_set_wait_and_is_waiting(void) { tc_replica_free(rep); } +// updating modified on a task works +static void test_task_get_set_modified(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + // creation of a task sets modified to current time + TEST_ASSERT_NOT_EQUAL(0, tc_task_get_modified(task)); + + tc_task_to_mut(task, rep); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_modified(task, 1643679997)); + TEST_ASSERT_EQUAL(1643679997, tc_task_get_modified(task)); + + // zero is not allowed + TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_set_modified(task, 0)); + + tc_task_free(task); + + tc_replica_free(rep); +} + // starting and stopping a task works, as seen by tc_task_is_active static void test_task_start_stop_is_active(void) { TCReplica *rep = tc_replica_new_in_memory(); @@ -197,6 +224,29 @@ static void test_task_start_stop_is_active(void) { tc_replica_free(rep); } +// tc_task_done and delete work and set the status +static void test_task_done_and_delete(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + tc_task_to_mut(task, rep); + + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_done(task)); + TEST_ASSERT_EQUAL(TC_STATUS_COMPLETED, tc_task_get_status(task)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_delete(task)); + TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); + + tc_task_free(task); + tc_replica_free(rep); +} + // adding tags to a task works, and invalid tags are rejected static void task_task_add_tag(void) { TCReplica *rep = tc_replica_new_in_memory(); @@ -249,8 +299,10 @@ int task_tests(void) { RUN_TEST(test_task_get_set_status); RUN_TEST(test_task_get_set_description); RUN_TEST(test_task_get_set_entry); + RUN_TEST(test_task_get_set_modified); RUN_TEST(test_task_get_set_wait_and_is_waiting); RUN_TEST(test_task_start_stop_is_active); + RUN_TEST(test_task_done_and_delete); RUN_TEST(task_task_add_tag); return UNITY_END(); } diff --git a/lib/src/task.rs b/lib/src/task.rs index 03318d1f9..4caa9a0c7 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -275,7 +275,11 @@ pub extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t { wrap(task, |task| to_time_t(task.get_wait())) } -// TODO: tc_task_get_modified +/// Get the modified timestamp for a task, or 0 if not set. +#[no_mangle] +pub extern "C" fn tc_task_get_modified<'a>(task: *mut TCTask) -> libc::time_t { + wrap(task, |task| to_time_t(task.get_modified())) +} /// Check if a task is waiting. #[no_mangle] @@ -312,7 +316,6 @@ pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> // TODO: tc_task_get_udas // TODO: tc_task_get_legacy_uda // TODO: tc_task_get_legacy_udas -// TODO: tc_task_get_modified /// Set a mutable task's status. #[no_mangle] @@ -350,7 +353,7 @@ pub extern "C" fn tc_task_set_description<'a>( /// Set a mutable task's entry (creation time). Pass entry=0 to unset /// the entry field. #[no_mangle] -pub extern "C" fn tc_task_set_entry<'a>(task: *mut TCTask, entry: libc::time_t) -> TCResult { +pub extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult { wrap_mut( task, |task| { @@ -361,10 +364,9 @@ pub extern "C" fn tc_task_set_entry<'a>(task: *mut TCTask, entry: libc::time_t) ) } -/// Set a mutable task's wait (creation time). Pass wait=0 to unset the -/// wait field. +/// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. #[no_mangle] -pub extern "C" fn tc_task_set_wait<'a>(task: *mut TCTask, wait: libc::time_t) -> TCResult { +pub extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult { wrap_mut( task, |task| { @@ -375,12 +377,24 @@ pub extern "C" fn tc_task_set_wait<'a>(task: *mut TCTask, wait: libc::time_t) -> ) } -// TODO: tc_task_set_wait -// TODO: tc_task_set_modified +/// Set a mutable task's modified timestamp. The value cannot be zero. +#[no_mangle] +pub extern "C" fn tc_task_set_modified(task: *mut TCTask, modified: libc::time_t) -> TCResult { + wrap_mut( + task, + |task| { + task.set_modified( + to_datetime(modified).ok_or_else(|| anyhow::anyhow!("modified cannot be zero"))?, + )?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} /// Start a task. #[no_mangle] -pub extern "C" fn tc_task_start<'a>(task: *mut TCTask) -> TCResult { +pub extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -393,7 +407,7 @@ pub extern "C" fn tc_task_start<'a>(task: *mut TCTask) -> TCResult { /// Stop a task. #[no_mangle] -pub extern "C" fn tc_task_stop<'a>(task: *mut TCTask) -> TCResult { +pub extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -404,12 +418,35 @@ pub extern "C" fn tc_task_stop<'a>(task: *mut TCTask) -> TCResult { ) } -// TODO: tc_task_done -// TODO: tc_task_delete +/// Mark a task as done. +#[no_mangle] +pub extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { + wrap_mut( + task, + |task| { + task.done()?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + +/// Mark a task as deleted. +#[no_mangle] +pub extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { + wrap_mut( + task, + |task| { + task.delete()?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} /// Add a tag to a mutable task. #[no_mangle] -pub extern "C" fn tc_task_add_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> TCResult { +pub extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { // SAFETY: // - tcstring is not NULL (promised by caller) // - caller is exclusive owner of tcstring (implicitly promised by caller) diff --git a/lib/taskchampion.h b/lib/taskchampion.h index f102d03aa..4a3a33b96 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -276,6 +276,11 @@ time_t tc_task_get_entry(struct TCTask *task); */ time_t tc_task_get_wait(struct TCTask *task); +/** + * Get the modified timestamp for a task, or 0 if not set. + */ +time_t tc_task_get_modified(struct TCTask *task); + /** * Check if a task is waiting. */ @@ -309,11 +314,15 @@ TCResult tc_task_set_description(struct TCTask *task, struct TCString *descripti TCResult tc_task_set_entry(struct TCTask *task, time_t entry); /** - * Set a mutable task's wait (creation time). Pass wait=0 to unset the - * wait field. + * 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); +/** + * Set a mutable task's modified timestamp. The value cannot be zero. + */ +TCResult tc_task_set_modified(struct TCTask *task, time_t modified); + /** * Start a task. */ @@ -324,6 +333,16 @@ TCResult tc_task_start(struct TCTask *task); */ TCResult tc_task_stop(struct TCTask *task); +/** + * Mark a task as done. + */ +TCResult tc_task_done(struct TCTask *task); + +/** + * Mark a task as deleted. + */ +TCResult tc_task_delete(struct TCTask *task); + /** * Add a tag to a mutable task. */ From 3dd2ae501194c248e943347717ca4167da4d3a57 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 2 Feb 2022 03:28:54 +0000 Subject: [PATCH 469/548] implement TCTags as an array --- integration-tests/src/bindings_tests/task.c | 49 ++++++++++- lib/src/arrays.rs | 98 +++++++++++++++++++++ lib/src/lib.rs | 1 + lib/src/string.rs | 4 +- lib/src/task.rs | 42 ++++++++- lib/taskchampion.h | 40 +++++++++ 6 files changed, 226 insertions(+), 8 deletions(-) create mode 100644 lib/src/arrays.rs diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 74dcfcda7..2ef89204c 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -247,8 +247,8 @@ static void test_task_done_and_delete(void) { tc_replica_free(rep); } -// adding tags to a task works, and invalid tags are rejected -static void task_task_add_tag(void) { +// adding and removing tags to a task works, and invalid tags are rejected +static void test_task_add_remove_has_tag(void) { TCReplica *rep = tc_replica_new_in_memory(); TEST_ASSERT_NULL(tc_replica_error(rep)); @@ -287,6 +287,48 @@ static void task_task_add_tag(void) { TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next"))); + // remove the tag + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_remove_tag(task, tc_string_borrow("next"))); + TEST_ASSERT_NULL(tc_task_error(task)); + + TEST_ASSERT_FALSE(tc_task_has_tag(task, tc_string_borrow("next"))); + + tc_task_free(task); + tc_replica_free(rep); +} + +// get_tags returns the list of tags +static void test_task_get_tags(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + tc_task_to_mut(task, rep); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); + + TCTags tags = tc_task_get_tags(task); + + int found_pending = false, found_next = false; + for (size_t i = 0; i < tags.num_tags; i++) { + if (strcmp("PENDING", tc_string_content(tags.tags[i])) == 0) { + found_pending = true; + } + if (strcmp("next", tc_string_content(tags.tags[i])) == 0) { + found_next = true; + } + } + TEST_ASSERT_TRUE(found_pending); + TEST_ASSERT_TRUE(found_next); + + tc_tags_free(&tags); + TEST_ASSERT_NULL(tags.tags); + tc_task_free(task); tc_replica_free(rep); } @@ -303,6 +345,7 @@ int task_tests(void) { RUN_TEST(test_task_get_set_wait_and_is_waiting); RUN_TEST(test_task_start_stop_is_active); RUN_TEST(test_task_done_and_delete); - RUN_TEST(task_task_add_tag); + RUN_TEST(test_task_add_remove_has_tag); + RUN_TEST(test_task_get_tags); return UNITY_END(); } diff --git a/lib/src/arrays.rs b/lib/src/arrays.rs new file mode 100644 index 000000000..e94ead43b --- /dev/null +++ b/lib/src/arrays.rs @@ -0,0 +1,98 @@ +use crate::string::TCString; +use std::ptr::NonNull; + +// TODO: generalize to TCStrings? + +/// TCTags represents a list of tags associated with a task. +/// +/// The content of this struct must be treated as read-only. +/// +/// The lifetime of a TCTags instance is independent of the task, and it +/// will remain valid even if the task is freed. +#[repr(C)] +pub struct TCTags { + // TODO: can we use NonNull here? + /// strings representing each tag. these remain owned by the + /// TCTags instance and will be freed by tc_tags_free. + tags: *const NonNull>, + /// number of tags in tags + num_tags: libc::size_t, + /// total size of tags (internal use only) + _capacity: libc::size_t, +} + +impl Default for TCTags { + fn default() -> Self { + Self { + tags: std::ptr::null_mut(), + num_tags: 0, + _capacity: 0, + } + } +} + +impl TCTags { + /// Create a Vec of TCStrings into a TCTags instance. + pub(crate) fn new(tags: Vec>>) -> Self { + // emulate Vec::into_raw_parts(): + // - disable dropping the Vec with ManuallyDrop + // - extract ptr, len, and capacity using those methods + let mut tags = std::mem::ManuallyDrop::new(tags); + Self { + tags: tags.as_mut_ptr(), + num_tags: tags.len(), + _capacity: tags.capacity(), + } + } + + /// Convert a TCTags to a Vec<_>. + /// + /// # Safety + /// + /// Tags must be _exactly_ as created by [`new`] + unsafe fn into_vec(self) -> Vec>> { + // SAFETY: + // + // * tags.tags needs to have been previously allocated via Vec<*mut TCString> + // * TCString needs to have the same size and alignment as what ptr was allocated with. + // * length needs to be less than or equal to capacity. + // * capacity needs to be the capacity that the pointer was allocated with. + // * vec elements are not NULL + // + // All of this is true for a value returned from `new`, which the caller promised + // not to change. + unsafe { Vec::from_raw_parts(self.tags as *mut _, self.num_tags, self._capacity) } + } +} + +/// Free a TCTags instance. The given pointer must not be NULL. The instance must not be used +/// after this call. +#[no_mangle] +pub extern "C" fn tc_tags_free<'a>(tctags: *mut TCTags) { + debug_assert!(!tctags.is_null()); + // SAFETY: + // - tctags is not NULL + // - tctags is valid (caller promises it has not been changed) + // - caller will not use the TCTags after this (promised by caller) + let tctags: &'a mut TCTags = unsafe { &mut *tctags }; + + debug_assert!(!tctags.tags.is_null()); + + // replace the caller's TCTags with one containing a NULL pointer + let tctags: TCTags = std::mem::take(tctags); + + // convert to a regular Vec + // SAFETY: + // - tctags is exactly as returned from TCTags::new (promised by caller) + let mut vec: Vec<_> = unsafe { tctags.into_vec() }; + + // drop each contained string + for tcstring in vec.drain(..) { + // SAFETY: + // - the pointer is not NULL (as created by TCString::new) + // - C does not expect the string's lifetime to exceed this function + drop(unsafe { TCString::from_arg(tcstring.as_ptr()) }); + } + + drop(vec); +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 7706f8d5c..e72f4bf9b 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,6 +1,7 @@ #![warn(unsafe_op_in_unsafe_fn)] mod util; +pub mod arrays; pub mod replica; pub mod result; pub mod status; diff --git a/lib/src/string.rs b/lib/src/string.rs index 163c2caf8..e711966a3 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -108,8 +108,8 @@ impl<'a> From for TCString<'a> { } } -impl<'a> From<&str> for TCString<'a> { - fn from(string: &str) -> TCString<'a> { +impl<'a> From<&str> for TCString<'static> { + fn from(string: &str) -> TCString<'static> { TCString::String(string.to_string()) } } diff --git a/lib/src/task.rs b/lib/src/task.rs index 4caa9a0c7..6c305792d 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,13 +1,17 @@ use crate::util::err_to_tcstring; use crate::{ - replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, uuid::TCUuid, + arrays::TCTags, replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, + uuid::TCUuid, }; use chrono::{DateTime, TimeZone, Utc}; use std::convert::TryFrom; use std::ops::Deref; +use std::ptr::NonNull; use std::str::FromStr; use taskchampion::{Tag, Task, TaskMut}; +// TODO: use NonNull more + /// A task, as publicly exposed by this library. /// /// A task begins in "immutable" mode. It must be converted to "mutable" mode @@ -310,7 +314,22 @@ pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> }) } -// TODO: tc_task_get_tags +/// Get the tags for the task. The task must not be NULL. +#[no_mangle] +pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCTags { + wrap(task, |task| { + let tcstrings: Vec>> = task + .get_tags() + .map(|t| { + let t_ptr = TCString::from(t.as_ref()).return_val(); + // SAFETY: t_ptr was just created and is not NULL + unsafe { NonNull::new_unchecked(t_ptr) } + }) + .collect(); + TCTags::new(tcstrings) + }) +} + // TODO: tc_task_get_annotations // TODO: tc_task_get_uda // TODO: tc_task_get_udas @@ -462,7 +481,24 @@ pub extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCRe ) } -// TODO: tc_task_remove_tag +/// Remove a tag from a mutable task. +#[no_mangle] +pub extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - caller is exclusive owner of tcstring (implicitly promised by caller) + let tcstring = unsafe { TCString::from_arg(tag) }; + wrap_mut( + task, + |task| { + let tag = Tag::try_from(tcstring)?; + task.remove_tag(&tag)?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + // TODO: tc_task_add_annotation // TODO: tc_task_remove_annotation // TODO: tc_task_set_uda diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 4a3a33b96..854119bcc 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -88,6 +88,30 @@ typedef struct TCString TCString; */ typedef struct TCTask TCTask; +/** + * TCTags represents a list of tags associated with a task. + * + * The content of this struct must be treated as read-only. + * + * The lifetime of a TCTags instance is independent of the task, and it + * will remain valid even if the task is freed. + */ +typedef struct TCTags { + /** + * strings representing each tag. these remain owned by the + * TCTags instance and will be freed by tc_tags_free. + */ + struct TCString *const *tags; + /** + * number of tags in tags + */ + size_t num_tags; + /** + * total size of tags (internal use only; do not change) + */ + size_t _capacity; +} TCTags; + /** * 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. @@ -101,6 +125,12 @@ typedef struct TCUuid { extern "C" { #endif // __cplusplus +/** + * Free a TCTags instance. The given pointer must not be NULL. The instance must not be used + * after this call. + */ +void tc_tags_free(struct TCTags *tctags); + /** * Create a new TCReplica with an in-memory database. The contents of the database will be * lost when it is freed. @@ -297,6 +327,11 @@ bool tc_task_is_active(struct TCTask *task); */ bool tc_task_has_tag(struct TCTask *task, struct TCString *tag); +/** + * Get the tags for the task. The task must not be NULL. + */ +struct TCTags tc_task_get_tags(struct TCTask *task); + /** * Set a mutable task's status. */ @@ -348,6 +383,11 @@ TCResult tc_task_delete(struct TCTask *task); */ TCResult tc_task_add_tag(struct TCTask *task, struct TCString *tag); +/** + * Remove a tag from a mutable task. + */ +TCResult tc_task_remove_tag(struct TCTask *task, struct TCString *tag); + /** * Get the latest error for a task, or NULL 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 From a46a9d587a9cfa5dc30e5e7fbc436800b4161029 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 5 Feb 2022 00:23:53 +0000 Subject: [PATCH 470/548] fix typo --- integration-tests/README.md | 2 +- lib/taskchampion.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/README.md b/integration-tests/README.md index b0a94d050..cbec3515a 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -26,5 +26,5 @@ Keep the `RUN_TEST`s in the same order as the functions they call. To add a suite, -1. Add a new C file in `integration-tests/src/bindings_tests/`, based off of one of hte others. +1. Add a new C file in `integration-tests/src/bindings_tests/`, based off of one of the others. 1. Add a the suite name to `suites` in `integration-tests/build.rs`. diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 854119bcc..c001538a6 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -107,7 +107,7 @@ typedef struct TCTags { */ size_t num_tags; /** - * total size of tags (internal use only; do not change) + * total size of tags (internal use only) */ size_t _capacity; } TCTags; From 23ba6a57b3916c25a7ebef99b58fceed8de01f94 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Feb 2022 04:02:53 +0000 Subject: [PATCH 471/548] switch to PassByValue and PassByPointer traits --- lib/src/arrays.rs | 7 ++- lib/src/lib.rs | 2 + lib/src/replica.rs | 104 ++++++++++++++++++--------------------------- lib/src/string.rs | 98 +++++++++++++++++++----------------------- lib/src/task.rs | 44 +++++++++---------- lib/src/uuid.rs | 45 +++++++++++--------- lib/taskchampion.h | 79 ++++++++++++++++++++++------------ 7 files changed, 187 insertions(+), 192 deletions(-) diff --git a/lib/src/arrays.rs b/lib/src/arrays.rs index e94ead43b..3c266cb3c 100644 --- a/lib/src/arrays.rs +++ b/lib/src/arrays.rs @@ -1,4 +1,5 @@ use crate::string::TCString; +use crate::traits::*; use std::ptr::NonNull; // TODO: generalize to TCStrings? @@ -88,10 +89,8 @@ pub extern "C" fn tc_tags_free<'a>(tctags: *mut TCTags) { // drop each contained string for tcstring in vec.drain(..) { - // SAFETY: - // - the pointer is not NULL (as created by TCString::new) - // - C does not expect the string's lifetime to exceed this function - drop(unsafe { TCString::from_arg(tcstring.as_ptr()) }); + // SAFETY: see TCString docstring + drop(unsafe { TCString::take_from_arg(tcstring.as_ptr()) }); } drop(vec); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index e72f4bf9b..18cba4ac9 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,4 +1,6 @@ #![warn(unsafe_op_in_unsafe_fn)] + +mod traits; mod util; pub mod arrays; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 0d2a5af4b..7512d1f91 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,3 +1,4 @@ +use crate::traits::*; use crate::util::err_to_tcstring; use crate::{result::TCResult, status::TCStatus, string::TCString, task::TCTask, uuid::TCUuid}; use taskchampion::{Replica, StorageConfig, Uuid}; @@ -5,10 +6,25 @@ use taskchampion::{Replica, StorageConfig, Uuid}; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. /// -/// TCReplicas are not threadsafe. +/// # 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` becmes invalid and must not be used again. +/// +/// TCReplicas are not threadsafe. pub struct TCReplica { /// The wrapped Replica inner: Replica, @@ -20,37 +36,9 @@ pub struct TCReplica { error: Option>, } +impl PassByPointer for TCReplica {} + impl TCReplica { - /// Borrow a TCReplica from C as an argument. - /// - /// # Safety - /// - /// The pointer must not be NULL. It is the caller's responsibility to ensure that the - /// lifetime assigned to the reference and the lifetime of the TCReplica itself do not outlive - /// the lifetime promised by C. - pub(crate) unsafe fn from_arg_ref<'a>(tcreplica: *mut TCReplica) -> &'a mut Self { - debug_assert!(!tcreplica.is_null()); - // SAFETY: see doc comment - unsafe { &mut *tcreplica } - } - - /// Take a TCReplica from C as an argument. - /// - /// # Safety - /// - /// The pointer must not be NULL and must point to a valid replica. The pointer becomes - /// invalid before this function returns and must not be used afterward. - pub(crate) unsafe fn from_arg(tcreplica: *mut TCReplica) -> Self { - debug_assert!(!tcreplica.is_null()); - // SAFETY: see doc comment - unsafe { *Box::from_raw(tcreplica) } - } - - /// Convert this to a return value for handing off to C. - pub(crate) fn return_val(self) -> *mut TCReplica { - Box::into_raw(Box::new(self)) - } - /// Mutably borrow the inner Replica pub(crate) fn borrow_mut(&mut self) -> &mut Replica { if self.mut_borrowed { @@ -85,10 +73,8 @@ fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T where F: FnOnce(&mut Replica) -> anyhow::Result, { - // SAFETY: - // - rep is not null (promised by caller) - // - rep outlives 'a (promised by caller) - let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; + // SAFETY: see type docstring + let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref_mut(rep) }; if rep.mut_borrowed { panic!("replica is borrowed and cannot be used"); } @@ -109,21 +95,19 @@ pub extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { let storage = StorageConfig::InMemory .into_storage() .expect("in-memory always succeeds"); - TCReplica::from(Replica::new(storage)).return_val() + // SAFETY: see type docstring + unsafe { TCReplica::from(Replica::new(storage)).return_val() } } -/// Create a new TCReplica with an on-disk database having the given filename. The filename must -/// not be NULL. On error, a string is written to the `error_out` parameter (if it is not NULL) and -/// NULL is returned. +/// 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. #[no_mangle] pub extern "C" fn tc_replica_new_on_disk<'a>( path: *mut TCString, error_out: *mut *mut TCString, ) -> *mut TCReplica { - // SAFETY: - // - tcstring is not NULL (promised by caller) - // - caller is exclusive owner of tcstring (implicitly promised by caller) - let path = unsafe { TCString::from_arg(path) }; + // SAFETY: see TCString docstring + let path = unsafe { TCString::take_from_arg(path) }; let storage_res = StorageConfig::OnDisk { taskdb_dir: path.to_path_buf(), } @@ -141,7 +125,8 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( } }; - TCReplica::from(Replica::new(storage)).return_val() + // SAFETY: see type docstring + unsafe { TCReplica::from(Replica::new(storage)).return_val() } } // TODO: tc_replica_all_tasks @@ -153,11 +138,11 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( /// Returns NULL when the task does not exist, and on error. Consult tc_replica_error /// to distinguish the two conditions. #[no_mangle] -pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, uuid: TCUuid) -> *mut TCTask { +pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask { wrap( rep, |rep| { - let uuid: Uuid = uuid.into(); + let uuid = Uuid::from_arg(tcuuid); if let Some(task) = rep.get_task(uuid)? { Ok(TCTask::from(task).return_val()) } else { @@ -170,8 +155,6 @@ pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, uuid: TCUuid) -> *mut /// Create a new task. The task must not already exist. /// -/// The description must not be NULL. -/// /// Returns the task, or NULL on error. #[no_mangle] pub extern "C" fn tc_replica_new_task( @@ -179,10 +162,8 @@ pub extern "C" fn tc_replica_new_task( status: TCStatus, description: *mut TCString, ) -> *mut TCTask { - // SAFETY: - // - tcstring is not NULL (promised by caller) - // - caller is exclusive owner of tcstring (implicitly promised by caller) - let description = unsafe { TCString::from_arg(description) }; + // SAFETY: see TCString docstring + let description = unsafe { TCString::take_from_arg(description) }; wrap( rep, |rep| { @@ -199,12 +180,12 @@ pub extern "C" fn tc_replica_new_task( #[no_mangle] pub extern "C" fn tc_replica_import_task_with_uuid( rep: *mut TCReplica, - uuid: TCUuid, + tcuuid: TCUuid, ) -> *mut TCTask { wrap( rep, |rep| { - let uuid: Uuid = uuid.into(); + let uuid = Uuid::from_arg(tcuuid); let task = rep.import_task_with_uuid(uuid)?; Ok(TCTask::from(task).return_val()) }, @@ -241,12 +222,11 @@ pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica, undone_out: *mut i32) /// returned string. #[no_mangle] pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'static> { - // SAFETY: - // - rep is not null (promised by caller) - // - rep outlives 'a (promised by caller) - let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref(rep) }; + // SAFETY: see type docstring + let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref_mut(rep) }; if let Some(tcstring) = rep.error.take() { - tcstring.return_val() + // SAFETY: see TCString docstring + unsafe { tcstring.return_val() } } else { std::ptr::null_mut() } @@ -256,10 +236,8 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'st /// more than once. #[no_mangle] pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { - // SAFETY: - // - rep is not NULL (promised by caller) - // - caller will not use the TCReplica after this (promised by caller) - let replica = unsafe { TCReplica::from_arg(rep) }; + // SAFETY: see type docstring + let replica = unsafe { TCReplica::take_from_arg(rep) }; if replica.mut_borrowed { panic!("replica is borrowed and cannot be freed"); } diff --git a/lib/src/string.rs b/lib/src/string.rs index e711966a3..d3f952189 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -1,24 +1,39 @@ +use crate::traits::*; use std::ffi::{CStr, CString, OsStr}; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; use std::str::Utf8Error; -/// TCString supports passing strings into and out of the TaskChampion API. A string can contain -/// embedded NUL characters. Strings containing such 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. +/// TCString supports passing strings into and out of the TaskChampion API. /// -/// Rust expects all strings to be UTF-8, and API functions will fail if given a TCString -/// containing invalid UTF-8. +/// # Rust Strings and C Strings /// -/// Unless specified otherwise, functions in this API take ownership of a TCString when it is given -/// as a function argument, and free the string before returning. Callers must not use or free -/// strings after passing them to such API functions. +/// 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. /// -/// When a TCString appears as a return value or output argument, it is the responsibility of the -/// caller to free the string. +/// # 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 +/// +/// When a `*TCString` appears as a return value or output argument, ownership is passed to the +/// caller. The caller must pass that ownerhsip back to another function or free the string. +/// +/// Any function taking a `*TCReplica` 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 pointer is invalid when the function returns. Callers +/// must not use or free TCStrings after passing them to such API functions. +/// +/// TCStrings are not threadsafe. pub enum TCString<'a> { CString(CString), CStr(&'a CStr), @@ -39,36 +54,9 @@ impl<'a> Default for TCString<'a> { } } +impl<'a> PassByPointer for TCString<'a> {} + impl<'a> TCString<'a> { - /// Take a TCString from C as an argument. - /// - /// C callers generally expect TC functions to take ownership of a string, which is what this - /// function does. - /// - /// # Safety - /// - /// The pointer must not be NULL. It is the caller's responsibility to ensure that the - /// lifetime assigned to the reference and the lifetime of the TCString itself do not outlive - /// the lifetime promised by C. - pub(crate) unsafe fn from_arg(tcstring: *mut TCString<'a>) -> Self { - debug_assert!(!tcstring.is_null()); - // SAFETY: see docstring - unsafe { *(Box::from_raw(tcstring)) } - } - - /// Borrow a TCString from C as an argument. - /// - /// # Safety - /// - /// The pointer must not be NULL. It is the caller's responsibility to ensure that the - /// lifetime assigned to the reference and the lifetime of the TCString itself do not outlive - /// the lifetime promised by C. - pub(crate) unsafe fn from_arg_ref(tcstring: *mut TCString<'a>) -> &'a mut Self { - debug_assert!(!tcstring.is_null()); - // SAFETY: see docstring - unsafe { &mut *tcstring } - } - /// Get a regular Rust &str for this value. pub(crate) fn as_str(&self) -> Result<&str, std::str::Utf8Error> { match self { @@ -95,11 +83,6 @@ impl<'a> TCString<'a> { let path: &OsStr = OsStr::from_bytes(self.as_bytes()); path.to_os_string().into() } - - /// Convert this to a return value for handing off to C. - pub(crate) fn return_val(self) -> *mut TCString<'a> { - Box::into_raw(Box::new(self)) - } } impl<'a> From for TCString<'a> { @@ -137,7 +120,8 @@ pub extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<' // - cstr contains a valid NUL terminator (promised by caller) // - cstr's content will not change before it is destroyed (promised by caller) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - TCString::CStr(cstr).return_val() + // SAFETY: see docstring + unsafe { TCString::CStr(cstr).return_val() } } /// Create a new TCString by cloning the content of the given C string. The resulting TCString @@ -151,7 +135,8 @@ pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'s // - cstr contains a valid NUL terminator (promised by caller) // - cstr's content will not change before it is destroyed (by C convention) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - TCString::CString(cstr.into()).return_val() + // SAFETY: see docstring + unsafe { TCString::CString(cstr.into()).return_val() } } /// Create a new TCString containing the given string with the given length. This allows creation @@ -176,14 +161,15 @@ pub extern "C" fn tc_string_clone_with_len( let vec = slice.to_vec(); // try converting to a string, which is the only variant that can contain embedded NULs. If // the bytes are not valid utf-8, store that information for reporting later. - match String::from_utf8(vec) { + let tcstring = match String::from_utf8(vec) { Ok(string) => TCString::String(string), Err(e) => { let (e, vec) = (e.utf8_error(), e.into_bytes()); TCString::InvalidUtf8(e, vec) } - } - .return_val() + }; + // SAFETY: see docstring + unsafe { tcstring.return_val() } } /// Get the content of the string as a regular C string. The given string must not be NULL. The @@ -200,11 +186,12 @@ pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_c // - tcstring is not NULL (promised by caller) // - lifetime of tcstring outlives the lifetime of this function // - lifetime of tcstring outlives the lifetime of the returned pointer (promised by caller) - let tcstring = unsafe { TCString::from_arg_ref(tcstring) }; + let tcstring = unsafe { TCString::from_arg_ref_mut(tcstring) }; // if we have a String, we need to consume it and turn it into // a CString. if matches!(tcstring, TCString::String(_)) { + // TODO: put this in a method if let TCString::String(string) = std::mem::take(tcstring) { match CString::new(string) { Ok(cstring) => { @@ -237,7 +224,8 @@ pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_c /// 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 TC API function. +/// returned buffer is valid until the TCString is freed or passed to another TaskChampio +/// function. /// /// This function does _not_ take ownership of the TCString. #[no_mangle] @@ -274,5 +262,5 @@ pub extern "C" fn tc_string_free(tcstring: *mut TCString) { // SAFETY: // - tcstring is not NULL (promised by caller) // - caller is exclusive owner of tcstring (promised by caller) - drop(unsafe { TCString::from_arg(tcstring) }); + drop(unsafe { TCString::take_from_arg(tcstring) }); } diff --git a/lib/src/task.rs b/lib/src/task.rs index 6c305792d..87dcdd773 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,3 +1,4 @@ +use crate::traits::*; use crate::util::err_to_tcstring; use crate::{ arrays::TCTags, replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, @@ -93,7 +94,8 @@ impl TCTask { // SAFETY: // - tcreplica is not null (promised by caller) // - tcreplica outlives the pointer in this variant (promised by caller) - let tcreplica_ref: &mut TCReplica = unsafe { TCReplica::from_arg_ref(tcreplica) }; + let tcreplica_ref: &mut TCReplica = + unsafe { TCReplica::from_arg_ref_mut(tcreplica) }; let rep_ref = tcreplica_ref.borrow_mut(); Inner::Mutable(task.into_mut(rep_ref), tcreplica) } @@ -112,7 +114,8 @@ impl TCTask { // - tcreplica is not null (promised by caller of to_mut, which created this // variant) // - tcreplica is still alive (promised by caller of to_mut) - let tcreplica_ref: &mut TCReplica = unsafe { TCReplica::from_arg_ref(tcreplica) }; + let tcreplica_ref: &mut TCReplica = + unsafe { TCReplica::from_arg_ref_mut(tcreplica) }; tcreplica_ref.release_borrow(); Inner::Immutable(task.into_immut()) } @@ -246,7 +249,7 @@ pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { /// Get a task's UUID. #[no_mangle] pub extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { - wrap(task, |task| task.get_uuid().into()) + wrap(task, |task| task.get_uuid().return_val()) } /// Get a task's status. @@ -263,7 +266,8 @@ pub extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { pub extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCString<'static> { wrap(task, |task| { let descr: TCString = task.get_description().into(); - descr.return_val() + // SAFETY: see TCString docstring + unsafe { descr.return_val() } }) } @@ -298,13 +302,12 @@ pub extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { } /// Check if a task has the given tag. If the tag is invalid, this function will simply return -/// false with no error from `tc_task_error`. The given tag must not be NULL. +/// false, with no error from `tc_task_error`. +// TODO: why no error?? #[no_mangle] pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { - // SAFETY: - // - tcstring is not NULL (promised by caller) - // - caller is exclusive owner of tcstring (implicitly promised by caller) - let tcstring = unsafe { TCString::from_arg(tag) }; + // SAFETY: see TCString docstring + let tcstring = unsafe { TCString::take_from_arg(tag) }; wrap(task, |task| { if let Ok(tag) = Tag::try_from(tcstring) { task.has_tag(&tag) @@ -321,7 +324,8 @@ pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCTags { let tcstrings: Vec>> = task .get_tags() .map(|t| { - let t_ptr = TCString::from(t.as_ref()).return_val(); + // SAFETY: see TCString docstring + let t_ptr = unsafe { TCString::from(t.as_ref()).return_val() }; // SAFETY: t_ptr was just created and is not NULL unsafe { NonNull::new_unchecked(t_ptr) } }) @@ -355,10 +359,8 @@ pub extern "C" fn tc_task_set_description<'a>( task: *mut TCTask, description: *mut TCString, ) -> TCResult { - // SAFETY: - // - tcstring is not NULL (promised by caller) - // - caller is exclusive owner of tcstring (implicitly promised by caller) - let description = unsafe { TCString::from_arg(description) }; + // SAFETY: see TCString docstring + let description = unsafe { TCString::take_from_arg(description) }; wrap_mut( task, |task| { @@ -466,10 +468,8 @@ pub extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { /// Add a tag to a mutable task. #[no_mangle] pub extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { - // SAFETY: - // - tcstring is not NULL (promised by caller) - // - caller is exclusive owner of tcstring (implicitly promised by caller) - let tcstring = unsafe { TCString::from_arg(tag) }; + // SAFETY: see TCString docstring + let tcstring = unsafe { TCString::take_from_arg(tag) }; wrap_mut( task, |task| { @@ -484,10 +484,8 @@ pub extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCRe /// Remove a tag from a mutable task. #[no_mangle] pub extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { - // SAFETY: - // - tcstring is not NULL (promised by caller) - // - caller is exclusive owner of tcstring (implicitly promised by caller) - let tcstring = unsafe { TCString::from_arg(tag) }; + // SAFETY: see TCString docstring + let tcstring = unsafe { TCString::take_from_arg(tag) }; wrap_mut( task, |task| { @@ -516,7 +514,7 @@ pub extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<'static> // - task outlives 'a (promised by caller) let task: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; if let Some(tcstring) = task.error.take() { - tcstring.return_val() + unsafe { tcstring.return_val() } } else { std::ptr::null_mut() } diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 9dcd3797b..8f57c7d1a 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -1,4 +1,5 @@ use crate::string::TCString; +use crate::traits::*; use libc; use taskchampion::Uuid; @@ -9,28 +10,30 @@ use taskchampion::Uuid; #[repr(C)] pub struct TCUuid([u8; 16]); -impl From for TCUuid { - fn from(uuid: Uuid) -> TCUuid { - TCUuid(*uuid.as_bytes()) - } -} +impl PassByValue for Uuid { + type CType = TCUuid; -impl From for Uuid { - fn from(uuid: TCUuid) -> Uuid { - Uuid::from_bytes(uuid.0) + unsafe fn from_ctype(arg: TCUuid) -> Self { + // SAFETY: + // - any 16-byte value is a valid Uuid + Uuid::from_bytes(arg.0) + } + + fn as_ctype(self) -> TCUuid { + TCUuid(*self.as_bytes()) } } /// Create a new, randomly-generated UUID. #[no_mangle] pub extern "C" fn tc_uuid_new_v4() -> TCUuid { - Uuid::new_v4().into() + Uuid::new_v4().return_val() } /// Create a new UUID with the nil value. #[no_mangle] pub extern "C" fn tc_uuid_nil() -> TCUuid { - Uuid::nil().into() + Uuid::nil().return_val() } // NOTE: this must be a simple constant so that cbindgen can evaluate it @@ -40,7 +43,7 @@ pub const TC_UUID_STRING_BYTES: usize = 36; /// 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. #[no_mangle] -pub extern "C" fn tc_uuid_to_buf<'a>(uuid: TCUuid, buf: *mut libc::c_char) { +pub extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { debug_assert!(!buf.is_null()); // SAFETY: // - buf is valid for len bytes (by C convention) @@ -51,34 +54,34 @@ pub extern "C" fn tc_uuid_to_buf<'a>(uuid: TCUuid, buf: *mut libc::c_char) { let buf: &'a mut [u8] = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, ::uuid::adapter::Hyphenated::LENGTH) }; - let uuid: Uuid = uuid.into(); + let uuid: Uuid = Uuid::from_arg(tcuuid); uuid.to_hyphenated().encode_lower(buf); } /// 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. #[no_mangle] -pub extern "C" fn tc_uuid_to_str(uuid: TCUuid) -> *mut TCString<'static> { - let uuid: Uuid = uuid.into(); +pub extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { + let uuid: Uuid = Uuid::from_arg(tcuuid); let s = uuid.to_string(); - TCString::from(s).return_val() + // SAFETY: see TCString docstring + unsafe { TCString::from(s).return_val() } } -/// Parse the given string as a UUID. The string must not be NULL. Returns false on failure. +/// Parse the given string as a UUID. Returns false on failure. #[no_mangle] pub extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> bool { + // TODO: TCResult instead debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null()); - // SAFETY: - // - tcstring is not NULL (promised by caller) - // - caller is exclusive owner of tcstring (implicitly promised by caller) - let s = unsafe { TCString::from_arg(s) }; + // SAFETY: see TCString docstring + let s = unsafe { TCString::take_from_arg(s) }; if let Ok(s) = s.as_str() { if let Ok(u) = Uuid::parse_str(s) { // SAFETY: // - uuid_out is not NULL (promised by caller) // - alignment is not required - unsafe { *uuid_out = u.into() }; + unsafe { u.to_arg_out(uuid_out) }; return true; } } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index c001538a6..ccb8ff1db 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -41,30 +41,59 @@ typedef enum TCStatus { * A replica represents an instance of a user's task data, providing an easy interface * for querying and modifying that data. * - * TCReplicas are not threadsafe. + * # 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` becmes invalid and must not be used again. + * + * TCReplicas are not threadsafe. */ typedef struct TCReplica TCReplica; /** - * TCString supports passing strings into and out of the TaskChampion API. A string can contain - * embedded NUL characters. Strings containing such 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. + * TCString supports passing strings into and out of the TaskChampion API. * - * Rust expects all strings to be UTF-8, and API functions will fail if given a TCString - * containing invalid UTF-8. + * # Rust Strings and C Strings * - * Unless specified otherwise, functions in this API take ownership of a TCString when it is given - * as a function argument, and free the string before returning. Callers must not use or free - * strings after passing them to such API functions. + * 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. * - * When a TCString appears as a return value or output argument, it is the responsibility of the - * caller to free the string. + * # 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 + * + * When a `*TCString` appears as a return value or output argument, ownership is passed to the + * caller. The caller must pass that ownerhsip back to another function or free the string. + * + * Any function taking a `*TCReplica` 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 pointer is invalid when the function returns. Callers + * must not use or free TCStrings after passing them to such API functions. + * + * TCStrings are not threadsafe. */ typedef struct TCString TCString; @@ -138,9 +167,8 @@ void tc_tags_free(struct TCTags *tctags); struct TCReplica *tc_replica_new_in_memory(void); /** - * Create a new TCReplica with an on-disk database having the given filename. The filename must - * not be NULL. On error, a string is written to the `error_out` parameter (if it is not NULL) and - * NULL is returned. + * 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. */ struct TCReplica *tc_replica_new_on_disk(struct TCString *path, struct TCString **error_out); @@ -150,13 +178,11 @@ struct TCReplica *tc_replica_new_on_disk(struct TCString *path, struct TCString * 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 uuid); +struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid); /** * Create a new task. The task must not already exist. * - * The description must not be NULL. - * * Returns the task, or NULL on error. */ struct TCTask *tc_replica_new_task(struct TCReplica *rep, @@ -168,7 +194,7 @@ struct TCTask *tc_replica_new_task(struct TCReplica *rep, * * Returns the task, or NULL on error. */ -struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid uuid); +struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); /** * Undo local operations until the most recent UndoPoint. @@ -239,7 +265,8 @@ const char *tc_string_content(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 TC API function. + * returned buffer is valid until the TCString is freed or passed to another TaskChampio + * function. * * This function does _not_ take ownership of the TCString. */ @@ -323,7 +350,7 @@ bool tc_task_is_active(struct TCTask *task); /** * Check if a task has the given tag. If the tag is invalid, this function will simply return - * false with no error from `tc_task_error`. The given tag must not be NULL. + * false, with no error from `tc_task_error`. */ bool tc_task_has_tag(struct TCTask *task, struct TCString *tag); @@ -417,16 +444,16 @@ 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. */ -void tc_uuid_to_buf(struct TCUuid uuid, char *buf); +void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); /** * 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. */ -struct TCString *tc_uuid_to_str(struct TCUuid uuid); +struct TCString *tc_uuid_to_str(struct TCUuid tcuuid); /** - * Parse the given string as a UUID. The string must not be NULL. Returns false on failure. + * Parse the given string as a UUID. Returns false on failure. */ bool tc_uuid_from_str(struct TCString *s, struct TCUuid *uuid_out); From f4c6e04d444ecfee0d754ac220a25a74d33d9a97 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Feb 2022 05:04:44 +0000 Subject: [PATCH 472/548] TCTags as PassByValue --- integration-tests/src/bindings_tests/task.c | 8 +- lib/src/arrays.rs | 122 ++++++++--------- lib/src/replica.rs | 6 +- lib/src/task.rs | 11 +- lib/src/traits.rs | 143 ++++++++++++++++++++ lib/src/uuid.rs | 8 +- lib/taskchampion.h | 20 +-- 7 files changed, 227 insertions(+), 91 deletions(-) create mode 100644 lib/src/traits.rs diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 2ef89204c..c011509ca 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -315,11 +315,11 @@ static void test_task_get_tags(void) { TCTags tags = tc_task_get_tags(task); int found_pending = false, found_next = false; - for (size_t i = 0; i < tags.num_tags; i++) { - if (strcmp("PENDING", tc_string_content(tags.tags[i])) == 0) { + for (size_t i = 0; i < tags.len; i++) { + if (strcmp("PENDING", tc_string_content(tags.items[i])) == 0) { found_pending = true; } - if (strcmp("next", tc_string_content(tags.tags[i])) == 0) { + if (strcmp("next", tc_string_content(tags.items[i])) == 0) { found_next = true; } } @@ -327,7 +327,7 @@ static void test_task_get_tags(void) { TEST_ASSERT_TRUE(found_next); tc_tags_free(&tags); - TEST_ASSERT_NULL(tags.tags); + TEST_ASSERT_NULL(tags.items); tc_task_free(task); tc_replica_free(rep); diff --git a/lib/src/arrays.rs b/lib/src/arrays.rs index 3c266cb3c..a9d5dde49 100644 --- a/lib/src/arrays.rs +++ b/lib/src/arrays.rs @@ -2,8 +2,6 @@ use crate::string::TCString; use crate::traits::*; use std::ptr::NonNull; -// TODO: generalize to TCStrings? - /// TCTags represents a list of tags associated with a task. /// /// The content of this struct must be treated as read-only. @@ -12,86 +10,74 @@ use std::ptr::NonNull; /// will remain valid even if the task is freed. #[repr(C)] pub struct TCTags { - // TODO: can we use NonNull here? - /// strings representing each tag. these remain owned by the - /// TCTags instance and will be freed by tc_tags_free. - tags: *const NonNull>, - /// number of tags in tags - num_tags: libc::size_t, - /// total size of tags (internal use only) + // WARNING: this struct must match CPointerArray exactly, in size and order + // of fields. Names can differ, as can the pointer type. + /// number of tags in items + len: libc::size_t, + + /// total size of items (internal use only) _capacity: libc::size_t, + + /// strings representing each tag. these remain owned by the TCTags instance and will be freed + /// by tc_tags_free. + items: *const NonNull>, +} + +impl PassByValue for Vec>> { + type CType = TCTags; + + unsafe fn from_ctype(arg: TCTags) -> Self { + // SAFETY: + // - C treats TCTags as read-only, so items, len, and _capacity all came + // from a Vec originally. + unsafe { Vec::from_raw_parts(arg.items as *mut _, arg.len, arg._capacity) } + } + + fn as_ctype(self) -> TCTags { + // emulate Vec::into_raw_parts(): + // - disable dropping the Vec with ManuallyDrop + // - extract ptr, len, and capacity using those methods + let mut vec = std::mem::ManuallyDrop::new(self); + TCTags { + len: vec.len(), + _capacity: vec.capacity(), + items: vec.as_mut_ptr(), + } + } } impl Default for TCTags { fn default() -> Self { Self { - tags: std::ptr::null_mut(), - num_tags: 0, + len: 0, _capacity: 0, + items: std::ptr::null(), } } } -impl TCTags { - /// Create a Vec of TCStrings into a TCTags instance. - pub(crate) fn new(tags: Vec>>) -> Self { - // emulate Vec::into_raw_parts(): - // - disable dropping the Vec with ManuallyDrop - // - extract ptr, len, and capacity using those methods - let mut tags = std::mem::ManuallyDrop::new(tags); - Self { - tags: tags.as_mut_ptr(), - num_tags: tags.len(), - _capacity: tags.capacity(), - } - } - - /// Convert a TCTags to a Vec<_>. - /// - /// # Safety - /// - /// Tags must be _exactly_ as created by [`new`] - unsafe fn into_vec(self) -> Vec>> { - // SAFETY: - // - // * tags.tags needs to have been previously allocated via Vec<*mut TCString> - // * TCString needs to have the same size and alignment as what ptr was allocated with. - // * length needs to be less than or equal to capacity. - // * capacity needs to be the capacity that the pointer was allocated with. - // * vec elements are not NULL - // - // All of this is true for a value returned from `new`, which the caller promised - // not to change. - unsafe { Vec::from_raw_parts(self.tags as *mut _, self.num_tags, self._capacity) } - } -} - -/// Free a TCTags instance. The given pointer must not be NULL. The instance must not be used -/// after this call. +/// Free a TCTags instance. The instance, and all TCStrings it contains, must not be used after +/// this call. #[no_mangle] pub extern "C" fn tc_tags_free<'a>(tctags: *mut TCTags) { debug_assert!(!tctags.is_null()); // SAFETY: - // - tctags is not NULL - // - tctags is valid (caller promises it has not been changed) - // - caller will not use the TCTags after this (promised by caller) - let tctags: &'a mut TCTags = unsafe { &mut *tctags }; + // - *tctags is a valid TCTags (caller promises to treat it as read-only) + let tags = unsafe { Vec::take_from_arg(tctags, TCTags::default()) }; - debug_assert!(!tctags.tags.is_null()); - - // replace the caller's TCTags with one containing a NULL pointer - let tctags: TCTags = std::mem::take(tctags); - - // convert to a regular Vec - // SAFETY: - // - tctags is exactly as returned from TCTags::new (promised by caller) - let mut vec: Vec<_> = unsafe { tctags.into_vec() }; - - // drop each contained string - for tcstring in vec.drain(..) { - // SAFETY: see TCString docstring - drop(unsafe { TCString::take_from_arg(tcstring.as_ptr()) }); - } - - drop(vec); + // tags is a Vec>, so we convert it to a Vec that + // will properly drop those strings when dropped. + let tags: Vec = tags + .iter() + .map(|p| { + // SAFETY: + // *p is a pointer delivered to us from a Vec>, so + // - *p is not NULL + // - *p was generated by Rust + // - *p was not modified (promised by caller) + // - the caller will not use this pointer again (promised by caller) + unsafe { TCString::take_from_arg(p.as_ptr()) } + }) + .collect(); + drop(tags); } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 7512d1f91..131a8eb91 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -142,7 +142,8 @@ pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *m wrap( rep, |rep| { - let uuid = Uuid::from_arg(tcuuid); + // SAFETY: see TCUuid docstring + let uuid = unsafe { Uuid::from_arg(tcuuid) }; if let Some(task) = rep.get_task(uuid)? { Ok(TCTask::from(task).return_val()) } else { @@ -185,7 +186,8 @@ pub extern "C" fn tc_replica_import_task_with_uuid( wrap( rep, |rep| { - let uuid = Uuid::from_arg(tcuuid); + // SAFETY: see TCUuid docstring + let uuid = unsafe { Uuid::from_arg(tcuuid) }; let task = rep.import_task_with_uuid(uuid)?; Ok(TCTask::from(task).return_val()) }, diff --git a/lib/src/task.rs b/lib/src/task.rs index 87dcdd773..75bf12888 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -324,13 +324,14 @@ pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCTags { let tcstrings: Vec>> = task .get_tags() .map(|t| { - // SAFETY: see TCString docstring - let t_ptr = unsafe { TCString::from(t.as_ref()).return_val() }; - // SAFETY: t_ptr was just created and is not NULL - unsafe { NonNull::new_unchecked(t_ptr) } + NonNull::new( + // SAFETY: see TCString docstring + unsafe { TCString::from(t.as_ref()).return_val() }, + ) + .expect("TCString::return_val() returned NULL") }) .collect(); - TCTags::new(tcstrings) + tcstrings.return_val() }) } diff --git a/lib/src/traits.rs b/lib/src/traits.rs new file mode 100644 index 000000000..8011a46e4 --- /dev/null +++ b/lib/src/traits.rs @@ -0,0 +1,143 @@ +/// Support for values passed to Rust by value. These are represented as full structs in C. Such +/// values are implicitly copyable, via C's struct assignment. +/// +/// The Rust and C types may differ, with from_ctype and as_ctype converting between them. +pub(crate) trait PassByValue: Sized { + type CType; + + /// Convert a C value to a Rust value. + /// + /// # Safety + /// + /// `arg` must be a valid CType. + unsafe fn from_ctype(arg: Self::CType) -> Self; + + /// Convert a Rust value to a C value. + fn as_ctype(self) -> Self::CType; + + /// Take a value from C as an argument. + /// + /// # Safety + /// + /// `arg` must be a valid CType. This is typically ensured either by requiring that C + /// code not modify it, or by defining the valid values in C comments. + unsafe fn from_arg(arg: Self::CType) -> Self { + // SAFETY: + // - arg is a valid CType (promised by caller) + unsafe { Self::from_ctype(arg) } + } + + /// Take a value from C as a pointer argument, replacing it with the given value. This is used + /// to invalidate the C value as an additional assurance against subsequent use of the value. + /// + /// # Safety + /// + /// `*arg` must be a valid CType, as with [`from_arg`]. + unsafe fn take_from_arg(arg: *mut Self::CType, mut replacement: Self::CType) -> Self { + // SAFETY: + // - arg is valid (promised by caller) + // - replacement is valid (guaranteed by Rust) + unsafe { std::ptr::swap(arg, &mut replacement) }; + // SAFETY: + // - replacement (formerly *arg) is a valid CType (promised by caller) + unsafe { PassByValue::from_arg(replacement) } + } + + /// Return a value to C + fn return_val(self) -> Self::CType { + self.as_ctype() + } + + /// Return a value to C, via an "output parameter" + /// + /// # Safety + /// + /// `arg_out` must not be NULL and must be properly aligned and pointing to valid memory + /// of the size of CType. + unsafe fn to_arg_out(self, arg_out: *mut Self::CType) { + debug_assert!(!arg_out.is_null()); + // SAFETY: + // - arg_out is not NULL (promised by caller, asserted) + // - arg_out is properly aligned and points to valid memory (promised by caller) + unsafe { *arg_out = self.as_ctype() }; + } +} + +/// Support for values passed to Rust by pointer. These are represented as opaque structs in C, +/// and always handled as pointers. +/// +/// # Safety +/// +/// The functions provided by this trait are used directly in C interface functions, and make the +/// following expectations of the C code: +/// +/// - When passing a value to Rust (via the `…arg…` functions), +/// - the pointer must not be NULL; +/// - the pointer must be one previously returned from Rust; and +/// - the memory addressed by the pointer must never be modified by C code. +/// - For `from_arg_ref`, the value must not be modified during the call to the Rust function +/// - For `from_arg_ref_mut`, the value must not be accessed (read or write) during the call +/// (these last two points are trivially ensured by all TC… types being non-threadsafe) +/// - For `take_from_arg`, the pointer becomes invalid and must not be used in _any way_ after it +/// is passed to the Rust function. +/// - For `return_val` and `to_arg_out`, it is the C caller's responsibility to later free the value. +/// - For `to_arg_out`, `arg_out` must not be NULL and must be properly aligned and pointing to +/// valid memory. +/// +/// These requirements should be expressed in the C documentation for the type implementing this +/// trait. +pub(crate) trait PassByPointer: Sized { + /// Take a value from C as an argument. + /// + /// # Safety + /// + /// See trait documentation. + unsafe fn take_from_arg(arg: *mut Self) -> Self { + debug_assert!(!arg.is_null()); + // SAFETY: see trait documentation + unsafe { *(Box::from_raw(arg)) } + } + + /// Borrow a value from C as an argument. + /// + /// # Safety + /// + /// See trait documentation. + unsafe fn from_arg_ref<'a>(arg: *const Self) -> &'a Self { + debug_assert!(!arg.is_null()); + // SAFETY: see trait documentation + unsafe { &*arg } + } + + /// Mutably borrow a value from C as an argument. + /// + /// # Safety + /// + /// See trait documentation. + unsafe fn from_arg_ref_mut<'a>(arg: *mut Self) -> &'a mut Self { + debug_assert!(!arg.is_null()); + // SAFETY: see trait documentation + unsafe { &mut *arg } + } + + /// Return a value to C, transferring ownership + /// + /// # Safety + /// + /// See trait documentation. + unsafe fn return_val(self) -> *mut Self { + Box::into_raw(Box::new(self)) + } + + /// Return a value to C, transferring ownership, via an "output parameter". + /// + /// # Safety + /// + /// See trait documentation. + unsafe fn to_arg_out(self, arg_out: *mut *mut Self) { + // SAFETY: see trait documentation + unsafe { + *arg_out = self.return_val(); + } + } +} diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 8f57c7d1a..dfb084887 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -54,7 +54,9 @@ pub extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { let buf: &'a mut [u8] = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, ::uuid::adapter::Hyphenated::LENGTH) }; - let uuid: Uuid = Uuid::from_arg(tcuuid); + // SAFETY: + // - tcuuid is a valid TCUuid (all byte patterns are valid) + let uuid: Uuid = unsafe { Uuid::from_arg(tcuuid) }; uuid.to_hyphenated().encode_lower(buf); } @@ -62,7 +64,9 @@ pub extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { /// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. #[no_mangle] pub extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { - let uuid: Uuid = Uuid::from_arg(tcuuid); + // SAFETY: + // - tcuuid is a valid TCUuid (all byte patterns are valid) + let uuid: Uuid = unsafe { Uuid::from_arg(tcuuid) }; let s = uuid.to_string(); // SAFETY: see TCString docstring unsafe { TCString::from(s).return_val() } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index ccb8ff1db..b60658d58 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -127,18 +127,18 @@ typedef struct TCTask TCTask; */ typedef struct TCTags { /** - * strings representing each tag. these remain owned by the - * TCTags instance and will be freed by tc_tags_free. + * number of tags in items */ - struct TCString *const *tags; + size_t len; /** - * number of tags in tags - */ - size_t num_tags; - /** - * total size of tags (internal use only) + * total size of items (internal use only) */ size_t _capacity; + /** + * strings representing each tag. these remain owned by the TCTags instance and will be freed + * by tc_tags_free. + */ + struct TCString *const *items; } TCTags; /** @@ -155,8 +155,8 @@ extern "C" { #endif // __cplusplus /** - * Free a TCTags instance. The given pointer must not be NULL. The instance must not be used - * after this call. + * Free a TCTags instance. The instance, and all TCStrings it contains, must not be used after + * this call. */ void tc_tags_free(struct TCTags *tctags); From dadc9473d3b1d419a5aa531b775b46b834af5fd3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Feb 2022 16:21:42 +0000 Subject: [PATCH 473/548] unit tests for TCString --- Cargo.lock | 1 + lib/Cargo.toml | 3 + lib/src/string.rs | 181 ++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 155 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad2920754..ca9160c27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3038,6 +3038,7 @@ dependencies = [ "cbindgen", "chrono", "libc", + "pretty_assertions", "taskchampion", "uuid", ] diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 66b8c6051..86a2ec735 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -15,5 +15,8 @@ taskchampion = { path = "../taskchampion" } uuid = { version = "^0.8.2", features = ["v4"] } anyhow = "1.0" +[dev-dependencies] +pretty_assertions = "1" + [build-dependencies] cbindgen = "0.20.0" diff --git a/lib/src/string.rs b/lib/src/string.rs index d3f952189..08eed500c 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -34,6 +34,7 @@ use std::str::Utf8Error; /// must not use or free TCStrings after passing them to such API functions. /// /// TCStrings are not threadsafe. +#[derive(PartialEq, Debug)] pub enum TCString<'a> { CString(CString), CStr(&'a CStr), @@ -78,6 +79,32 @@ impl<'a> TCString<'a> { } } + /// Convert the TCString, in place, into one of the C variants. If this is not + /// possible, such as if the string contains an embedded NUL, then the string + /// remains unchanged. + fn to_c_string(&mut self) { + if matches!(self, TCString::String(_)) { + // we must take ownership of the String in order to try converting it, + // leaving the underlying TCString as its default (None) + if let TCString::String(string) = std::mem::take(self) { + match CString::new(string) { + Ok(cstring) => *self = TCString::CString(cstring), + Err(nul_err) => { + // recover the underlying String from the NulError and restore + // the TCString + let original_bytes = nul_err.into_vec(); + // SAFETY: original_bytes came from a String moments ago, so still valid utf8 + let string = unsafe { String::from_utf8_unchecked(original_bytes) }; + *self = TCString::String(string); + } + } + } else { + // the `matches!` above verified self was a TCString::String + unreachable!() + } + } + } + pub(crate) fn to_path_buf(&self) -> PathBuf { // TODO: this is UNIX-specific. let path: &OsStr = OsStr::from_bytes(self.as_bytes()); @@ -158,7 +185,10 @@ pub extern "C" fn tc_string_clone_with_len( // does not outlive this function call) // - the length of the buffer is less than isize::MAX (promised by caller) let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) }; + + // allocate and copy into Rust-controlled memory let vec = slice.to_vec(); + // try converting to a string, which is the only variant that can contain embedded NULs. If // the bytes are not valid utf-8, store that information for reporting later. let tcstring = match String::from_utf8(vec) { @@ -168,6 +198,7 @@ pub extern "C" fn tc_string_clone_with_len( TCString::InvalidUtf8(e, vec) } }; + // SAFETY: see docstring unsafe { tcstring.return_val() } } @@ -190,32 +221,11 @@ pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_c // if we have a String, we need to consume it and turn it into // a CString. - if matches!(tcstring, TCString::String(_)) { - // TODO: put this in a method - if let TCString::String(string) = std::mem::take(tcstring) { - match CString::new(string) { - Ok(cstring) => { - *tcstring = TCString::CString(cstring); - } - Err(nul_err) => { - // recover the underlying String from the NulError - let original_bytes = nul_err.into_vec(); - // SAFETY: original_bytes came from a String moments ago, so still valid utf8 - let string = unsafe { String::from_utf8_unchecked(original_bytes) }; - *tcstring = TCString::String(string); - - // and return NULL as advertized - return std::ptr::null(); - } - } - } else { - unreachable!() - } - } + tcstring.to_c_string(); match tcstring { TCString::CString(cstring) => cstring.as_ptr(), - TCString::String(_) => unreachable!(), // just converted to CString + TCString::String(_) => std::ptr::null(), // to_c_string failed TCString::CStr(cstr) => cstr.as_ptr(), TCString::InvalidUtf8(_, _) => std::ptr::null(), TCString::None => unreachable!(), @@ -240,13 +250,8 @@ pub extern "C" fn tc_string_content_with_len( let tcstring = unsafe { TCString::from_arg_ref(tcstring) }; debug_assert!(!len_out.is_null()); - let bytes = match tcstring { - TCString::CString(cstring) => cstring.as_bytes(), - TCString::String(string) => string.as_bytes(), - TCString::CStr(cstr) => cstr.to_bytes(), - TCString::InvalidUtf8(_, ref v) => v.as_ref(), - TCString::None => unreachable!(), - }; + let bytes = tcstring.as_bytes(); + // SAFETY: // - len_out is not NULL (checked by assertion, promised by caller) // - len_out points to valid memory (promised by caller) @@ -264,3 +269,119 @@ pub extern "C" fn tc_string_free(tcstring: *mut TCString) { // - caller is exclusive owner of tcstring (promised by caller) drop(unsafe { TCString::take_from_arg(tcstring) }); } + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28"; + + fn make_cstring() -> TCString<'static> { + TCString::CString(CString::new("a string").unwrap()) + } + + fn make_cstr() -> TCString<'static> { + let cstr = CStr::from_bytes_with_nul(b"a string\0").unwrap(); + TCString::CStr(&cstr) + } + + fn make_string() -> TCString<'static> { + TCString::String("a string".into()) + } + + fn make_string_with_nul() -> TCString<'static> { + TCString::String("a \0 nul!".into()) + } + + fn make_invalid() -> TCString<'static> { + let e = String::from_utf8(INVALID_UTF8.to_vec()).unwrap_err(); + TCString::InvalidUtf8(e.utf8_error(), e.into_bytes()) + } + + #[test] + fn cstring_as_str() { + assert_eq!(make_cstring().as_str().unwrap(), "a string"); + } + + #[test] + fn cstr_as_str() { + assert_eq!(make_cstr().as_str().unwrap(), "a string"); + } + + #[test] + fn string_as_str() { + assert_eq!(make_string().as_str().unwrap(), "a string"); + } + + #[test] + fn string_with_nul_as_str() { + assert_eq!(make_string_with_nul().as_str().unwrap(), "a \0 nul!"); + } + + #[test] + fn invalid_as_str() { + let as_str_err = make_invalid().as_str().unwrap_err(); + assert_eq!(as_str_err.valid_up_to(), 3); // "abc" is valid + } + + #[test] + fn cstring_as_bytes() { + assert_eq!(make_cstring().as_bytes(), b"a string"); + } + + #[test] + fn cstr_as_bytes() { + assert_eq!(make_cstr().as_bytes(), b"a string"); + } + + #[test] + fn string_as_bytes() { + assert_eq!(make_string().as_bytes(), b"a string"); + } + + #[test] + fn string_with_nul_as_bytes() { + assert_eq!(make_string_with_nul().as_bytes(), b"a \0 nul!"); + } + + #[test] + fn invalid_as_bytes() { + assert_eq!(make_invalid().as_bytes(), INVALID_UTF8); + } + + #[test] + fn cstring_to_c_string() { + let mut tcstring = make_cstring(); + tcstring.to_c_string(); + assert_eq!(tcstring, make_cstring()); // unchanged + } + + #[test] + fn cstr_to_c_string() { + let mut tcstring = make_cstr(); + tcstring.to_c_string(); + assert_eq!(tcstring, make_cstr()); // unchanged + } + + #[test] + fn string_to_c_string() { + let mut tcstring = make_string(); + tcstring.to_c_string(); + assert_eq!(tcstring, make_cstring()); // converted to CString, same content + } + + #[test] + fn string_with_nul_to_c_string() { + let mut tcstring = make_string_with_nul(); + tcstring.to_c_string(); + assert_eq!(tcstring, make_string_with_nul()); // unchanged + } + + #[test] + fn invalid_to_c_string() { + let mut tcstring = make_invalid(); + tcstring.to_c_string(); + assert_eq!(tcstring, make_invalid()); // unchanged + } +} From b0f785071111322c1719ac5d2391040216e5b506 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Feb 2022 16:26:09 +0000 Subject: [PATCH 474/548] trivially implement PassByValue for usize --- lib/src/atomic.rs | 15 +++++++++++++++ lib/src/lib.rs | 1 + lib/src/string.rs | 5 ++--- 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 lib/src/atomic.rs diff --git a/lib/src/atomic.rs b/lib/src/atomic.rs new file mode 100644 index 000000000..341f7b339 --- /dev/null +++ b/lib/src/atomic.rs @@ -0,0 +1,15 @@ +//! Trait implementations for a few atomic types + +use crate::traits::*; + +impl PassByValue for usize { + type CType = usize; + + unsafe fn from_ctype(arg: usize) -> usize { + arg + } + + fn as_ctype(self) -> usize { + self + } +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 18cba4ac9..cd3839f8c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -4,6 +4,7 @@ mod traits; mod util; pub mod arrays; +pub mod atomic; pub mod replica; pub mod result; pub mod status; diff --git a/lib/src/string.rs b/lib/src/string.rs index 08eed500c..3e95e8f6e 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -248,15 +248,14 @@ pub extern "C" fn tc_string_content_with_len( // - lifetime of tcstring outlives the lifetime of this function // - lifetime of tcstring outlives the lifetime of the returned pointer (promised by caller) let tcstring = unsafe { TCString::from_arg_ref(tcstring) }; - debug_assert!(!len_out.is_null()); let bytes = tcstring.as_bytes(); // SAFETY: - // - len_out is not NULL (checked by assertion, promised by caller) + // - len_out is not NULL (promised by caller) // - len_out points to valid memory (promised by caller) // - len_out is properly aligned (C convention) - unsafe { *len_out = bytes.len() }; + unsafe { bytes.len().to_arg_out(len_out) }; bytes.as_ptr() as *const libc::c_char } From 3d248b55fdbd0349f3861294fd339c606af1d676 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Feb 2022 16:38:31 +0000 Subject: [PATCH 475/548] factor out some utilities for pointer arrays --- lib/src/arrays.rs | 28 ++++++---------------------- lib/src/util.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/lib/src/arrays.rs b/lib/src/arrays.rs index a9d5dde49..301d273d3 100644 --- a/lib/src/arrays.rs +++ b/lib/src/arrays.rs @@ -1,5 +1,6 @@ use crate::string::TCString; use crate::traits::*; +use crate::util::{drop_pointer_array, vec_into_raw_parts}; use std::ptr::NonNull; /// TCTags represents a list of tags associated with a task. @@ -10,8 +11,6 @@ use std::ptr::NonNull; /// will remain valid even if the task is freed. #[repr(C)] pub struct TCTags { - // WARNING: this struct must match CPointerArray exactly, in size and order - // of fields. Names can differ, as can the pointer type. /// number of tags in items len: libc::size_t, @@ -37,11 +36,11 @@ impl PassByValue for Vec>> { // emulate Vec::into_raw_parts(): // - disable dropping the Vec with ManuallyDrop // - extract ptr, len, and capacity using those methods - let mut vec = std::mem::ManuallyDrop::new(self); + let (items, len, _capacity) = vec_into_raw_parts(self); TCTags { - len: vec.len(), - _capacity: vec.capacity(), - items: vec.as_mut_ptr(), + len, + _capacity, + items, } } } @@ -64,20 +63,5 @@ pub extern "C" fn tc_tags_free<'a>(tctags: *mut TCTags) { // SAFETY: // - *tctags is a valid TCTags (caller promises to treat it as read-only) let tags = unsafe { Vec::take_from_arg(tctags, TCTags::default()) }; - - // tags is a Vec>, so we convert it to a Vec that - // will properly drop those strings when dropped. - let tags: Vec = tags - .iter() - .map(|p| { - // SAFETY: - // *p is a pointer delivered to us from a Vec>, so - // - *p is not NULL - // - *p was generated by Rust - // - *p was not modified (promised by caller) - // - the caller will not use this pointer again (promised by caller) - unsafe { TCString::take_from_arg(p.as_ptr()) } - }) - .collect(); - drop(tags); + drop_pointer_array(tags); } diff --git a/lib/src/util.rs b/lib/src/util.rs index 0709d2640..00676424a 100644 --- a/lib/src/util.rs +++ b/lib/src/util.rs @@ -1,5 +1,34 @@ use crate::string::TCString; +use crate::traits::*; +use std::ptr::NonNull; pub(crate) fn err_to_tcstring(e: impl std::string::ToString) -> TCString<'static> { TCString::from(e.to_string()) } + +/// A version of Vec::into_raw_parts, which is still unstable. Returns ptr, len, cap. +pub(crate) fn vec_into_raw_parts(vec: Vec) -> (*mut T, usize, usize) { + // emulate Vec::into_raw_parts(): + // - disable dropping the Vec with ManuallyDrop + // - extract ptr, len, and capacity using those methods + let mut vec = std::mem::ManuallyDrop::new(vec); + return (vec.as_mut_ptr(), vec.len(), vec.capacity()); +} + +/// Drop an array of PassByPointer values +pub(crate) fn drop_pointer_array(mut array: Vec>) +where + T: 'static + PassByPointer, +{ + // first, drop each of the elements in turn + for p in array.drain(..) { + // SAFETY: + // - p is not NULL (NonNull) + // - p was generated by Rust (true for all arrays) + // - p was not modified (all arrays are immutable from C) + // - caller will not use this pointer again (promised by caller; p has been drain'd from + // the vector) + drop(unsafe { PassByPointer::take_from_arg(p.as_ptr()) }); + } + drop(array); +} From 831eb0bb152720e99fdd16ce7e3fccda36a386a4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Feb 2022 16:40:17 +0000 Subject: [PATCH 476/548] TCTags -> TCStrings to be more general --- integration-tests/src/bindings_tests/task.c | 4 +- lib/src/lib.rs | 2 +- lib/src/{arrays.rs => strings.rs} | 30 +++++++------- lib/src/task.rs | 4 +- lib/taskchampion.h | 44 ++++++++++----------- 5 files changed, 42 insertions(+), 42 deletions(-) rename lib/src/{arrays.rs => strings.rs} (61%) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index c011509ca..2ab5e3602 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -312,7 +312,7 @@ static void test_task_get_tags(void) { TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); - TCTags tags = tc_task_get_tags(task); + TCStrings tags = tc_task_get_tags(task); int found_pending = false, found_next = false; for (size_t i = 0; i < tags.len; i++) { @@ -326,7 +326,7 @@ static void test_task_get_tags(void) { TEST_ASSERT_TRUE(found_pending); TEST_ASSERT_TRUE(found_next); - tc_tags_free(&tags); + tc_strings_free(&tags); TEST_ASSERT_NULL(tags.items); tc_task_free(task); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index cd3839f8c..5b9160970 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -3,11 +3,11 @@ mod traits; mod util; -pub mod arrays; pub mod atomic; pub mod replica; pub mod result; pub mod status; pub mod string; +pub mod strings; pub mod task; pub mod uuid; diff --git a/lib/src/arrays.rs b/lib/src/strings.rs similarity index 61% rename from lib/src/arrays.rs rename to lib/src/strings.rs index 301d273d3..341680de3 100644 --- a/lib/src/arrays.rs +++ b/lib/src/strings.rs @@ -3,41 +3,41 @@ use crate::traits::*; use crate::util::{drop_pointer_array, vec_into_raw_parts}; use std::ptr::NonNull; -/// TCTags represents a list of tags associated with a task. +/// TCStrings represents a list of tags associated with a task. /// /// The content of this struct must be treated as read-only. /// -/// The lifetime of a TCTags instance is independent of the task, and it +/// The lifetime of a TCStrings instance is independent of the task, and it /// will remain valid even if the task is freed. #[repr(C)] -pub struct TCTags { +pub struct TCStrings { /// number of tags in items len: libc::size_t, /// total size of items (internal use only) _capacity: libc::size_t, - /// strings representing each tag. these remain owned by the TCTags instance and will be freed - /// by tc_tags_free. + /// strings representing each tag. these remain owned by the TCStrings instance and will be freed + /// by tc_strings_free. items: *const NonNull>, } impl PassByValue for Vec>> { - type CType = TCTags; + type CType = TCStrings; - unsafe fn from_ctype(arg: TCTags) -> Self { + unsafe fn from_ctype(arg: TCStrings) -> Self { // SAFETY: - // - C treats TCTags as read-only, so items, len, and _capacity all came + // - C treats TCStrings as read-only, so items, len, and _capacity all came // from a Vec originally. unsafe { Vec::from_raw_parts(arg.items as *mut _, arg.len, arg._capacity) } } - fn as_ctype(self) -> TCTags { + fn as_ctype(self) -> TCStrings { // emulate Vec::into_raw_parts(): // - disable dropping the Vec with ManuallyDrop // - extract ptr, len, and capacity using those methods let (items, len, _capacity) = vec_into_raw_parts(self); - TCTags { + TCStrings { len, _capacity, items, @@ -45,7 +45,7 @@ impl PassByValue for Vec>> { } } -impl Default for TCTags { +impl Default for TCStrings { fn default() -> Self { Self { len: 0, @@ -55,13 +55,13 @@ impl Default for TCTags { } } -/// Free a TCTags instance. The instance, and all TCStrings it contains, must not be used after +/// Free a TCStrings instance. The instance, and all TCStrings it contains, must not be used after /// this call. #[no_mangle] -pub extern "C" fn tc_tags_free<'a>(tctags: *mut TCTags) { +pub extern "C" fn tc_strings_free<'a>(tctags: *mut TCStrings) { debug_assert!(!tctags.is_null()); // SAFETY: - // - *tctags is a valid TCTags (caller promises to treat it as read-only) - let tags = unsafe { Vec::take_from_arg(tctags, TCTags::default()) }; + // - *tctags is a valid TCStrings (caller promises to treat it as read-only) + let tags = unsafe { Vec::take_from_arg(tctags, TCStrings::default()) }; drop_pointer_array(tags); } diff --git a/lib/src/task.rs b/lib/src/task.rs index 75bf12888..65574b583 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,7 +1,7 @@ use crate::traits::*; use crate::util::err_to_tcstring; use crate::{ - arrays::TCTags, replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, + replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, strings::TCStrings, uuid::TCUuid, }; use chrono::{DateTime, TimeZone, Utc}; @@ -319,7 +319,7 @@ pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> /// Get the tags for the task. The task must not be NULL. #[no_mangle] -pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCTags { +pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { wrap(task, |task| { let tcstrings: Vec>> = task .get_tags() diff --git a/lib/taskchampion.h b/lib/taskchampion.h index b60658d58..84c471b26 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -118,14 +118,23 @@ typedef struct TCString TCString; typedef struct TCTask TCTask; /** - * TCTags represents a list of tags associated with a task. + * 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; + +/** + * TCStrings represents a list of tags associated with a task. * * The content of this struct must be treated as read-only. * - * The lifetime of a TCTags instance is independent of the task, and it + * The lifetime of a TCStrings instance is independent of the task, and it * will remain valid even if the task is freed. */ -typedef struct TCTags { +typedef struct TCStrings { /** * number of tags in items */ @@ -135,31 +144,16 @@ typedef struct TCTags { */ size_t _capacity; /** - * strings representing each tag. these remain owned by the TCTags instance and will be freed - * by tc_tags_free. + * strings representing each tag. these remain owned by the TCStrings instance and will be freed + * by tc_strings_free. */ struct TCString *const *items; -} TCTags; - -/** - * 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; +} TCStrings; #ifdef __cplusplus extern "C" { #endif // __cplusplus -/** - * Free a TCTags instance. The instance, and all TCStrings it contains, must not be used after - * this call. - */ -void tc_tags_free(struct TCTags *tctags); - /** * Create a new TCReplica with an in-memory database. The contents of the database will be * lost when it is freed. @@ -278,6 +272,12 @@ const char *tc_string_content_with_len(struct TCString *tcstring, size_t *len_ou */ void tc_string_free(struct TCString *tcstring); +/** + * Free a TCStrings instance. The instance, and all TCStrings it contains, must not be used after + * this call. + */ +void tc_strings_free(struct TCStrings *tctags); + /** * Convert an immutable task into a mutable task. * @@ -357,7 +357,7 @@ bool tc_task_has_tag(struct TCTask *task, struct TCString *tag); /** * Get the tags for the task. The task must not be NULL. */ -struct TCTags tc_task_get_tags(struct TCTask *task); +struct TCStrings tc_task_get_tags(struct TCTask *task); /** * Set a mutable task's status. From 1e585ba0d94dc825e7c08b9a33b95af45aa650bd Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Feb 2022 16:46:19 +0000 Subject: [PATCH 477/548] comment updates --- lib/src/strings.rs | 5 +---- lib/src/task.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/src/strings.rs b/lib/src/strings.rs index 341680de3..c9f30460d 100644 --- a/lib/src/strings.rs +++ b/lib/src/strings.rs @@ -3,12 +3,9 @@ use crate::traits::*; use crate::util::{drop_pointer_array, vec_into_raw_parts}; use std::ptr::NonNull; -/// TCStrings represents a list of tags associated with a task. +/// TCStrings represents a list of string. /// /// The content of this struct must be treated as read-only. -/// -/// The lifetime of a TCStrings instance is independent of the task, and it -/// will remain valid even if the task is freed. #[repr(C)] pub struct TCStrings { /// number of tags in items diff --git a/lib/src/task.rs b/lib/src/task.rs index 65574b583..fff4c490a 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -11,8 +11,6 @@ use std::ptr::NonNull; use std::str::FromStr; use taskchampion::{Tag, Task, TaskMut}; -// TODO: use NonNull more - /// A task, as publicly exposed by this library. /// /// A task begins in "immutable" mode. It must be converted to "mutable" mode @@ -301,9 +299,8 @@ pub extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { wrap(task, |task| task.is_active()) } -/// Check if a task has the given tag. If the tag is invalid, this function will simply return -/// false, with no error from `tc_task_error`. -// TODO: why no error?? +/// 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`. #[no_mangle] pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { // SAFETY: see TCString docstring @@ -317,7 +314,10 @@ pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> }) } -/// Get the tags for the task. The task must not be NULL. +/// Get the tags for the task. +/// +/// The caller must free the returned TCStrings instance. The TCStrings instance does not +/// reference the task and the two may be freed in any order. #[no_mangle] pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { wrap(task, |task| { From e11506ee6a70fc2f14d5c3a45739c88c9400cc8e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Feb 2022 23:05:33 +0000 Subject: [PATCH 478/548] always implement traits for C type --- lib/src/atomic.rs | 12 ++++++------ lib/src/replica.rs | 6 +++--- lib/src/string.rs | 2 +- lib/src/strings.rs | 28 ++++++++++++++-------------- lib/src/task.rs | 4 ++-- lib/src/traits.rs | 25 +++++++++++++------------ lib/src/uuid.rs | 22 +++++++++++----------- lib/taskchampion.h | 20 ++++++++++---------- 8 files changed, 60 insertions(+), 59 deletions(-) diff --git a/lib/src/atomic.rs b/lib/src/atomic.rs index 341f7b339..db8315922 100644 --- a/lib/src/atomic.rs +++ b/lib/src/atomic.rs @@ -3,13 +3,13 @@ use crate::traits::*; impl PassByValue for usize { - type CType = usize; + type RustType = usize; - unsafe fn from_ctype(arg: usize) -> usize { - arg - } - - fn as_ctype(self) -> usize { + unsafe fn from_ctype(self) -> usize { self } + + fn as_ctype(arg: usize) -> usize { + arg + } } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 131a8eb91..d393d95c2 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,7 +1,7 @@ use crate::traits::*; use crate::util::err_to_tcstring; use crate::{result::TCResult, status::TCStatus, string::TCString, task::TCTask, uuid::TCUuid}; -use taskchampion::{Replica, StorageConfig, Uuid}; +use taskchampion::{Replica, StorageConfig}; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. @@ -143,7 +143,7 @@ pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *m rep, |rep| { // SAFETY: see TCUuid docstring - let uuid = unsafe { Uuid::from_arg(tcuuid) }; + let uuid = unsafe { TCUuid::from_arg(tcuuid) }; if let Some(task) = rep.get_task(uuid)? { Ok(TCTask::from(task).return_val()) } else { @@ -187,7 +187,7 @@ pub extern "C" fn tc_replica_import_task_with_uuid( rep, |rep| { // SAFETY: see TCUuid docstring - let uuid = unsafe { Uuid::from_arg(tcuuid) }; + let uuid = unsafe { TCUuid::from_arg(tcuuid) }; let task = rep.import_task_with_uuid(uuid)?; Ok(TCTask::from(task).return_val()) }, diff --git a/lib/src/string.rs b/lib/src/string.rs index 3e95e8f6e..138ff31ea 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -255,7 +255,7 @@ pub extern "C" fn tc_string_content_with_len( // - len_out is not NULL (promised by caller) // - len_out points to valid memory (promised by caller) // - len_out is properly aligned (C convention) - unsafe { bytes.len().to_arg_out(len_out) }; + unsafe { usize::to_arg_out(bytes.len(), len_out) }; bytes.as_ptr() as *const libc::c_char } diff --git a/lib/src/strings.rs b/lib/src/strings.rs index c9f30460d..7d7e5bda3 100644 --- a/lib/src/strings.rs +++ b/lib/src/strings.rs @@ -3,37 +3,37 @@ use crate::traits::*; use crate::util::{drop_pointer_array, vec_into_raw_parts}; use std::ptr::NonNull; -/// TCStrings represents a list of string. +/// TCStrings represents a list of strings. /// /// The content of this struct must be treated as read-only. #[repr(C)] pub struct TCStrings { - /// number of tags in items + /// number of strings in items len: libc::size_t, /// total size of items (internal use only) _capacity: libc::size_t, - /// strings representing each tag. these remain owned by the TCStrings instance and will be freed + /// strings representing each string. these remain owned by the TCStrings instance and will be freed /// by tc_strings_free. items: *const NonNull>, } -impl PassByValue for Vec>> { - type CType = TCStrings; +impl PassByValue for TCStrings { + type RustType = Vec>>; - unsafe fn from_ctype(arg: TCStrings) -> Self { + unsafe fn from_ctype(self) -> Self::RustType { // SAFETY: // - C treats TCStrings as read-only, so items, len, and _capacity all came // from a Vec originally. - unsafe { Vec::from_raw_parts(arg.items as *mut _, arg.len, arg._capacity) } + unsafe { Vec::from_raw_parts(self.items as *mut _, self.len, self._capacity) } } - fn as_ctype(self) -> TCStrings { + fn as_ctype(arg: Self::RustType) -> Self { // emulate Vec::into_raw_parts(): // - disable dropping the Vec with ManuallyDrop // - extract ptr, len, and capacity using those methods - let (items, len, _capacity) = vec_into_raw_parts(self); + let (items, len, _capacity) = vec_into_raw_parts(arg); TCStrings { len, _capacity, @@ -55,10 +55,10 @@ impl Default for TCStrings { /// Free a TCStrings instance. The instance, and all TCStrings it contains, must not be used after /// this call. #[no_mangle] -pub extern "C" fn tc_strings_free<'a>(tctags: *mut TCStrings) { - debug_assert!(!tctags.is_null()); +pub extern "C" fn tc_strings_free<'a>(tcstrings: *mut TCStrings) { + debug_assert!(!tcstrings.is_null()); // SAFETY: - // - *tctags is a valid TCStrings (caller promises to treat it as read-only) - let tags = unsafe { Vec::take_from_arg(tctags, TCStrings::default()) }; - drop_pointer_array(tags); + // - *tcstrings is a valid TCStrings (caller promises to treat it as read-only) + let strings = unsafe { TCStrings::take_from_arg(tcstrings, TCStrings::default()) }; + drop_pointer_array(strings); } diff --git a/lib/src/task.rs b/lib/src/task.rs index fff4c490a..1a2bbaf6d 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -247,7 +247,7 @@ pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { /// Get a task's UUID. #[no_mangle] pub extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { - wrap(task, |task| task.get_uuid().return_val()) + wrap(task, |task| TCUuid::return_val(task.get_uuid())) } /// Get a task's status. @@ -331,7 +331,7 @@ pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { .expect("TCString::return_val() returned NULL") }) .collect(); - tcstrings.return_val() + TCStrings::return_val(tcstrings) }) } diff --git a/lib/src/traits.rs b/lib/src/traits.rs index 8011a46e4..df57d11c7 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -2,29 +2,30 @@ /// values are implicitly copyable, via C's struct assignment. /// /// The Rust and C types may differ, with from_ctype and as_ctype converting between them. +/// Implement this trait for the C type. pub(crate) trait PassByValue: Sized { - type CType; + type RustType; /// Convert a C value to a Rust value. /// /// # Safety /// - /// `arg` must be a valid CType. - unsafe fn from_ctype(arg: Self::CType) -> Self; + /// `self` must be a valid CType. + unsafe fn from_ctype(self) -> Self::RustType; /// Convert a Rust value to a C value. - fn as_ctype(self) -> Self::CType; + fn as_ctype(arg: Self::RustType) -> Self; /// Take a value from C as an argument. /// /// # Safety /// - /// `arg` must be a valid CType. This is typically ensured either by requiring that C + /// `self` must be a valid CType. This is typically ensured either by requiring that C /// code not modify it, or by defining the valid values in C comments. - unsafe fn from_arg(arg: Self::CType) -> Self { + unsafe fn from_arg(arg: Self) -> Self::RustType { // SAFETY: // - arg is a valid CType (promised by caller) - unsafe { Self::from_ctype(arg) } + unsafe { arg.from_ctype() } } /// Take a value from C as a pointer argument, replacing it with the given value. This is used @@ -33,7 +34,7 @@ pub(crate) trait PassByValue: Sized { /// # Safety /// /// `*arg` must be a valid CType, as with [`from_arg`]. - unsafe fn take_from_arg(arg: *mut Self::CType, mut replacement: Self::CType) -> Self { + unsafe fn take_from_arg(arg: *mut Self, mut replacement: Self) -> Self::RustType { // SAFETY: // - arg is valid (promised by caller) // - replacement is valid (guaranteed by Rust) @@ -44,8 +45,8 @@ pub(crate) trait PassByValue: Sized { } /// Return a value to C - fn return_val(self) -> Self::CType { - self.as_ctype() + fn return_val(arg: Self::RustType) -> Self { + Self::as_ctype(arg) } /// Return a value to C, via an "output parameter" @@ -54,12 +55,12 @@ pub(crate) trait PassByValue: Sized { /// /// `arg_out` must not be NULL and must be properly aligned and pointing to valid memory /// of the size of CType. - unsafe fn to_arg_out(self, arg_out: *mut Self::CType) { + unsafe fn to_arg_out(val: Self::RustType, arg_out: *mut Self) { debug_assert!(!arg_out.is_null()); // SAFETY: // - arg_out is not NULL (promised by caller, asserted) // - arg_out is properly aligned and points to valid memory (promised by caller) - unsafe { *arg_out = self.as_ctype() }; + unsafe { *arg_out = Self::as_ctype(val) }; } } diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index dfb084887..3c7751747 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -10,30 +10,30 @@ use taskchampion::Uuid; #[repr(C)] pub struct TCUuid([u8; 16]); -impl PassByValue for Uuid { - type CType = TCUuid; +impl PassByValue for TCUuid { + type RustType = Uuid; - unsafe fn from_ctype(arg: TCUuid) -> Self { + unsafe fn from_ctype(self) -> Self::RustType { // SAFETY: // - any 16-byte value is a valid Uuid - Uuid::from_bytes(arg.0) + Uuid::from_bytes(self.0) } - fn as_ctype(self) -> TCUuid { - TCUuid(*self.as_bytes()) + fn as_ctype(arg: Uuid) -> Self { + TCUuid(*arg.as_bytes()) } } /// Create a new, randomly-generated UUID. #[no_mangle] pub extern "C" fn tc_uuid_new_v4() -> TCUuid { - Uuid::new_v4().return_val() + TCUuid::return_val(Uuid::new_v4()) } /// Create a new UUID with the nil value. #[no_mangle] pub extern "C" fn tc_uuid_nil() -> TCUuid { - Uuid::nil().return_val() + TCUuid::return_val(Uuid::nil()) } // NOTE: this must be a simple constant so that cbindgen can evaluate it @@ -56,7 +56,7 @@ pub extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { }; // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { Uuid::from_arg(tcuuid) }; + let uuid: Uuid = unsafe { TCUuid::from_arg(tcuuid) }; uuid.to_hyphenated().encode_lower(buf); } @@ -66,7 +66,7 @@ pub extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { pub extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { Uuid::from_arg(tcuuid) }; + let uuid: Uuid = unsafe { TCUuid::from_arg(tcuuid) }; let s = uuid.to_string(); // SAFETY: see TCString docstring unsafe { TCString::from(s).return_val() } @@ -85,7 +85,7 @@ pub extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) // SAFETY: // - uuid_out is not NULL (promised by caller) // - alignment is not required - unsafe { u.to_arg_out(uuid_out) }; + unsafe { TCUuid::to_arg_out(u, uuid_out) }; return true; } } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 84c471b26..818fc166f 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -127,16 +127,13 @@ typedef struct TCUuid { } TCUuid; /** - * TCStrings represents a list of tags associated with a task. + * TCStrings represents a list of strings. * * The content of this struct must be treated as read-only. - * - * The lifetime of a TCStrings instance is independent of the task, and it - * will remain valid even if the task is freed. */ typedef struct TCStrings { /** - * number of tags in items + * number of strings in items */ size_t len; /** @@ -144,7 +141,7 @@ typedef struct TCStrings { */ size_t _capacity; /** - * strings representing each tag. these remain owned by the TCStrings instance and will be freed + * strings representing each string. these remain owned by the TCStrings instance and will be freed * by tc_strings_free. */ struct TCString *const *items; @@ -276,7 +273,7 @@ void tc_string_free(struct TCString *tcstring); * Free a TCStrings instance. The instance, and all TCStrings it contains, must not be used after * this call. */ -void tc_strings_free(struct TCStrings *tctags); +void tc_strings_free(struct TCStrings *tcstrings); /** * Convert an immutable task into a mutable task. @@ -349,13 +346,16 @@ bool tc_task_is_waiting(struct TCTask *task); bool tc_task_is_active(struct TCTask *task); /** - * Check if a task has the given tag. If the tag is invalid, this function will simply return - * false, with no error from `tc_task_error`. + * 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 tags for the task. The task must not be NULL. + * Get the tags for the task. + * + * The caller must free the returned TCStrings instance. The TCStrings instance does not + * reference the task and the two may be freed in any order. */ struct TCStrings tc_task_get_tags(struct TCTask *task); From a270b6c254eab6acff2dab2acd07227e9e8b39b6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 7 Feb 2022 00:15:09 +0000 Subject: [PATCH 479/548] Simplify implementation of arrays --- lib/src/strings.rs | 65 +++++++++++++++++++---------------- lib/src/task.rs | 4 +-- lib/src/traits.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++++ lib/src/util.rs | 22 +----------- lib/taskchampion.h | 7 ++-- 5 files changed, 130 insertions(+), 54 deletions(-) diff --git a/lib/src/strings.rs b/lib/src/strings.rs index 7d7e5bda3..55572ec25 100644 --- a/lib/src/strings.rs +++ b/lib/src/strings.rs @@ -1,6 +1,5 @@ use crate::string::TCString; use crate::traits::*; -use crate::util::{drop_pointer_array, vec_into_raw_parts}; use std::ptr::NonNull; /// TCStrings represents a list of strings. @@ -14,51 +13,59 @@ pub struct TCStrings { /// total size of items (internal use only) _capacity: libc::size_t, - /// strings representing each string. these remain owned by the TCStrings instance and will be freed - /// by tc_strings_free. + /// TCStrings representing each string. these remain owned by the TCStrings instance and will + /// be freed by tc_strings_free. This pointer is never NULL for a valid TCStrings, and the + /// *TCStrings at indexes 0..len-1 are not NULL. items: *const NonNull>, } -impl PassByValue for TCStrings { - type RustType = Vec>>; +impl PointerArray for TCStrings { + type Element = TCString<'static>; - unsafe fn from_ctype(self) -> Self::RustType { - // SAFETY: - // - C treats TCStrings as read-only, so items, len, and _capacity all came - // from a Vec originally. - unsafe { Vec::from_raw_parts(self.items as *mut _, self.len, self._capacity) } - } - - fn as_ctype(arg: Self::RustType) -> Self { - // emulate Vec::into_raw_parts(): - // - disable dropping the Vec with ManuallyDrop - // - extract ptr, len, and capacity using those methods - let (items, len, _capacity) = vec_into_raw_parts(arg); + unsafe fn from_raw_parts(items: *const NonNull, len: usize, cap: usize) -> Self { TCStrings { len, - _capacity, + _capacity: cap, items, } } -} -impl Default for TCStrings { - fn default() -> Self { - Self { - len: 0, - _capacity: 0, - items: std::ptr::null(), - } + fn into_raw_parts(self) -> (*const NonNull, usize, usize) { + (self.items, self.len, self._capacity) } } /// Free a TCStrings instance. The instance, and all TCStrings it contains, must not be used after /// this call. +/// +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStrings. #[no_mangle] -pub extern "C" fn tc_strings_free<'a>(tcstrings: *mut TCStrings) { +pub extern "C" fn tc_strings_free(tcstrings: *mut TCStrings) { debug_assert!(!tcstrings.is_null()); // SAFETY: // - *tcstrings is a valid TCStrings (caller promises to treat it as read-only) - let strings = unsafe { TCStrings::take_from_arg(tcstrings, TCStrings::default()) }; - drop_pointer_array(strings); + let strings = unsafe { TCStrings::take_from_arg(tcstrings, TCStrings::null_value()) }; + TCStrings::drop_pointer_vector(strings); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_array_has_non_null_pointer() { + let tcstrings = TCStrings::return_val(Vec::new()); + assert!(!tcstrings.items.is_null()); + assert_eq!(tcstrings.len, 0); + assert_eq!(tcstrings._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tcstrings = TCStrings::return_val(Vec::new()); + tc_strings_free(&mut tcstrings); + assert!(tcstrings.items.is_null()); + assert_eq!(tcstrings.len, 0); + assert_eq!(tcstrings._capacity, 0); + } } diff --git a/lib/src/task.rs b/lib/src/task.rs index 1a2bbaf6d..823fbbffe 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -321,7 +321,7 @@ pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> #[no_mangle] pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { wrap(task, |task| { - let tcstrings: Vec>> = task + let vec: Vec>> = task .get_tags() .map(|t| { NonNull::new( @@ -331,7 +331,7 @@ pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { .expect("TCString::return_val() returned NULL") }) .collect(); - TCStrings::return_val(tcstrings) + TCStrings::return_val(vec) }) } diff --git a/lib/src/traits.rs b/lib/src/traits.rs index df57d11c7..1e4a67dc6 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -1,3 +1,6 @@ +use crate::util::vec_into_raw_parts; +use std::ptr::NonNull; + /// Support for values passed to Rust by value. These are represented as full structs in C. Such /// values are implicitly copyable, via C's struct assignment. /// @@ -142,3 +145,86 @@ pub(crate) trait PassByPointer: Sized { } } } + +/// Support for arrays of objects referenced by pointer. +/// +/// The underlying C type should have three fields, containing items, length, and capacity. The +/// required trait functions just fetch and set these fields. The PassByValue trait will be +/// implemented automatically, converting between the C type and `Vec>`. For most +/// cases, it is only necessary to implement `tc_.._free` that first calls +/// `PassByValue::take_from_arg(arg, PointerArray::null_value())` to take the existing value and +/// replace it with the null value; then `PointerArray::drop_pointer_vector(..)` to drop the +/// resulting vector and all of the objects it points to. +/// +/// # Safety +/// +/// The C type must be documented as read-only. None of the fields may be modified, nor anything +/// in the `items` array. +/// +/// This class guarantees that the items pointer is non-NULL for any valid array (even when len=0), +/// and that all pointers at indexes 0..len-1 are non-NULL. +pub(crate) trait PointerArray: Sized { + type Element: 'static + PassByPointer; + + /// Create a new PointerArray from the given items, len, and capacity. + /// + /// # Safety + /// + /// The arguments must either: + /// - be NULL, 0, and 0, respectively; or + /// - be valid for Vec::from_raw_parts + unsafe fn from_raw_parts(items: *const NonNull, len: usize, cap: usize) -> Self; + + /// Get the items, len, and capacity (in that order) for this instance. These must be + /// precisely the same values passed tearlier to `from_raw_parts`. + fn into_raw_parts(self) -> (*const NonNull, usize, usize); + + /// Generate a NULL value. By default this is a NULL items pointer with zero length and + /// capacity. + fn null_value() -> Self { + // SAFETY: + // - satisfies the first case in from_raw_parts' safety documentation + unsafe { Self::from_raw_parts(std::ptr::null(), 0, 0) } + } + + /// Drop a vector of element pointers. This is a convenience function for implementing + /// tc_.._free functions. + fn drop_pointer_vector(mut vec: Vec>) { + // first, drop each of the elements in turn + for p in vec.drain(..) { + // SAFETY: + // - p is not NULL (NonNull) + // - p was generated by Rust (true for all arrays) + // - p was not modified (all arrays are immutable from C) + // - caller will not use this pointer again (promised by caller; p has been drain'd from + // the vector) + drop(unsafe { PassByPointer::take_from_arg(p.as_ptr()) }); + } + drop(vec); + } +} + +impl PassByValue for A +where + A: PointerArray, +{ + type RustType = Vec>; + + unsafe fn from_ctype(self) -> Self::RustType { + let (items, len, cap) = self.into_raw_parts(); + debug_assert!(!items.is_null()); + // SAFETY: + // - PointerArray::from_raw_parts requires that items, len, and cap be valid for + // Vec::from_raw_parts if not NULL, and they are not NULL (as promised by caller) + // - PointerArray::into_raw_parts returns precisely the values passed to from_raw_parts. + // - those parts are passed to Vec::from_raw_parts here. + unsafe { Vec::from_raw_parts(items as *mut _, len, cap) } + } + + fn as_ctype(arg: Self::RustType) -> Self { + let (items, len, cap) = vec_into_raw_parts(arg); + // SAFETY: + // - satisfies the second case in from_raw_parts' safety documentation + unsafe { Self::from_raw_parts(items, len, cap) } + } +} diff --git a/lib/src/util.rs b/lib/src/util.rs index 00676424a..bcab209ec 100644 --- a/lib/src/util.rs +++ b/lib/src/util.rs @@ -1,12 +1,10 @@ use crate::string::TCString; -use crate::traits::*; -use std::ptr::NonNull; pub(crate) fn err_to_tcstring(e: impl std::string::ToString) -> TCString<'static> { TCString::from(e.to_string()) } -/// A version of Vec::into_raw_parts, which is still unstable. Returns ptr, len, cap. +/// An implementation of Vec::into_raw_parts, which is still unstable. Returns ptr, len, cap. pub(crate) fn vec_into_raw_parts(vec: Vec) -> (*mut T, usize, usize) { // emulate Vec::into_raw_parts(): // - disable dropping the Vec with ManuallyDrop @@ -14,21 +12,3 @@ pub(crate) fn vec_into_raw_parts(vec: Vec) -> (*mut T, usize, usize) { let mut vec = std::mem::ManuallyDrop::new(vec); return (vec.as_mut_ptr(), vec.len(), vec.capacity()); } - -/// Drop an array of PassByPointer values -pub(crate) fn drop_pointer_array(mut array: Vec>) -where - T: 'static + PassByPointer, -{ - // first, drop each of the elements in turn - for p in array.drain(..) { - // SAFETY: - // - p is not NULL (NonNull) - // - p was generated by Rust (true for all arrays) - // - p was not modified (all arrays are immutable from C) - // - caller will not use this pointer again (promised by caller; p has been drain'd from - // the vector) - drop(unsafe { PassByPointer::take_from_arg(p.as_ptr()) }); - } - drop(array); -} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 818fc166f..86aa40cba 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -141,8 +141,9 @@ typedef struct TCStrings { */ size_t _capacity; /** - * strings representing each string. these remain owned by the TCStrings instance and will be freed - * by tc_strings_free. + * TCStrings representing each string. these remain owned by the TCStrings instance and will + * be freed by tc_strings_free. This pointer is never NULL for a valid TCStrings, and the + * *TCStrings at indexes 0..len-1 are not NULL. */ struct TCString *const *items; } TCStrings; @@ -272,6 +273,8 @@ void tc_string_free(struct TCString *tcstring); /** * Free a TCStrings instance. The instance, and all TCStrings it contains, must not be used after * this call. + * + * When this call returns, the `items` pointer will be NULL, signalling an invalid TCStrings. */ void tc_strings_free(struct TCStrings *tcstrings); From f96b5415c8a83fc7fbe8e81af49f9119a6f1ab8e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 9 Feb 2022 02:59:01 +0000 Subject: [PATCH 480/548] fix some clippy warnings --- lib/src/lib.rs | 1 + lib/src/replica.rs | 4 ++-- lib/src/util.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 5b9160970..fe17d1efd 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,4 +1,5 @@ #![warn(unsafe_op_in_unsafe_fn)] +#![warn(clippy::undocumented_unsafe_blocks)] mod traits; mod util; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index d393d95c2..efa5dcbc2 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -69,12 +69,12 @@ impl From for TCReplica { /// Utility function to allow using `?` notation to return an error value. This makes /// a mutable borrow, because most Replica methods require a `&mut`. -fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T +fn wrap(rep: *mut TCReplica, f: F, err_value: T) -> T where F: FnOnce(&mut Replica) -> anyhow::Result, { // SAFETY: see type docstring - let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref_mut(rep) }; + let rep: &mut TCReplica = unsafe { TCReplica::from_arg_ref_mut(rep) }; if rep.mut_borrowed { panic!("replica is borrowed and cannot be used"); } diff --git a/lib/src/util.rs b/lib/src/util.rs index bcab209ec..405a8b292 100644 --- a/lib/src/util.rs +++ b/lib/src/util.rs @@ -10,5 +10,5 @@ pub(crate) fn vec_into_raw_parts(vec: Vec) -> (*mut T, usize, usize) { // - disable dropping the Vec with ManuallyDrop // - extract ptr, len, and capacity using those methods let mut vec = std::mem::ManuallyDrop::new(vec); - return (vec.as_mut_ptr(), vec.len(), vec.capacity()); + (vec.as_mut_ptr(), vec.len(), vec.capacity()) } From 5cf3ce4bc89031e8f30aca985e6c1c18dc476788 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 9 Feb 2022 03:16:14 +0000 Subject: [PATCH 481/548] comment out failing clippy lint --- lib/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index fe17d1efd..2451efdaa 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,5 +1,6 @@ #![warn(unsafe_op_in_unsafe_fn)] -#![warn(clippy::undocumented_unsafe_blocks)] +// Not working yet in stable - https://github.com/rust-lang/rust-clippy/issues/8020 +// #![warn(clippy::undocumented_unsafe_blocks)] mod traits; mod util; From ae5afff4f7d3a08aac3a671eee7471a34da17196 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 9 Feb 2022 03:20:17 +0000 Subject: [PATCH 482/548] fix another lint --- taskchampion/src/storage/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index d3d494a1b..dd3c9786a 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -106,6 +106,7 @@ pub trait StorageTxn { fn clear_working_set(&mut self) -> Result<()>; /// Check whether this storage is entirely empty + #[allow(clippy::wrong_self_convention)] // mut is required here for storage access fn is_empty(&mut self) -> Result { let mut empty = true; empty = empty && self.all_tasks()?.is_empty(); From 8caf442e3f4a3d492144a856a88fd7eeff67b240 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 9 Feb 2022 23:26:39 +0000 Subject: [PATCH 483/548] mark all extern-C functions as unsafe --- lib/src/replica.rs | 16 +++++++-------- lib/src/string.rs | 12 +++++------ lib/src/strings.rs | 5 +++-- lib/src/task.rs | 50 +++++++++++++++++++++++----------------------- lib/src/uuid.rs | 10 +++++----- 5 files changed, 47 insertions(+), 46 deletions(-) diff --git a/lib/src/replica.rs b/lib/src/replica.rs index efa5dcbc2..07cb3f7a8 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -91,7 +91,7 @@ where /// Create a new TCReplica with an in-memory database. The contents of the database will be /// lost when it is freed. #[no_mangle] -pub extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { +pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { let storage = StorageConfig::InMemory .into_storage() .expect("in-memory always succeeds"); @@ -102,7 +102,7 @@ pub extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { /// 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. #[no_mangle] -pub extern "C" fn tc_replica_new_on_disk<'a>( +pub unsafe extern "C" fn tc_replica_new_on_disk<'a>( path: *mut TCString, error_out: *mut *mut TCString, ) -> *mut TCReplica { @@ -138,7 +138,7 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( /// Returns NULL when the task does not exist, and on error. Consult tc_replica_error /// to distinguish the two conditions. #[no_mangle] -pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask { +pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask { wrap( rep, |rep| { @@ -158,7 +158,7 @@ pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *m /// /// Returns the task, or NULL on error. #[no_mangle] -pub extern "C" fn tc_replica_new_task( +pub unsafe extern "C" fn tc_replica_new_task( rep: *mut TCReplica, status: TCStatus, description: *mut TCString, @@ -179,7 +179,7 @@ pub extern "C" fn tc_replica_new_task( /// /// Returns the task, or NULL on error. #[no_mangle] -pub extern "C" fn tc_replica_import_task_with_uuid( +pub unsafe extern "C" fn tc_replica_import_task_with_uuid( rep: *mut TCReplica, tcuuid: TCUuid, ) -> *mut TCTask { @@ -202,7 +202,7 @@ pub extern "C" fn tc_replica_import_task_with_uuid( /// 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. #[no_mangle] -pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica, undone_out: *mut i32) -> TCResult { +pub unsafe extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica, undone_out: *mut i32) -> TCResult { wrap( rep, |rep| { @@ -223,7 +223,7 @@ pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica, undone_out: *mut i32) /// to this function will return NULL. The rep pointer must not be NULL. The caller must free the /// returned string. #[no_mangle] -pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'static> { // SAFETY: see type docstring let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref_mut(rep) }; if let Some(tcstring) = rep.error.take() { @@ -237,7 +237,7 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'st /// Free a replica. The replica may not be used after this function returns and must not be freed /// more than once. #[no_mangle] -pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { +pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) { // SAFETY: see type docstring let replica = unsafe { TCReplica::take_from_arg(rep) }; if replica.mut_borrowed { diff --git a/lib/src/string.rs b/lib/src/string.rs index 138ff31ea..b29258ae0 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -139,7 +139,7 @@ impl<'a> From<&str> for TCString<'static> { /// free(url); // string is no longer referenced and can be freed /// ``` #[no_mangle] -pub extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<'static> { debug_assert!(!cstr.is_null()); // SAFETY: // - cstr is not NULL (promised by caller, verified by assertion) @@ -154,7 +154,7 @@ pub extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<' /// 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. #[no_mangle] -pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'static> { debug_assert!(!cstr.is_null()); // SAFETY: // - cstr is not NULL (promised by caller, verified by assertion) @@ -172,7 +172,7 @@ pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'s /// /// The given length must be less than half the maximum value of usize. #[no_mangle] -pub extern "C" fn tc_string_clone_with_len( +pub unsafe extern "C" fn tc_string_clone_with_len( buf: *const libc::c_char, len: usize, ) -> *mut TCString<'static> { @@ -212,7 +212,7 @@ pub extern "C" fn tc_string_clone_with_len( /// /// This function does _not_ take ownership of the TCString. #[no_mangle] -pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { +pub unsafe extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { // SAFETY: // - tcstring is not NULL (promised by caller) // - lifetime of tcstring outlives the lifetime of this function @@ -239,7 +239,7 @@ pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_c /// /// This function does _not_ take ownership of the TCString. #[no_mangle] -pub extern "C" fn tc_string_content_with_len( +pub unsafe extern "C" fn tc_string_content_with_len( tcstring: *mut TCString, len_out: *mut usize, ) -> *const libc::c_char { @@ -262,7 +262,7 @@ pub extern "C" fn tc_string_content_with_len( /// 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. #[no_mangle] -pub extern "C" fn tc_string_free(tcstring: *mut TCString) { +pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { // SAFETY: // - tcstring is not NULL (promised by caller) // - caller is exclusive owner of tcstring (promised by caller) diff --git a/lib/src/strings.rs b/lib/src/strings.rs index 55572ec25..751a0aaed 100644 --- a/lib/src/strings.rs +++ b/lib/src/strings.rs @@ -40,7 +40,7 @@ impl PointerArray for TCStrings { /// /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStrings. #[no_mangle] -pub extern "C" fn tc_strings_free(tcstrings: *mut TCStrings) { +pub unsafe extern "C" fn tc_strings_free(tcstrings: *mut TCStrings) { debug_assert!(!tcstrings.is_null()); // SAFETY: // - *tcstrings is a valid TCStrings (caller promises to treat it as read-only) @@ -63,7 +63,8 @@ mod test { #[test] fn free_sets_null_pointer() { let mut tcstrings = TCStrings::return_val(Vec::new()); - tc_strings_free(&mut tcstrings); + // SAFETY: testing expected behavior + unsafe { tc_strings_free(&mut tcstrings) }; assert!(tcstrings.items.is_null()); assert_eq!(tcstrings.len, 0); assert_eq!(tcstrings._capacity, 0); diff --git a/lib/src/task.rs b/lib/src/task.rs index 823fbbffe..f37f08a09 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -218,7 +218,7 @@ fn to_datetime(time: libc::time_t) -> Option> { /// if (!success) { ... } /// ``` #[no_mangle] -pub extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut TCReplica) { +pub unsafe extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut TCReplica) { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) @@ -236,7 +236,7 @@ pub extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut TCReplic /// /// The replica passed to `tc_task_to_mut` may be used freely after this call. #[no_mangle] -pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { +pub unsafe extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) @@ -246,13 +246,13 @@ pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { /// Get a task's UUID. #[no_mangle] -pub extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { +pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { wrap(task, |task| TCUuid::return_val(task.get_uuid())) } /// Get a task's status. #[no_mangle] -pub extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { +pub unsafe extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { wrap(task, |task| task.get_status().into()) } @@ -261,7 +261,7 @@ pub extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). #[no_mangle] -pub extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCString<'static> { wrap(task, |task| { let descr: TCString = task.get_description().into(); // SAFETY: see TCString docstring @@ -271,38 +271,38 @@ pub extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCStrin /// Get the entry timestamp for a task (when it was created), or 0 if not set. #[no_mangle] -pub extern "C" fn tc_task_get_entry<'a>(task: *mut TCTask) -> libc::time_t { +pub unsafe extern "C" fn tc_task_get_entry<'a>(task: *mut TCTask) -> libc::time_t { wrap(task, |task| to_time_t(task.get_entry())) } /// Get the wait timestamp for a task, or 0 if not set. #[no_mangle] -pub extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t { +pub unsafe extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t { wrap(task, |task| to_time_t(task.get_wait())) } /// Get the modified timestamp for a task, or 0 if not set. #[no_mangle] -pub extern "C" fn tc_task_get_modified<'a>(task: *mut TCTask) -> libc::time_t { +pub unsafe extern "C" fn tc_task_get_modified<'a>(task: *mut TCTask) -> libc::time_t { wrap(task, |task| to_time_t(task.get_modified())) } /// Check if a task is waiting. #[no_mangle] -pub extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool { +pub unsafe extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool { wrap(task, |task| task.is_waiting()) } /// Check if a task is active (started and not stopped). #[no_mangle] -pub extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { +pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { wrap(task, |task| task.is_active()) } /// 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`. #[no_mangle] -pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { +pub unsafe extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { // SAFETY: see TCString docstring let tcstring = unsafe { TCString::take_from_arg(tag) }; wrap(task, |task| { @@ -319,7 +319,7 @@ pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> /// The caller must free the returned TCStrings instance. The TCStrings instance does not /// reference the task and the two may be freed in any order. #[no_mangle] -pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { +pub unsafe extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { wrap(task, |task| { let vec: Vec>> = task .get_tags() @@ -343,7 +343,7 @@ pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { /// Set a mutable task's status. #[no_mangle] -pub extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> TCResult { +pub unsafe extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> TCResult { wrap_mut( task, |task| { @@ -356,7 +356,7 @@ pub extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> /// Set a mutable task's description. #[no_mangle] -pub extern "C" fn tc_task_set_description<'a>( +pub unsafe extern "C" fn tc_task_set_description<'a>( task: *mut TCTask, description: *mut TCString, ) -> TCResult { @@ -375,7 +375,7 @@ pub extern "C" fn tc_task_set_description<'a>( /// Set a mutable task's entry (creation time). Pass entry=0 to unset /// the entry field. #[no_mangle] -pub extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult { +pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult { wrap_mut( task, |task| { @@ -388,7 +388,7 @@ pub extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> T /// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. #[no_mangle] -pub extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult { +pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult { wrap_mut( task, |task| { @@ -401,7 +401,7 @@ pub extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCR /// Set a mutable task's modified timestamp. The value cannot be zero. #[no_mangle] -pub extern "C" fn tc_task_set_modified(task: *mut TCTask, modified: libc::time_t) -> TCResult { +pub unsafe extern "C" fn tc_task_set_modified(task: *mut TCTask, modified: libc::time_t) -> TCResult { wrap_mut( task, |task| { @@ -416,7 +416,7 @@ pub extern "C" fn tc_task_set_modified(task: *mut TCTask, modified: libc::time_t /// Start a task. #[no_mangle] -pub extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { +pub unsafe extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -429,7 +429,7 @@ pub extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { /// Stop a task. #[no_mangle] -pub extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { +pub unsafe extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -442,7 +442,7 @@ pub extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { /// Mark a task as done. #[no_mangle] -pub extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { +pub unsafe extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -455,7 +455,7 @@ pub extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { /// Mark a task as deleted. #[no_mangle] -pub extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { +pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -468,7 +468,7 @@ pub extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { /// Add a tag to a mutable task. #[no_mangle] -pub extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { +pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { // SAFETY: see TCString docstring let tcstring = unsafe { TCString::take_from_arg(tag) }; wrap_mut( @@ -484,7 +484,7 @@ pub extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCRe /// Remove a tag from a mutable task. #[no_mangle] -pub extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { +pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { // SAFETY: see TCString docstring let tcstring = unsafe { TCString::take_from_arg(tag) }; wrap_mut( @@ -509,7 +509,7 @@ pub extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> T /// to this function will return NULL. The task pointer must not be NULL. The caller must free the /// returned string. #[no_mangle] -pub extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<'static> { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) @@ -526,7 +526,7 @@ pub extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<'static> /// /// If the task is currently mutable, it will first be made immutable. #[no_mangle] -pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) { +pub unsafe extern "C" fn tc_task_free<'a>(task: *mut TCTask) { // SAFETY: // - rep is not NULL (promised by caller) // - caller will not use the TCTask after this (promised by caller) diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 3c7751747..cef445e40 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -26,13 +26,13 @@ impl PassByValue for TCUuid { /// Create a new, randomly-generated UUID. #[no_mangle] -pub extern "C" fn tc_uuid_new_v4() -> TCUuid { +pub unsafe extern "C" fn tc_uuid_new_v4() -> TCUuid { TCUuid::return_val(Uuid::new_v4()) } /// Create a new UUID with the nil value. #[no_mangle] -pub extern "C" fn tc_uuid_nil() -> TCUuid { +pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid { TCUuid::return_val(Uuid::nil()) } @@ -43,7 +43,7 @@ pub const TC_UUID_STRING_BYTES: usize = 36; /// 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. #[no_mangle] -pub extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { +pub unsafe extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { debug_assert!(!buf.is_null()); // SAFETY: // - buf is valid for len bytes (by C convention) @@ -63,7 +63,7 @@ pub extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { /// 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. #[no_mangle] -pub extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) let uuid: Uuid = unsafe { TCUuid::from_arg(tcuuid) }; @@ -74,7 +74,7 @@ pub extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { /// Parse the given string as a UUID. Returns false on failure. #[no_mangle] -pub extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> bool { +pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> bool { // TODO: TCResult instead debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null()); From 28a4599a6a5fbb9a86b30eb847bbd413c9b8be6b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 9 Feb 2022 23:37:32 +0000 Subject: [PATCH 484/548] rename TCStrings to TCStringList --- integration-tests/src/bindings_tests/task.c | 4 +-- lib/src/lib.rs | 2 +- lib/src/string.rs | 4 +-- lib/src/{strings.rs => stringlist.rs} | 32 ++++++++++----------- lib/src/task.rs | 15 ++++++---- lib/taskchampion.h | 26 ++++++++--------- 6 files changed, 43 insertions(+), 40 deletions(-) rename lib/src/{strings.rs => stringlist.rs} (55%) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 2ab5e3602..5a0214ce3 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -312,7 +312,7 @@ static void test_task_get_tags(void) { TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); - TCStrings tags = tc_task_get_tags(task); + TCStringList tags = tc_task_get_tags(task); int found_pending = false, found_next = false; for (size_t i = 0; i < tags.len; i++) { @@ -326,7 +326,7 @@ static void test_task_get_tags(void) { TEST_ASSERT_TRUE(found_pending); TEST_ASSERT_TRUE(found_next); - tc_strings_free(&tags); + tc_string_list_free(&tags); TEST_ASSERT_NULL(tags.items); tc_task_free(task); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 2451efdaa..f60142262 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -10,6 +10,6 @@ pub mod replica; pub mod result; pub mod status; pub mod string; -pub mod strings; +pub mod stringlist; pub mod task; pub mod uuid; diff --git a/lib/src/string.rs b/lib/src/string.rs index b29258ae0..da35e1220 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -31,9 +31,9 @@ use std::str::Utf8Error; /// /// Unless specified otherwise, TaskChampion functions take ownership of a `*TCString` when it is /// given as a function argument, and the pointer is invalid when the function returns. Callers -/// must not use or free TCStrings after passing them to such API functions. +/// must not use or free TCStringList after passing them to such API functions. /// -/// TCStrings are not threadsafe. +/// TCString is not threadsafe. #[derive(PartialEq, Debug)] pub enum TCString<'a> { CString(CString), diff --git a/lib/src/strings.rs b/lib/src/stringlist.rs similarity index 55% rename from lib/src/strings.rs rename to lib/src/stringlist.rs index 751a0aaed..3daf5f3cd 100644 --- a/lib/src/strings.rs +++ b/lib/src/stringlist.rs @@ -2,28 +2,28 @@ use crate::string::TCString; use crate::traits::*; use std::ptr::NonNull; -/// TCStrings represents a list of strings. +/// TCStringList represents a list of strings. /// /// The content of this struct must be treated as read-only. #[repr(C)] -pub struct TCStrings { +pub struct TCStringList { /// number of strings in items len: libc::size_t, /// total size of items (internal use only) _capacity: libc::size_t, - /// TCStrings representing each string. these remain owned by the TCStrings instance and will - /// be freed by tc_strings_free. This pointer is never NULL for a valid TCStrings, and the - /// *TCStrings at indexes 0..len-1 are not NULL. + /// TCStringList representing each string. these remain owned by the TCStringList instance and will + /// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the + /// *TCStringList at indexes 0..len-1 are not NULL. items: *const NonNull>, } -impl PointerArray for TCStrings { +impl PointerArray for TCStringList { type Element = TCString<'static>; unsafe fn from_raw_parts(items: *const NonNull, len: usize, cap: usize) -> Self { - TCStrings { + TCStringList { len, _capacity: cap, items, @@ -35,17 +35,17 @@ impl PointerArray for TCStrings { } } -/// Free a TCStrings instance. The instance, and all TCStrings it contains, must not be used after +/// 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 TCStrings. +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. #[no_mangle] -pub unsafe extern "C" fn tc_strings_free(tcstrings: *mut TCStrings) { +pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { debug_assert!(!tcstrings.is_null()); // SAFETY: - // - *tcstrings is a valid TCStrings (caller promises to treat it as read-only) - let strings = unsafe { TCStrings::take_from_arg(tcstrings, TCStrings::null_value()) }; - TCStrings::drop_pointer_vector(strings); + // - *tcstrings is a valid TCStringList (caller promises to treat it as read-only) + let strings = unsafe { TCStringList::take_from_arg(tcstrings, TCStringList::null_value()) }; + TCStringList::drop_pointer_vector(strings); } #[cfg(test)] @@ -54,7 +54,7 @@ mod test { #[test] fn empty_array_has_non_null_pointer() { - let tcstrings = TCStrings::return_val(Vec::new()); + let tcstrings = TCStringList::return_val(Vec::new()); assert!(!tcstrings.items.is_null()); assert_eq!(tcstrings.len, 0); assert_eq!(tcstrings._capacity, 0); @@ -62,9 +62,9 @@ mod test { #[test] fn free_sets_null_pointer() { - let mut tcstrings = TCStrings::return_val(Vec::new()); + let mut tcstrings = TCStringList::return_val(Vec::new()); // SAFETY: testing expected behavior - unsafe { tc_strings_free(&mut tcstrings) }; + unsafe { tc_string_list_free(&mut tcstrings) }; assert!(tcstrings.items.is_null()); assert_eq!(tcstrings.len, 0); assert_eq!(tcstrings._capacity, 0); diff --git a/lib/src/task.rs b/lib/src/task.rs index f37f08a09..a69aedf04 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,8 +1,8 @@ use crate::traits::*; use crate::util::err_to_tcstring; use crate::{ - replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, strings::TCStrings, - uuid::TCUuid, + replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, + stringlist::TCStringList, uuid::TCUuid, }; use chrono::{DateTime, TimeZone, Utc}; use std::convert::TryFrom; @@ -316,10 +316,10 @@ pub unsafe extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCStri /// Get the tags for the task. /// -/// The caller must free the returned TCStrings instance. The TCStrings instance does not +/// 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. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { +pub unsafe extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStringList { wrap(task, |task| { let vec: Vec>> = task .get_tags() @@ -331,7 +331,7 @@ pub unsafe extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { .expect("TCString::return_val() returned NULL") }) .collect(); - TCStrings::return_val(vec) + TCStringList::return_val(vec) }) } @@ -401,7 +401,10 @@ pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) /// Set a mutable task's modified timestamp. The value cannot be zero. #[no_mangle] -pub unsafe extern "C" fn tc_task_set_modified(task: *mut TCTask, modified: libc::time_t) -> TCResult { +pub unsafe extern "C" fn tc_task_set_modified( + task: *mut TCTask, + modified: libc::time_t, +) -> TCResult { wrap_mut( task, |task| { diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 86aa40cba..fa15ae95d 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -91,9 +91,9 @@ typedef struct TCReplica TCReplica; * * Unless specified otherwise, TaskChampion functions take ownership of a `*TCString` when it is * given as a function argument, and the pointer is invalid when the function returns. Callers - * must not use or free TCStrings after passing them to such API functions. + * must not use or free TCStringList after passing them to such API functions. * - * TCStrings are not threadsafe. + * TCString is not threadsafe. */ typedef struct TCString TCString; @@ -127,11 +127,11 @@ typedef struct TCUuid { } TCUuid; /** - * TCStrings represents a list of strings. + * TCStringList represents a list of strings. * * The content of this struct must be treated as read-only. */ -typedef struct TCStrings { +typedef struct TCStringList { /** * number of strings in items */ @@ -141,12 +141,12 @@ typedef struct TCStrings { */ size_t _capacity; /** - * TCStrings representing each string. these remain owned by the TCStrings instance and will - * be freed by tc_strings_free. This pointer is never NULL for a valid TCStrings, and the - * *TCStrings at indexes 0..len-1 are not NULL. + * 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 *const *items; -} TCStrings; +} TCStringList; #ifdef __cplusplus extern "C" { @@ -271,12 +271,12 @@ const char *tc_string_content_with_len(struct TCString *tcstring, size_t *len_ou void tc_string_free(struct TCString *tcstring); /** - * Free a TCStrings instance. The instance, and all TCStrings it contains, must not be used after + * 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 TCStrings. + * When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. */ -void tc_strings_free(struct TCStrings *tcstrings); +void tc_string_list_free(struct TCStringList *tcstrings); /** * Convert an immutable task into a mutable task. @@ -357,10 +357,10 @@ bool tc_task_has_tag(struct TCTask *task, struct TCString *tag); /** * Get the tags for the task. * - * The caller must free the returned TCStrings instance. The TCStrings instance does not + * 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 TCStrings tc_task_get_tags(struct TCTask *task); +struct TCStringList tc_task_get_tags(struct TCTask *task); /** * Set a mutable task's status. From c9c72b4fd33e439b2d4bbd65d2c2b59f7469b8d1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 9 Feb 2022 23:43:23 +0000 Subject: [PATCH 485/548] return TCResult from tc_uuid_from_str --- integration-tests/src/bindings_tests/replica.c | 4 ++-- integration-tests/src/bindings_tests/uuid.c | 8 ++++---- lib/src/uuid.rs | 12 ++++++------ lib/taskchampion.h | 5 +++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index bf3cdaa43..11fc6d85c 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -79,7 +79,7 @@ static void test_replica_task_import(void) { TEST_ASSERT_NULL(tc_replica_error(rep)); TCUuid uuid; - TEST_ASSERT_TRUE(tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); TCTask *task = tc_replica_import_task_with_uuid(rep, uuid); TEST_ASSERT_NOT_NULL(task); @@ -110,7 +110,7 @@ static void test_replica_get_task_not_found(void) { TEST_ASSERT_NULL(tc_replica_error(rep)); TCUuid uuid; - TEST_ASSERT_TRUE(tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); TCTask *task = tc_replica_get_task(rep, uuid); TEST_ASSERT_NULL(task); TEST_ASSERT_NULL(tc_replica_error(rep)); diff --git a/integration-tests/src/bindings_tests/uuid.c b/integration-tests/src/bindings_tests/uuid.c index 572a85322..0c9b72be1 100644 --- a/integration-tests/src/bindings_tests/uuid.c +++ b/integration-tests/src/bindings_tests/uuid.c @@ -33,7 +33,7 @@ static void test_uuid_to_str(void) { static void test_uuid_valid_from_str(void) { TCUuid u; char *ustr = "23cb25e0-5d1a-4932-8131-594ac6d3a843"; - TEST_ASSERT_TRUE(tc_uuid_from_str(tc_string_borrow(ustr), &u)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow(ustr), &u)); TEST_ASSERT_EQUAL(0x23, u.bytes[0]); TEST_ASSERT_EQUAL(0x43, u.bytes[15]); } @@ -42,20 +42,20 @@ static void test_uuid_valid_from_str(void) { static void test_uuid_invalid_string_fails(void) { TCUuid u; char *ustr = "not-a-valid-uuid"; - TEST_ASSERT_FALSE(tc_uuid_from_str(tc_string_borrow(ustr), &u)); + TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_uuid_from_str(tc_string_borrow(ustr), &u)); } // converting invalid UTF-8 UUIDs from string fails as expected static void test_uuid_bad_utf8(void) { TCUuid u; char *ustr = "\xf0\x28\x8c\xbc"; - TEST_ASSERT_FALSE(tc_uuid_from_str(tc_string_borrow(ustr), &u)); + TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_uuid_from_str(tc_string_borrow(ustr), &u)); } // converting a string with embedded NUL fails as expected static void test_uuid_embedded_nul(void) { TCUuid u; - TEST_ASSERT_FALSE(tc_uuid_from_str(tc_string_clone_with_len("ab\0de", 5), &u)); + TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_uuid_from_str(tc_string_clone_with_len("ab\0de", 5), &u)); } int uuid_tests(void) { diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index cef445e40..415e9501a 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -1,5 +1,5 @@ -use crate::string::TCString; use crate::traits::*; +use crate::{result::TCResult, string::TCString}; use libc; use taskchampion::Uuid; @@ -72,10 +72,10 @@ pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static unsafe { TCString::from(s).return_val() } } -/// Parse the given string as a UUID. Returns false on failure. +/// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given +/// string is not valid. #[no_mangle] -pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> bool { - // TODO: TCResult instead +pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> TCResult { debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null()); // SAFETY: see TCString docstring @@ -86,8 +86,8 @@ pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut T // - uuid_out is not NULL (promised by caller) // - alignment is not required unsafe { TCUuid::to_arg_out(u, uuid_out) }; - return true; + return TCResult::Ok; } } - false + TCResult::Error } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index fa15ae95d..4ac06acfd 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -456,9 +456,10 @@ void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); struct TCString *tc_uuid_to_str(struct TCUuid tcuuid); /** - * Parse the given string as a UUID. Returns false on failure. + * Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given + * string is not valid. */ -bool tc_uuid_from_str(struct TCString *s, struct TCUuid *uuid_out); +TCResult tc_uuid_from_str(struct TCString *s, struct TCUuid *uuid_out); #ifdef __cplusplus } // extern "C" From 914017b46c49e712431a1427c9754151845894c0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 10 Feb 2022 00:10:39 +0000 Subject: [PATCH 486/548] tc_replica_all_tasks --- .../src/bindings_tests/replica.c | 44 ++++++++++++ lib/src/lib.rs | 1 + lib/src/replica.rs | 49 +++++++++++-- lib/src/task.rs | 16 +++-- lib/src/tasklist.rs | 72 +++++++++++++++++++ lib/taskchampion.h | 37 ++++++++++ 6 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 lib/src/tasklist.rs diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 11fc6d85c..41cf2dfe8 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -73,6 +73,49 @@ static void test_replica_task_creation(void) { tc_replica_free(rep); } +// a replica with tasks in it returns an appropriate list of tasks +static void test_replica_all_tasks(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("task1")); + TEST_ASSERT_NOT_NULL(task); + tc_task_free(task); + + task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("task2")); + TEST_ASSERT_NOT_NULL(task); + tc_task_free(task); + + TCTaskList tasks = tc_replica_all_tasks(rep); + TEST_ASSERT_NOT_NULL(tasks.items); + TEST_ASSERT_EQUAL(2, tasks.len); + + int seen1, seen2 = false; + for (size_t i = 0; i < tasks.len; i++) { + TCTask *task = tasks.items[i]; + TCString *descr = tc_task_get_description(task); + if (0 == strcmp(tc_string_content(descr), "task1")) { + seen1 = true; + } else if (0 == strcmp(tc_string_content(descr), "task2")) { + seen2 = true; + } + tc_string_free(descr); + } + TEST_ASSERT_TRUE(seen1); + TEST_ASSERT_TRUE(seen2); + + tc_task_list_free(&tasks); + TEST_ASSERT_NULL(tasks.items); + + tc_replica_free(rep); +} + // importing a task succeeds and the resulting task looks good static void test_replica_task_import(void) { TCReplica *rep = tc_replica_new_in_memory(); @@ -124,6 +167,7 @@ int replica_tests(void) { RUN_TEST(test_replica_undo_empty); RUN_TEST(test_replica_undo_empty_null_undone_out); RUN_TEST(test_replica_task_creation); + RUN_TEST(test_replica_all_tasks); RUN_TEST(test_replica_task_import); RUN_TEST(test_replica_get_task_not_found); return UNITY_END(); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f60142262..f9e353d21 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -12,4 +12,5 @@ pub mod status; pub mod string; pub mod stringlist; pub mod task; +pub mod tasklist; pub mod uuid; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 07cb3f7a8..b17c256a1 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,6 +1,10 @@ use crate::traits::*; use crate::util::err_to_tcstring; -use crate::{result::TCResult, status::TCStatus, string::TCString, task::TCTask, uuid::TCUuid}; +use crate::{ + result::TCResult, status::TCStatus, string::TCString, task::TCTask, tasklist::TCTaskList, + uuid::TCUuid, +}; +use std::ptr::NonNull; use taskchampion::{Replica, StorageConfig}; /// A replica represents an instance of a user's task data, providing an easy interface @@ -129,7 +133,34 @@ pub unsafe extern "C" fn tc_replica_new_on_disk<'a>( unsafe { TCReplica::from(Replica::new(storage)).return_val() } } -// TODO: tc_replica_all_tasks +/// Get a list of all tasks in the replica. +/// +/// Returns a TCTaskList with a NULL items field on error. +#[no_mangle] +pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList { + wrap( + rep, + |rep| { + // note that the Replica API returns a hashmap here, but we discard + // the keys and return a simple array. The task UUIDs are available + // from task.get_uuid(), so information is not lost. + let tasks: Vec<_> = rep + .all_tasks()? + .drain() + .map(|(_uuid, t)| { + NonNull::new( + // SAFETY: see TCTask docstring + unsafe { TCTask::from(t).return_val() }, + ) + .expect("TCTask::return_val returned NULL") + }) + .collect(); + Ok(TCTaskList::return_val(tasks)) + }, + TCTaskList::null_value(), + ) +} + // TODO: tc_replica_all_task_uuids // TODO: tc_replica_working_set @@ -145,7 +176,8 @@ pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid // SAFETY: see TCUuid docstring let uuid = unsafe { TCUuid::from_arg(tcuuid) }; if let Some(task) = rep.get_task(uuid)? { - Ok(TCTask::from(task).return_val()) + // SAFETY: caller promises to free this task + Ok(unsafe { TCTask::from(task).return_val() }) } else { Ok(std::ptr::null_mut()) } @@ -169,7 +201,8 @@ pub unsafe extern "C" fn tc_replica_new_task( rep, |rep| { let task = rep.new_task(status.into(), description.as_str()?.to_string())?; - Ok(TCTask::from(task).return_val()) + // SAFETY: caller promises to free this task + Ok(unsafe { TCTask::from(task).return_val() }) }, std::ptr::null_mut(), ) @@ -189,7 +222,8 @@ pub unsafe extern "C" fn tc_replica_import_task_with_uuid( // SAFETY: see TCUuid docstring let uuid = unsafe { TCUuid::from_arg(tcuuid) }; let task = rep.import_task_with_uuid(uuid)?; - Ok(TCTask::from(task).return_val()) + // SAFETY: caller promises to free this task + Ok(unsafe { TCTask::from(task).return_val() }) }, std::ptr::null_mut(), ) @@ -202,7 +236,10 @@ pub unsafe extern "C" fn tc_replica_import_task_with_uuid( /// 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. #[no_mangle] -pub unsafe extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica, undone_out: *mut i32) -> TCResult { +pub unsafe extern "C" fn tc_replica_undo<'a>( + rep: *mut TCReplica, + undone_out: *mut i32, +) -> TCResult { wrap( rep, |rep| { diff --git a/lib/src/task.rs b/lib/src/task.rs index a69aedf04..c244081ed 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -49,7 +49,10 @@ enum Inner { Invalid, } +impl PassByPointer for TCTask {} + impl TCTask { + /* /// Borrow a TCTask from C as an argument. /// /// # Safety @@ -78,6 +81,7 @@ impl TCTask { pub(crate) fn return_val(self) -> *mut TCTask { Box::into_raw(Box::new(self)) } + */ /// Make an immutable TCTask into a mutable TCTask. Does nothing if the task /// is already mutable. @@ -140,7 +144,7 @@ where // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; let task: &'a Task = match &tctask.inner { Inner::Immutable(t) => t, Inner::Mutable(t, _) => t.deref(), @@ -160,7 +164,7 @@ where // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; let task: &'a mut TaskMut = match tctask.inner { Inner::Immutable(_) => panic!("Task is immutable"), Inner::Mutable(ref mut t, _) => t, @@ -222,7 +226,7 @@ pub unsafe extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut T // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; + let tctask: &'a mut TCTask = unsafe { TCTask::from_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, @@ -240,7 +244,7 @@ pub unsafe extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; + let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; tctask.to_immut(); } @@ -516,7 +520,7 @@ pub unsafe extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<' // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let task: &'a mut TCTask = unsafe { TCTask::from_arg_ref(task) }; + let task: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; if let Some(tcstring) = task.error.take() { unsafe { tcstring.return_val() } } else { @@ -533,7 +537,7 @@ pub unsafe extern "C" fn tc_task_free<'a>(task: *mut TCTask) { // SAFETY: // - rep is not NULL (promised by caller) // - caller will not use the TCTask after this (promised by caller) - let mut tctask = unsafe { TCTask::from_arg(task) }; + let mut tctask = unsafe { TCTask::take_from_arg(task) }; // convert to immut if it was mutable tctask.to_immut(); diff --git a/lib/src/tasklist.rs b/lib/src/tasklist.rs new file mode 100644 index 000000000..eaba75ca1 --- /dev/null +++ b/lib/src/tasklist.rs @@ -0,0 +1,72 @@ +use crate::task::TCTask; +use crate::traits::*; +use std::ptr::NonNull; + +/// TCTaskList represents a list of tasks. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCTaskList { + /// number of tasks in items + len: libc::size_t, + + /// total size of items (internal use only) + _capacity: libc::size_t, + + /// array of pointers representing each task. these remain owned by the TCTaskList instance and + /// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList, + /// and the *TCTaskList at indexes 0..len-1 are not NULL. + items: *const NonNull, +} + +impl PointerArray for TCTaskList { + type Element = TCTask; + + unsafe fn from_raw_parts(items: *const NonNull, len: usize, cap: usize) -> Self { + TCTaskList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const NonNull, usize, usize) { + (self.items, self.len, self._capacity) + } +} + +/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after +/// this call. +/// +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. +#[no_mangle] +pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { + debug_assert!(!tctasks.is_null()); + // SAFETY: + // - *tctasks is a valid TCTaskList (caller promises to treat it as read-only) + let tasks = unsafe { TCTaskList::take_from_arg(tctasks, TCTaskList::null_value()) }; + TCTaskList::drop_pointer_vector(tasks); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_array_has_non_null_pointer() { + let tctasks = TCTaskList::return_val(Vec::new()); + assert!(!tctasks.items.is_null()); + assert_eq!(tctasks.len, 0); + assert_eq!(tctasks._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tctasks = TCTaskList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_task_list_free(&mut tctasks) }; + assert!(tctasks.items.is_null()); + assert_eq!(tctasks.len, 0); + assert_eq!(tctasks._capacity, 0); + } +} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 4ac06acfd..e59a9c59d 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -117,6 +117,28 @@ typedef struct TCString TCString; */ typedef struct TCTask TCTask; +/** + * TCTaskList represents a list of tasks. + * + * The content of this struct must be treated as read-only. + */ +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 *const *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. @@ -164,6 +186,13 @@ struct TCReplica *tc_replica_new_in_memory(void); */ struct TCReplica *tc_replica_new_on_disk(struct TCString *path, 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 an existing task by its UUID. * @@ -433,6 +462,14 @@ struct TCString *tc_task_error(struct TCTask *task); */ void tc_task_free(struct TCTask *task); +/** + * 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 *tctasks); + /** * Create a new, randomly-generated UUID. */ From 8cbd44544c2523ade0aae993ad9b295bdd7f772f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 10 Feb 2022 00:30:13 +0000 Subject: [PATCH 487/548] remove commented-out code --- lib/src/task.rs | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/lib/src/task.rs b/lib/src/task.rs index c244081ed..50acf04c8 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -52,37 +52,6 @@ enum Inner { impl PassByPointer for TCTask {} impl TCTask { - /* - /// Borrow a TCTask from C as an argument. - /// - /// # Safety - /// - /// The pointer must not be NULL. It is the caller's responsibility to ensure that the - /// lifetime assigned to the reference and the lifetime of the TCTask itself do not outlive - /// the lifetime promised by C. - pub(crate) unsafe fn from_arg_ref<'a>(tctask: *mut TCTask) -> &'a mut Self { - debug_assert!(!tctask.is_null()); - // SAFETY: see docstring - unsafe { &mut *tctask } - } - - /// Take a TCTask from C as an argument. - /// - /// # Safety - /// - /// The pointer must not be NULL. The pointer becomes invalid before this function returns. - pub(crate) unsafe fn from_arg<'a>(tctask: *mut TCTask) -> Self { - debug_assert!(!tctask.is_null()); - // SAFETY: see docstring - unsafe { *Box::from_raw(tctask) } - } - - /// Convert a TCTask to a return value for handing off to C. - pub(crate) fn return_val(self) -> *mut TCTask { - Box::into_raw(Box::new(self)) - } - */ - /// Make an immutable TCTask into a mutable TCTask. Does nothing if the task /// is already mutable. /// From a4d992012e469f6e917f19bacc96c6638da369c0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 10 Feb 2022 00:55:34 +0000 Subject: [PATCH 488/548] TCUuidList, refactor traits --- .../src/bindings_tests/replica.c | 73 ++++++++---- lib/src/lib.rs | 1 + lib/src/replica.rs | 22 +++- lib/src/stringlist.rs | 10 +- lib/src/tasklist.rs | 10 +- lib/src/traits.rs | 112 +++++++++++++----- lib/src/uuidlist.rs | 70 +++++++++++ lib/taskchampion.h | 36 ++++++ 8 files changed, 271 insertions(+), 63 deletions(-) create mode 100644 lib/src/uuidlist.rs diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 41cf2dfe8..14084ab00 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -73,45 +73,70 @@ static void test_replica_task_creation(void) { tc_replica_free(rep); } -// a replica with tasks in it returns an appropriate list of tasks +// a replica with tasks in it returns an appropriate list of tasks and list of uuids static void test_replica_all_tasks(void) { TCReplica *rep = tc_replica_new_in_memory(); TEST_ASSERT_NULL(tc_replica_error(rep)); - TCTask *task = tc_replica_new_task( + TCTask *task1 = tc_replica_new_task( rep, TC_STATUS_PENDING, tc_string_borrow("task1")); - TEST_ASSERT_NOT_NULL(task); - tc_task_free(task); + TEST_ASSERT_NOT_NULL(task1); + TCUuid uuid1 = tc_task_get_uuid(task1); + tc_task_free(task1); - task = tc_replica_new_task( + TCTask *task2 = tc_replica_new_task( rep, TC_STATUS_PENDING, tc_string_borrow("task2")); - TEST_ASSERT_NOT_NULL(task); - tc_task_free(task); + TEST_ASSERT_NOT_NULL(task2); + TCUuid uuid2 = tc_task_get_uuid(task2); + tc_task_free(task2); - TCTaskList tasks = tc_replica_all_tasks(rep); - TEST_ASSERT_NOT_NULL(tasks.items); - TEST_ASSERT_EQUAL(2, tasks.len); + { + TCTaskList tasks = tc_replica_all_tasks(rep); + TEST_ASSERT_NOT_NULL(tasks.items); + TEST_ASSERT_EQUAL(2, tasks.len); - int seen1, seen2 = false; - for (size_t i = 0; i < tasks.len; i++) { - TCTask *task = tasks.items[i]; - TCString *descr = tc_task_get_description(task); - if (0 == strcmp(tc_string_content(descr), "task1")) { - seen1 = true; - } else if (0 == strcmp(tc_string_content(descr), "task2")) { - seen2 = true; + bool seen1, seen2 = false; + for (size_t i = 0; i < tasks.len; i++) { + TCTask *task = tasks.items[i]; + TCString *descr = tc_task_get_description(task); + if (0 == strcmp(tc_string_content(descr), "task1")) { + seen1 = true; + } else if (0 == strcmp(tc_string_content(descr), "task2")) { + seen2 = true; + } + tc_string_free(descr); } - tc_string_free(descr); - } - TEST_ASSERT_TRUE(seen1); - TEST_ASSERT_TRUE(seen2); + TEST_ASSERT_TRUE(seen1); + TEST_ASSERT_TRUE(seen2); - tc_task_list_free(&tasks); - TEST_ASSERT_NULL(tasks.items); + tc_task_list_free(&tasks); + TEST_ASSERT_NULL(tasks.items); + } + + { + TCUuidList uuids = tc_replica_all_task_uuids(rep); + TEST_ASSERT_NOT_NULL(uuids.items); + TEST_ASSERT_EQUAL(2, uuids.len); + + bool seen1, seen2 = false; + for (size_t i = 0; i < uuids.len; i++) { + TCUuid uuid = uuids.items[i]; + if (0 == memcmp(&uuid1, &uuid, sizeof(TCUuid))) { + seen1 = true; + } else if (0 == memcmp(&uuid2, &uuid, sizeof(TCUuid))) { + seen2 = true; + } + } + TEST_ASSERT_TRUE(seen1); + TEST_ASSERT_TRUE(seen2); + + tc_uuid_list_free(&uuids); + TEST_ASSERT_NULL(uuids.items); + } tc_replica_free(rep); } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f9e353d21..bb4ebe905 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -14,3 +14,4 @@ pub mod stringlist; pub mod task; pub mod tasklist; pub mod uuid; +pub mod uuidlist; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index b17c256a1..5119ea520 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -2,7 +2,7 @@ use crate::traits::*; use crate::util::err_to_tcstring; use crate::{ result::TCResult, status::TCStatus, string::TCString, task::TCTask, tasklist::TCTaskList, - uuid::TCUuid, + uuid::TCUuid, uuidlist::TCUuidList, }; use std::ptr::NonNull; use taskchampion::{Replica, StorageConfig}; @@ -161,7 +161,25 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList ) } -// TODO: tc_replica_all_task_uuids +/// Get a list of all uuids for tasks in the replica. +/// +/// Returns a TCUuidList with a NULL items field on error. +#[no_mangle] +pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUuidList { + wrap( + rep, + |rep| { + let uuids: Vec<_> = rep + .all_task_uuids()? + .drain(..) + .map(|uuid| TCUuid::return_val(uuid)) + .collect(); + Ok(TCUuidList::return_val(uuids)) + }, + TCUuidList::null_value(), + ) +} + // TODO: tc_replica_working_set /// Get an existing task by its UUID. diff --git a/lib/src/stringlist.rs b/lib/src/stringlist.rs index 3daf5f3cd..3ee3be48e 100644 --- a/lib/src/stringlist.rs +++ b/lib/src/stringlist.rs @@ -19,10 +19,10 @@ pub struct TCStringList { items: *const NonNull>, } -impl PointerArray for TCStringList { - type Element = TCString<'static>; +impl ValueArray for TCStringList { + type Element = NonNull>; - unsafe fn from_raw_parts(items: *const NonNull, len: usize, cap: usize) -> Self { + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { TCStringList { len, _capacity: cap, @@ -30,7 +30,7 @@ impl PointerArray for TCStringList { } } - fn into_raw_parts(self) -> (*const NonNull, usize, usize) { + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { (self.items, self.len, self._capacity) } } @@ -45,7 +45,7 @@ pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { // SAFETY: // - *tcstrings is a valid TCStringList (caller promises to treat it as read-only) let strings = unsafe { TCStringList::take_from_arg(tcstrings, TCStringList::null_value()) }; - TCStringList::drop_pointer_vector(strings); + TCStringList::drop_vector(strings); } #[cfg(test)] diff --git a/lib/src/tasklist.rs b/lib/src/tasklist.rs index eaba75ca1..c383139fe 100644 --- a/lib/src/tasklist.rs +++ b/lib/src/tasklist.rs @@ -19,10 +19,10 @@ pub struct TCTaskList { items: *const NonNull, } -impl PointerArray for TCTaskList { - type Element = TCTask; +impl ValueArray for TCTaskList { + type Element = NonNull; - unsafe fn from_raw_parts(items: *const NonNull, len: usize, cap: usize) -> Self { + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { TCTaskList { len, _capacity: cap, @@ -30,7 +30,7 @@ impl PointerArray for TCTaskList { } } - fn into_raw_parts(self) -> (*const NonNull, usize, usize) { + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { (self.items, self.len, self._capacity) } } @@ -45,7 +45,7 @@ pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { // SAFETY: // - *tctasks is a valid TCTaskList (caller promises to treat it as read-only) let tasks = unsafe { TCTaskList::take_from_arg(tctasks, TCTaskList::null_value()) }; - TCTaskList::drop_pointer_vector(tasks); + TCTaskList::drop_vector(tasks); } #[cfg(test)] diff --git a/lib/src/traits.rs b/lib/src/traits.rs index 1e4a67dc6..f0bde9e33 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -146,38 +146,100 @@ pub(crate) trait PassByPointer: Sized { } } -/// Support for arrays of objects referenced by pointer. +/// *mut P can be passed by value +impl

      PassByValue for *mut P +where + P: PassByPointer + 'static, +{ + type RustType = &'static mut P; + + unsafe fn from_ctype(self) -> Self::RustType { + // SAFETY: + // - self must be a valid *mut P (promised by caller) + // - TODO for 'static + unsafe { &mut *self } + } + + /// Convert a Rust value to a C value. + fn as_ctype(arg: Self::RustType) -> Self { + arg + } +} + +/// *const P can be passed by value +impl

      PassByValue for *const P +where + P: PassByPointer + 'static, +{ + type RustType = &'static P; + + unsafe fn from_ctype(self) -> Self::RustType { + // SAFETY: + // - self must be a valid *mut P (promised by caller) + // - TODO for 'static + unsafe { &*self } + } + + /// Convert a Rust value to a C value. + fn as_ctype(arg: Self::RustType) -> Self { + arg + } +} + +/// NonNull

      can be passed by value +impl

      PassByValue for NonNull

      +where + P: PassByPointer + 'static, +{ + type RustType = &'static mut P; + + unsafe fn from_ctype(mut self) -> Self::RustType { + // SAFETY: + // - self must be a valid *mut P (promised by caller) + // - TODO for 'static + unsafe { self.as_mut() } + } + + /// Convert a Rust value to a C value. + fn as_ctype(arg: Self::RustType) -> Self { + NonNull::new(arg).expect("value must not be NULL") + } +} + +/// Support for arrays of objects referenced by value. /// /// The underlying C type should have three fields, containing items, length, and capacity. The /// required trait functions just fetch and set these fields. The PassByValue trait will be -/// implemented automatically, converting between the C type and `Vec>`. For most -/// cases, it is only necessary to implement `tc_.._free` that first calls -/// `PassByValue::take_from_arg(arg, PointerArray::null_value())` to take the existing value and -/// replace it with the null value; then `PointerArray::drop_pointer_vector(..)` to drop the -/// resulting vector and all of the objects it points to. +/// implemented automatically, converting between the C type and `Vec`. For most cases, +/// it is only necessary to implement `tc_.._free` that first calls +/// `PassByValue::take_from_arg(arg, ValueArray::null_value())` to take the existing value and +/// replace it with the null value; then `ValueArray::drop_value_vector(..)` to drop the resulting +/// vector and all of the objects it points to. +/// +/// This can be used for objects referenced by pointer, too, with an Element type of `*const T` /// /// # Safety /// /// The C type must be documented as read-only. None of the fields may be modified, nor anything /// in the `items` array. /// -/// This class guarantees that the items pointer is non-NULL for any valid array (even when len=0), -/// and that all pointers at indexes 0..len-1 are non-NULL. -pub(crate) trait PointerArray: Sized { - type Element: 'static + PassByPointer; +/// This class guarantees that the items pointer is non-NULL for any valid array (even when len=0). +// TODO: rename Array +pub(crate) trait ValueArray: Sized { + type Element: PassByValue; - /// Create a new PointerArray from the given items, len, and capacity. + /// Create a new ValueArray from the given items, len, and capacity. /// /// # Safety /// /// The arguments must either: /// - be NULL, 0, and 0, respectively; or /// - be valid for Vec::from_raw_parts - unsafe fn from_raw_parts(items: *const NonNull, len: usize, cap: usize) -> Self; + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self; /// Get the items, len, and capacity (in that order) for this instance. These must be /// precisely the same values passed tearlier to `from_raw_parts`. - fn into_raw_parts(self) -> (*const NonNull, usize, usize); + fn into_raw_parts(self) -> (*const Self::Element, usize, usize); /// Generate a NULL value. By default this is a NULL items pointer with zero length and /// capacity. @@ -187,36 +249,32 @@ pub(crate) trait PointerArray: Sized { unsafe { Self::from_raw_parts(std::ptr::null(), 0, 0) } } - /// Drop a vector of element pointers. This is a convenience function for implementing + /// Drop a vector of elements. This is a convenience function for implementing /// tc_.._free functions. - fn drop_pointer_vector(mut vec: Vec>) { + fn drop_vector(mut vec: Vec) { // first, drop each of the elements in turn - for p in vec.drain(..) { - // SAFETY: - // - p is not NULL (NonNull) - // - p was generated by Rust (true for all arrays) - // - p was not modified (all arrays are immutable from C) - // - caller will not use this pointer again (promised by caller; p has been drain'd from - // the vector) - drop(unsafe { PassByPointer::take_from_arg(p.as_ptr()) }); + for e in vec.drain(..) { + // SAFETY: e is a valid Element (caller promisd not to change it) + drop(unsafe { PassByValue::from_arg(e) }); } + // then drop the vector drop(vec); } } impl PassByValue for A where - A: PointerArray, + A: ValueArray, { - type RustType = Vec>; + type RustType = Vec; unsafe fn from_ctype(self) -> Self::RustType { let (items, len, cap) = self.into_raw_parts(); debug_assert!(!items.is_null()); // SAFETY: - // - PointerArray::from_raw_parts requires that items, len, and cap be valid for + // - ValueArray::from_raw_parts requires that items, len, and cap be valid for // Vec::from_raw_parts if not NULL, and they are not NULL (as promised by caller) - // - PointerArray::into_raw_parts returns precisely the values passed to from_raw_parts. + // - ValueArray::into_raw_parts returns precisely the values passed to from_raw_parts. // - those parts are passed to Vec::from_raw_parts here. unsafe { Vec::from_raw_parts(items as *mut _, len, cap) } } diff --git a/lib/src/uuidlist.rs b/lib/src/uuidlist.rs new file mode 100644 index 000000000..862ece88e --- /dev/null +++ b/lib/src/uuidlist.rs @@ -0,0 +1,70 @@ +use crate::traits::*; +use crate::uuid::TCUuid; + +/// TCUuidList represents a list of uuids. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCUuidList { + /// number of uuids in items + len: libc::size_t, + + /// total size of items (internal use only) + _capacity: libc::size_t, + + /// array of uuids. these remain owned by the TCUuidList instance and will be freed by + /// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList. + items: *const TCUuid, +} + +impl ValueArray for TCUuidList { + type Element = TCUuid; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCUuidList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} + +/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after +/// this call. +/// +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. +#[no_mangle] +pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { + debug_assert!(!tcuuids.is_null()); + // SAFETY: + // - *tcuuids is a valid TCUuidList (caller promises to treat it as read-only) + let uuids = unsafe { TCUuidList::take_from_arg(tcuuids, TCUuidList::null_value()) }; + TCUuidList::drop_vector(uuids); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_array_has_non_null_pointer() { + let tcuuids = TCUuidList::return_val(Vec::new()); + assert!(!tcuuids.items.is_null()); + assert_eq!(tcuuids.len, 0); + assert_eq!(tcuuids._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tcuuids = TCUuidList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_uuid_list_free(&mut tcuuids) }; + assert!(tcuuids.items.is_null()); + assert_eq!(tcuuids.len, 0); + assert_eq!(tcuuids._capacity, 0); + } +} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index e59a9c59d..ed8321fd7 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -148,6 +148,27 @@ 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. + */ + const struct TCUuid *items; +} TCUuidList; + /** * TCStringList represents a list of strings. * @@ -193,6 +214,13 @@ struct TCReplica *tc_replica_new_on_disk(struct TCString *path, struct TCString */ 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. + */ +struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); + /** * Get an existing task by its UUID. * @@ -498,6 +526,14 @@ struct TCString *tc_uuid_to_str(struct TCUuid tcuuid); */ 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); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus From 7a473d0eda76884974db6e28a07b69653ed9d2e3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 10 Feb 2022 01:01:02 +0000 Subject: [PATCH 489/548] simplify imports --- lib/src/lib.rs | 12 ++++++++++++ lib/src/replica.rs | 5 +---- lib/src/stringlist.rs | 2 +- lib/src/task.rs | 5 +---- lib/src/tasklist.rs | 2 +- lib/src/uuid.rs | 2 +- lib/src/uuidlist.rs | 2 +- 7 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index bb4ebe905..b1456e73b 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -15,3 +15,15 @@ pub mod task; pub mod tasklist; pub mod uuid; pub mod uuidlist; + +pub(crate) mod types { + pub(crate) use crate::replica::TCReplica; + pub(crate) use crate::result::TCResult; + pub(crate) use crate::status::TCStatus; + pub(crate) use crate::string::TCString; + pub(crate) use crate::stringlist::TCStringList; + pub(crate) use crate::task::TCTask; + pub(crate) use crate::tasklist::TCTaskList; + pub(crate) use crate::uuid::TCUuid; + pub(crate) use crate::uuidlist::TCUuidList; +} diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 5119ea520..619dc2c46 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,9 +1,6 @@ use crate::traits::*; +use crate::types::*; use crate::util::err_to_tcstring; -use crate::{ - result::TCResult, status::TCStatus, string::TCString, task::TCTask, tasklist::TCTaskList, - uuid::TCUuid, uuidlist::TCUuidList, -}; use std::ptr::NonNull; use taskchampion::{Replica, StorageConfig}; diff --git a/lib/src/stringlist.rs b/lib/src/stringlist.rs index 3ee3be48e..b0d3ea8d4 100644 --- a/lib/src/stringlist.rs +++ b/lib/src/stringlist.rs @@ -1,5 +1,5 @@ -use crate::string::TCString; use crate::traits::*; +use crate::types::*; use std::ptr::NonNull; /// TCStringList represents a list of strings. diff --git a/lib/src/task.rs b/lib/src/task.rs index 50acf04c8..8f4055a8a 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,9 +1,6 @@ use crate::traits::*; +use crate::types::*; use crate::util::err_to_tcstring; -use crate::{ - replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, - stringlist::TCStringList, uuid::TCUuid, -}; use chrono::{DateTime, TimeZone, Utc}; use std::convert::TryFrom; use std::ops::Deref; diff --git a/lib/src/tasklist.rs b/lib/src/tasklist.rs index c383139fe..a3cdf536a 100644 --- a/lib/src/tasklist.rs +++ b/lib/src/tasklist.rs @@ -1,5 +1,5 @@ -use crate::task::TCTask; use crate::traits::*; +use crate::types::*; use std::ptr::NonNull; /// TCTaskList represents a list of tasks. diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 415e9501a..63be3823c 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -1,5 +1,5 @@ use crate::traits::*; -use crate::{result::TCResult, string::TCString}; +use crate::types::*; use libc; use taskchampion::Uuid; diff --git a/lib/src/uuidlist.rs b/lib/src/uuidlist.rs index 862ece88e..4b6684299 100644 --- a/lib/src/uuidlist.rs +++ b/lib/src/uuidlist.rs @@ -1,5 +1,5 @@ use crate::traits::*; -use crate::uuid::TCUuid; +use crate::types::*; /// TCUuidList represents a list of uuids. /// From 1c734851ae3183eced25773340015adfa14286f0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 10 Feb 2022 01:10:40 +0000 Subject: [PATCH 490/548] safety notes for new types --- lib/src/stringlist.rs | 2 +- lib/src/tasklist.rs | 2 +- lib/src/traits.rs | 51 ++++++++++++++++++++++--------------------- lib/src/uuidlist.rs | 2 +- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/src/stringlist.rs b/lib/src/stringlist.rs index b0d3ea8d4..c4fe8d0f7 100644 --- a/lib/src/stringlist.rs +++ b/lib/src/stringlist.rs @@ -19,7 +19,7 @@ pub struct TCStringList { items: *const NonNull>, } -impl ValueArray for TCStringList { +impl CArray for TCStringList { type Element = NonNull>; unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { diff --git a/lib/src/tasklist.rs b/lib/src/tasklist.rs index a3cdf536a..8905384aa 100644 --- a/lib/src/tasklist.rs +++ b/lib/src/tasklist.rs @@ -19,7 +19,7 @@ pub struct TCTaskList { items: *const NonNull, } -impl ValueArray for TCTaskList { +impl CArray for TCTaskList { type Element = NonNull; unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { diff --git a/lib/src/traits.rs b/lib/src/traits.rs index f0bde9e33..007d60ac3 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -23,8 +23,8 @@ pub(crate) trait PassByValue: Sized { /// /// # Safety /// - /// `self` must be a valid CType. This is typically ensured either by requiring that C - /// code not modify it, or by defining the valid values in C comments. + /// `self` must be a valid instance of Self. This is typically ensured either by requiring + /// that C code not modify it, or by defining the valid values in C comments. unsafe fn from_arg(arg: Self) -> Self::RustType { // SAFETY: // - arg is a valid CType (promised by caller) @@ -146,7 +146,13 @@ pub(crate) trait PassByPointer: Sized { } } -/// *mut P can be passed by value +/// *mut P can be passed by value. +/// +/// # Safety +/// +/// The 'static bound means that the pointer is treated as an "owning" pointer, +/// and must not be copied (leading to a double-free) or dropped without dropping +/// its contents (a leak). impl

      PassByValue for *mut P where P: PassByPointer + 'static, @@ -154,9 +160,7 @@ where type RustType = &'static mut P; unsafe fn from_ctype(self) -> Self::RustType { - // SAFETY: - // - self must be a valid *mut P (promised by caller) - // - TODO for 'static + // SAFETY: self must be a valid *mut P (promised by caller) unsafe { &mut *self } } @@ -166,7 +170,7 @@ where } } -/// *const P can be passed by value +/// *const P can be passed by value. See the implementation for *mut P. impl

      PassByValue for *const P where P: PassByPointer + 'static, @@ -174,9 +178,7 @@ where type RustType = &'static P; unsafe fn from_ctype(self) -> Self::RustType { - // SAFETY: - // - self must be a valid *mut P (promised by caller) - // - TODO for 'static + // SAFETY: self must be a valid *const P (promised by caller) unsafe { &*self } } @@ -186,7 +188,7 @@ where } } -/// NonNull

      can be passed by value +/// NonNull

      can be passed by value. See the implementation for NonNull

      . impl

      PassByValue for NonNull

      where P: PassByPointer + 'static, @@ -194,9 +196,7 @@ where type RustType = &'static mut P; unsafe fn from_ctype(mut self) -> Self::RustType { - // SAFETY: - // - self must be a valid *mut P (promised by caller) - // - TODO for 'static + // SAFETY: self must be a valid NonNull

      (promised by caller) unsafe { self.as_mut() } } @@ -206,14 +206,14 @@ where } } -/// Support for arrays of objects referenced by value. +/// Support for C arrays of objects referenced by value. /// /// The underlying C type should have three fields, containing items, length, and capacity. The /// required trait functions just fetch and set these fields. The PassByValue trait will be /// implemented automatically, converting between the C type and `Vec`. For most cases, /// it is only necessary to implement `tc_.._free` that first calls -/// `PassByValue::take_from_arg(arg, ValueArray::null_value())` to take the existing value and -/// replace it with the null value; then `ValueArray::drop_value_vector(..)` to drop the resulting +/// `PassByValue::take_from_arg(arg, CArray::null_value())` to take the existing value and +/// replace it with the null value; then `CArray::drop_value_vector(..)` to drop the resulting /// vector and all of the objects it points to. /// /// This can be used for objects referenced by pointer, too, with an Element type of `*const T` @@ -224,11 +224,10 @@ where /// in the `items` array. /// /// This class guarantees that the items pointer is non-NULL for any valid array (even when len=0). -// TODO: rename Array -pub(crate) trait ValueArray: Sized { +pub(crate) trait CArray: Sized { type Element: PassByValue; - /// Create a new ValueArray from the given items, len, and capacity. + /// Create a new CArray from the given items, len, and capacity. /// /// # Safety /// @@ -254,8 +253,10 @@ pub(crate) trait ValueArray: Sized { fn drop_vector(mut vec: Vec) { // first, drop each of the elements in turn for e in vec.drain(..) { - // SAFETY: e is a valid Element (caller promisd not to change it) - drop(unsafe { PassByValue::from_arg(e) }); + // SAFETY: + // - e is a valid Element (caller promisd not to change it) + // - Vec::drain has invalidated this entry (value is owned) + drop(unsafe { PassByValue::from_ctype(e) }); } // then drop the vector drop(vec); @@ -264,7 +265,7 @@ pub(crate) trait ValueArray: Sized { impl PassByValue for A where - A: ValueArray, + A: CArray, { type RustType = Vec; @@ -272,9 +273,9 @@ where let (items, len, cap) = self.into_raw_parts(); debug_assert!(!items.is_null()); // SAFETY: - // - ValueArray::from_raw_parts requires that items, len, and cap be valid for + // - CArray::from_raw_parts requires that items, len, and cap be valid for // Vec::from_raw_parts if not NULL, and they are not NULL (as promised by caller) - // - ValueArray::into_raw_parts returns precisely the values passed to from_raw_parts. + // - CArray::into_raw_parts returns precisely the values passed to from_raw_parts. // - those parts are passed to Vec::from_raw_parts here. unsafe { Vec::from_raw_parts(items as *mut _, len, cap) } } diff --git a/lib/src/uuidlist.rs b/lib/src/uuidlist.rs index 4b6684299..139bb8bd3 100644 --- a/lib/src/uuidlist.rs +++ b/lib/src/uuidlist.rs @@ -17,7 +17,7 @@ pub struct TCUuidList { items: *const TCUuid, } -impl ValueArray for TCUuidList { +impl CArray for TCUuidList { type Element = TCUuid; unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { From b01285d7807e966967b380c543a8f51ca0649c36 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 10 Feb 2022 01:18:50 +0000 Subject: [PATCH 491/548] add some simple replica functions --- .../src/bindings_tests/replica.c | 20 ++++++++++ lib/src/replica.rs | 37 +++++++++++++++++-- lib/taskchampion.h | 15 ++++++++ taskchampion/src/replica.rs | 2 +- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 14084ab00..7e598695b 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -31,6 +31,24 @@ static void test_replica_undo_empty(void) { tc_replica_free(rep); } +// adding an undo point succeeds +static void test_replica_add_undo_point(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_add_undo_point(rep, true)); + TEST_ASSERT_NULL(tc_replica_error(rep)); + tc_replica_free(rep); +} + +// rebuilding working set succeeds +static void test_replica_rebuild_working_set(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true)); + TEST_ASSERT_NULL(tc_replica_error(rep)); + tc_replica_free(rep); +} + // When tc_replica_undo is passed NULL for undone_out, it still succeeds static void test_replica_undo_empty_null_undone_out(void) { TCReplica *rep = tc_replica_new_in_memory(); @@ -190,6 +208,8 @@ int replica_tests(void) { RUN_TEST(test_replica_creation); RUN_TEST(test_replica_creation_disk); RUN_TEST(test_replica_undo_empty); + RUN_TEST(test_replica_add_undo_point); + RUN_TEST(test_replica_rebuild_working_set); RUN_TEST(test_replica_undo_empty_null_undone_out); RUN_TEST(test_replica_task_creation); RUN_TEST(test_replica_all_tasks); diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 619dc2c46..b946efb34 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -271,6 +271,40 @@ pub unsafe extern "C" fn tc_replica_undo<'a>( ) } +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_replica_add_undo_point(rep: *mut TCReplica, force: bool) -> TCResult { + wrap( + rep, + |rep| { + rep.add_undo_point(force)?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_replica_rebuild_working_set( + rep: *mut TCReplica, + renumber: bool, +) -> TCResult { + wrap( + rep, + |rep| { + rep.rebuild_working_set(renumber)?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + /// Get the latest error for a replica, or NULL if the last operation succeeded. Subsequent calls /// to this function will return NULL. The rep pointer must not be NULL. The caller must free the /// returned string. @@ -297,6 +331,3 @@ pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) { } drop(replica); } - -// TODO: tc_replica_rebuild_working_set -// TODO: tc_replica_add_undo_point diff --git a/lib/taskchampion.h b/lib/taskchampion.h index ed8321fd7..0e5753f67 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -253,6 +253,21 @@ struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TC */ TCResult tc_replica_undo(struct TCReplica *rep, int32_t *undone_out); +/** + * 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 NULL if the last operation succeeded. Subsequent calls * to this function will return NULL. The rep pointer must not be NULL. The caller must free the diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 2dd1e4887..79938423f 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -177,7 +177,7 @@ impl Replica { /// 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 laready been created by this Replica, and may be useful when a Replica + /// 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. pub fn add_undo_point(&mut self, force: bool) -> anyhow::Result<()> { if force || !self.added_undo_point { From 7996a989089401c3573bddd9b64d8f996a2d88ca Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 11 Feb 2022 23:54:52 +0000 Subject: [PATCH 492/548] add annotation support --- integration-tests/src/bindings_tests/task.c | 54 +++++++++ lib/src/annotation.rs | 127 ++++++++++++++++++++ lib/src/lib.rs | 2 + lib/src/string.rs | 12 ++ lib/src/task.rs | 49 +++++++- lib/taskchampion.h | 67 +++++++++++ 6 files changed, 308 insertions(+), 3 deletions(-) create mode 100644 lib/src/annotation.rs diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 5a0214ce3..0eb1e45c0 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -333,6 +333,59 @@ static void test_task_get_tags(void) { tc_replica_free(rep); } +// annotation manipulation (add, remove, list, free) +static void test_task_annotations(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + TCAnnotationList anns = tc_task_get_annotations(task); + TEST_ASSERT_EQUAL(0, anns.len); + TEST_ASSERT_NOT_NULL(anns.items); + tc_annotation_list_free(&anns); + + tc_task_to_mut(task, rep); + + TCAnnotation ann; + + ann.entry = 1644623411; + ann.description = tc_string_borrow("ann1"); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann)); + TEST_ASSERT_NULL(ann.description); + + ann.entry = 1644623422; + ann.description = tc_string_borrow("ann2"); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann)); + TEST_ASSERT_NULL(ann.description); + + anns = tc_task_get_annotations(task); + + int found1 = false, found2 = false; + for (size_t i = 0; i < anns.len; i++) { + if (0 == strcmp("ann1", tc_string_content(anns.items[i].description))) { + TEST_ASSERT_EQUAL(anns.items[i].entry, 1644623411); + found1 = true; + } + if (0 == strcmp("ann2", tc_string_content(anns.items[i].description))) { + TEST_ASSERT_EQUAL(anns.items[i].entry, 1644623422); + found2 = true; + } + } + TEST_ASSERT_TRUE(found1); + TEST_ASSERT_TRUE(found2); + + tc_annotation_list_free(&anns); + TEST_ASSERT_NULL(anns.items); + + tc_task_free(task); + tc_replica_free(rep); +} + int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. @@ -347,5 +400,6 @@ int task_tests(void) { RUN_TEST(test_task_done_and_delete); RUN_TEST(test_task_add_remove_has_tag); RUN_TEST(test_task_get_tags); + RUN_TEST(test_task_annotations); return UNITY_END(); } diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs new file mode 100644 index 000000000..8c403327b --- /dev/null +++ b/lib/src/annotation.rs @@ -0,0 +1,127 @@ +use crate::traits::*; +use crate::types::*; +use chrono::prelude::*; +use taskchampion::Annotation; + +/// TCAnnotation contains the details of an annotation. +#[repr(C)] +pub struct TCAnnotation { + /// Time the annotation was made, as a UNIX epoch timestamp + pub entry: i64, + /// Content of the annotation + pub description: *mut TCString<'static>, +} + +impl PassByValue for TCAnnotation { + type RustType = Annotation; + + unsafe fn from_ctype(self) -> Annotation { + let entry = Utc.timestamp(self.entry, 0); + // SAFETY: + // - self is owned, so we can take ownership of this TCString + // - self.description was created from a String so has valid UTF-8 + // - caller did not change it (promised by caller) + let description = unsafe { TCString::take_from_arg(self.description) } + .into_string() + .unwrap(); + Annotation { entry, description } + } + + fn as_ctype(arg: Annotation) -> Self { + let description: TCString = arg.description.into(); + TCAnnotation { + entry: arg.entry.timestamp(), + // SAFETY: caller will later free this value via tc_annotation_free + description: unsafe { description.return_val() }, + } + } +} + +impl Default for TCAnnotation { + fn default() -> Self { + TCAnnotation { + entry: 0, + description: std::ptr::null_mut(), + } + } +} + +/// TCAnnotationList represents a list of annotations. +/// +/// The content of this struct must be treated as read-only. +#[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, + + /// 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: *const TCAnnotation, +} + +impl CArray for TCAnnotationList { + type Element = TCAnnotation; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCAnnotationList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} + +/// Free a TCAnnotation instance. The instance, and the TCString it contains, must not be used +/// after this call. +#[no_mangle] +pub unsafe extern "C" fn tc_annotation_free(tcann: *mut TCAnnotation) { + debug_assert!(!tcann.is_null()); + // SAFETY: + // - *tcann is a valid TCAnnotation (caller promises to treat it as read-only) + let annotation = unsafe { TCAnnotation::take_from_arg(tcann, TCAnnotation::default()) }; + drop(annotation); +} + +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_annotation_list_free(tcanns: *mut TCAnnotationList) { + debug_assert!(!tcanns.is_null()); + // SAFETY: + // - *tcanns is a valid TCAnnotationList (caller promises to treat it as read-only) + let annotations = + unsafe { TCAnnotationList::take_from_arg(tcanns, TCAnnotationList::null_value()) }; + TCAnnotationList::drop_vector(annotations); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_array_has_non_null_pointer() { + let tcanns = TCAnnotationList::return_val(Vec::new()); + assert!(!tcanns.items.is_null()); + assert_eq!(tcanns.len, 0); + assert_eq!(tcanns._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tcanns = TCAnnotationList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_annotation_list_free(&mut tcanns) }; + assert!(tcanns.items.is_null()); + assert_eq!(tcanns.len, 0); + assert_eq!(tcanns._capacity, 0); + } +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b1456e73b..f5c43439c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -5,6 +5,7 @@ mod traits; mod util; +pub mod annotation; pub mod atomic; pub mod replica; pub mod result; @@ -17,6 +18,7 @@ pub mod uuid; pub mod uuidlist; pub(crate) mod types { + pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList}; pub(crate) use crate::replica::TCReplica; pub(crate) use crate::result::TCResult; pub(crate) use crate::status::TCStatus; diff --git a/lib/src/string.rs b/lib/src/string.rs index da35e1220..257d105eb 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -69,6 +69,18 @@ impl<'a> TCString<'a> { } } + /// Consume this TCString and return an equivalent String, or an error if not + /// valid UTF-8. In the error condition, the original data is lost. + pub(crate) fn into_string(self) -> Result { + match self { + TCString::CString(cstring) => cstring.into_string().map_err(|e| e.utf8_error()), + TCString::CStr(cstr) => cstr.to_str().map(|s| s.to_string()), + TCString::String(string) => Ok(string), + TCString::InvalidUtf8(e, _) => Err(e), + TCString::None => unreachable!(), + } + } + fn as_bytes(&self) -> &[u8] { match self { TCString::CString(cstring) => cstring.as_bytes(), diff --git a/lib/src/task.rs b/lib/src/task.rs index 8f4055a8a..2f7da32ef 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -305,7 +305,21 @@ pub unsafe extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStringList }) } -// TODO: tc_task_get_annotations +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_task_get_annotations<'a>(task: *mut TCTask) -> TCAnnotationList { + wrap(task, |task| { + let vec: Vec = task + .get_annotations() + .map(|a| TCAnnotation::as_ctype(a)) + .collect(); + TCAnnotationList::return_val(vec) + }) +} + // TODO: tc_task_get_uda // TODO: tc_task_get_udas // TODO: tc_task_get_legacy_uda @@ -471,8 +485,37 @@ pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCStrin ) } -// TODO: tc_task_add_annotation -// TODO: tc_task_remove_annotation +/// Add an annotation to a mutable task. +#[no_mangle] +pub unsafe extern "C" fn tc_task_add_annotation( + task: *mut TCTask, + annotation: *mut TCAnnotation, +) -> TCResult { + // SAFETY: see TCAnnotation docstring + let ann = unsafe { TCAnnotation::take_from_arg(annotation, TCAnnotation::default()) }; + wrap_mut( + task, + |task| { + task.add_annotation(ann)?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + +/// Remove an annotation from a mutable task. +#[no_mangle] +pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64) -> TCResult { + wrap_mut( + task, + |task| { + task.remove_annotation(Utc.timestamp(entry, 0))?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + // TODO: tc_task_set_uda // TODO: tc_task_remove_uda // TODO: tc_task_set_legacy_uda diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 0e5753f67..a6ffc7644 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -117,6 +117,41 @@ typedef struct TCString TCString; */ typedef struct TCTask TCTask; +/** + * TCAnnotation contains the details of an annotation. + */ +typedef struct TCAnnotation { + /** + * Time the annotation was made, as a UNIX epoch timestamp + */ + int64_t entry; + /** + * Content of the annotation + */ + struct TCString *description; +} TCAnnotation; + +/** + * 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 + */ + 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. + */ + const struct TCAnnotation *items; +} TCAnnotationList; + /** * TCTaskList represents a list of tasks. * @@ -195,6 +230,20 @@ typedef struct TCStringList { extern "C" { #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); + +/** + * 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 TCReplica with an in-memory database. The contents of the database will be * lost when it is freed. @@ -434,6 +483,14 @@ bool tc_task_has_tag(struct TCTask *task, struct TCString *tag); */ struct TCStringList tc_task_get_tags(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. + */ +struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); + /** * Set a mutable task's status. */ @@ -490,6 +547,16 @@ TCResult tc_task_add_tag(struct TCTask *task, struct TCString *tag); */ TCResult tc_task_remove_tag(struct TCTask *task, struct TCString *tag); +/** + * Add an annotation to a mutable task. + */ +TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); + +/** + * Remove an annotation from a mutable task. + */ +TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); + /** * Get the latest error for a task, or NULL 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 From af51e0382aa1a9c77d84f0afedac2a751b495409 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 11 Feb 2022 23:59:22 +0000 Subject: [PATCH 493/548] implement lists in the same files as singular data --- lib/src/lib.rs | 12 ++----- lib/src/string.rs | 65 +++++++++++++++++++++++++++++++++++++ lib/src/stringlist.rs | 72 ----------------------------------------- lib/src/task.rs | 69 ++++++++++++++++++++++++++++++++++++++++ lib/src/tasklist.rs | 72 ----------------------------------------- lib/src/uuid.rs | 74 +++++++++++++++++++++++++++++++++++++++++-- lib/src/uuidlist.rs | 70 ---------------------------------------- 7 files changed, 208 insertions(+), 226 deletions(-) delete mode 100644 lib/src/stringlist.rs delete mode 100644 lib/src/tasklist.rs delete mode 100644 lib/src/uuidlist.rs diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f5c43439c..41fa16005 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -11,21 +11,15 @@ pub mod replica; pub mod result; pub mod status; pub mod string; -pub mod stringlist; pub mod task; -pub mod tasklist; pub mod uuid; -pub mod uuidlist; pub(crate) mod types { pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList}; pub(crate) use crate::replica::TCReplica; pub(crate) use crate::result::TCResult; pub(crate) use crate::status::TCStatus; - pub(crate) use crate::string::TCString; - pub(crate) use crate::stringlist::TCStringList; - pub(crate) use crate::task::TCTask; - pub(crate) use crate::tasklist::TCTaskList; - pub(crate) use crate::uuid::TCUuid; - pub(crate) use crate::uuidlist::TCUuidList; + pub(crate) use crate::string::{TCString, TCStringList}; + pub(crate) use crate::task::{TCTask, TCTaskList}; + pub(crate) use crate::uuid::{TCUuid, TCUuidList}; } diff --git a/lib/src/string.rs b/lib/src/string.rs index 257d105eb..d9fc90507 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -2,6 +2,7 @@ use crate::traits::*; use std::ffi::{CStr, CString, OsStr}; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; +use std::ptr::NonNull; use std::str::Utf8Error; /// TCString supports passing strings into and out of the TaskChampion API. @@ -136,6 +137,39 @@ impl<'a> From<&str> for TCString<'static> { } } +/// TCStringList represents a list of strings. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCStringList { + /// number of strings in items + len: libc::size_t, + + /// total size of items (internal use only) + _capacity: libc::size_t, + + /// TCStringList representing each string. these remain owned by the TCStringList instance and will + /// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the + /// *TCStringList at indexes 0..len-1 are not NULL. + items: *const NonNull>, +} + +impl CArray for TCStringList { + type Element = NonNull>; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCStringList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} + /// Create a new TCString referencing the given C string. The C string must remain valid and /// unchanged until after the TCString is freed. It's typically easiest to ensure this by using a /// static string. @@ -281,11 +315,42 @@ pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { drop(unsafe { TCString::take_from_arg(tcstring) }); } +/// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after +/// this call. +/// +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. +#[no_mangle] +pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { + debug_assert!(!tcstrings.is_null()); + // SAFETY: + // - *tcstrings is a valid TCStringList (caller promises to treat it as read-only) + let strings = unsafe { TCStringList::take_from_arg(tcstrings, TCStringList::null_value()) }; + TCStringList::drop_vector(strings); +} + #[cfg(test)] mod test { use super::*; use pretty_assertions::assert_eq; + #[test] + fn empty_array_has_non_null_pointer() { + let tcstrings = TCStringList::return_val(Vec::new()); + assert!(!tcstrings.items.is_null()); + assert_eq!(tcstrings.len, 0); + assert_eq!(tcstrings._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tcstrings = TCStringList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_string_list_free(&mut tcstrings) }; + assert!(tcstrings.items.is_null()); + assert_eq!(tcstrings.len, 0); + assert_eq!(tcstrings._capacity, 0); + } + const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28"; fn make_cstring() -> TCString<'static> { diff --git a/lib/src/stringlist.rs b/lib/src/stringlist.rs deleted file mode 100644 index c4fe8d0f7..000000000 --- a/lib/src/stringlist.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use std::ptr::NonNull; - -/// TCStringList represents a list of strings. -/// -/// The content of this struct must be treated as read-only. -#[repr(C)] -pub struct TCStringList { - /// number of strings in items - len: libc::size_t, - - /// total size of items (internal use only) - _capacity: libc::size_t, - - /// TCStringList representing each string. these remain owned by the TCStringList instance and will - /// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the - /// *TCStringList at indexes 0..len-1 are not NULL. - items: *const NonNull>, -} - -impl CArray for TCStringList { - type Element = NonNull>; - - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { - TCStringList { - len, - _capacity: cap, - items, - } - } - - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -/// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. -#[no_mangle] -pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { - debug_assert!(!tcstrings.is_null()); - // SAFETY: - // - *tcstrings is a valid TCStringList (caller promises to treat it as read-only) - let strings = unsafe { TCStringList::take_from_arg(tcstrings, TCStringList::null_value()) }; - TCStringList::drop_vector(strings); -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_array_has_non_null_pointer() { - let tcstrings = TCStringList::return_val(Vec::new()); - assert!(!tcstrings.items.is_null()); - assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcstrings = TCStringList::return_val(Vec::new()); - // SAFETY: testing expected behavior - unsafe { tc_string_list_free(&mut tcstrings) }; - assert!(tcstrings.items.is_null()); - assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings._capacity, 0); - } -} diff --git a/lib/src/task.rs b/lib/src/task.rs index 2f7da32ef..b56a71498 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -171,6 +171,39 @@ fn to_datetime(time: libc::time_t) -> Option> { } } +/// TCTaskList represents a list of tasks. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCTaskList { + /// number of tasks in items + len: libc::size_t, + + /// total size of items (internal use only) + _capacity: libc::size_t, + + /// array of pointers representing each task. these remain owned by the TCTaskList instance and + /// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList, + /// and the *TCTaskList at indexes 0..len-1 are not NULL. + items: *const NonNull, +} + +impl CArray for TCTaskList { + type Element = NonNull; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCTaskList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} + /// Convert an immutable task into a mutable task. /// /// The task must not be NULL. It is modified in-place, and becomes mutable. @@ -553,3 +586,39 @@ pub unsafe extern "C" fn tc_task_free<'a>(task: *mut TCTask) { drop(tctask); } + +/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after +/// this call. +/// +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. +#[no_mangle] +pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { + debug_assert!(!tctasks.is_null()); + // SAFETY: + // - *tctasks is a valid TCTaskList (caller promises to treat it as read-only) + let tasks = unsafe { TCTaskList::take_from_arg(tctasks, TCTaskList::null_value()) }; + TCTaskList::drop_vector(tasks); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_array_has_non_null_pointer() { + let tctasks = TCTaskList::return_val(Vec::new()); + assert!(!tctasks.items.is_null()); + assert_eq!(tctasks.len, 0); + assert_eq!(tctasks._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tctasks = TCTaskList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_task_list_free(&mut tctasks) }; + assert!(tctasks.items.is_null()); + assert_eq!(tctasks.len, 0); + assert_eq!(tctasks._capacity, 0); + } +} diff --git a/lib/src/tasklist.rs b/lib/src/tasklist.rs deleted file mode 100644 index 8905384aa..000000000 --- a/lib/src/tasklist.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use std::ptr::NonNull; - -/// TCTaskList represents a list of tasks. -/// -/// The content of this struct must be treated as read-only. -#[repr(C)] -pub struct TCTaskList { - /// number of tasks in items - len: libc::size_t, - - /// total size of items (internal use only) - _capacity: libc::size_t, - - /// array of pointers representing each task. these remain owned by the TCTaskList instance and - /// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList, - /// and the *TCTaskList at indexes 0..len-1 are not NULL. - items: *const NonNull, -} - -impl CArray for TCTaskList { - type Element = NonNull; - - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { - TCTaskList { - len, - _capacity: cap, - items, - } - } - - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. -#[no_mangle] -pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { - debug_assert!(!tctasks.is_null()); - // SAFETY: - // - *tctasks is a valid TCTaskList (caller promises to treat it as read-only) - let tasks = unsafe { TCTaskList::take_from_arg(tctasks, TCTaskList::null_value()) }; - TCTaskList::drop_vector(tasks); -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_array_has_non_null_pointer() { - let tctasks = TCTaskList::return_val(Vec::new()); - assert!(!tctasks.items.is_null()); - assert_eq!(tctasks.len, 0); - assert_eq!(tctasks._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tctasks = TCTaskList::return_val(Vec::new()); - // SAFETY: testing expected behavior - unsafe { tc_task_list_free(&mut tctasks) }; - assert!(tctasks.items.is_null()); - assert_eq!(tctasks.len, 0); - assert_eq!(tctasks._capacity, 0); - } -} diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 63be3823c..d140a9eed 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -3,6 +3,10 @@ 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; + /// 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. /// @@ -36,9 +40,37 @@ pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid { TCUuid::return_val(Uuid::nil()) } -// 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; +/// TCUuidList represents a list of uuids. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCUuidList { + /// number of uuids in items + len: libc::size_t, + + /// total size of items (internal use only) + _capacity: libc::size_t, + + /// array of uuids. these remain owned by the TCUuidList instance and will be freed by + /// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList. + items: *const TCUuid, +} + +impl CArray for TCUuidList { + type Element = TCUuid; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCUuidList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} /// 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. @@ -91,3 +123,39 @@ pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut T } TCResult::Error } + +/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after +/// this call. +/// +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. +#[no_mangle] +pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { + debug_assert!(!tcuuids.is_null()); + // SAFETY: + // - *tcuuids is a valid TCUuidList (caller promises to treat it as read-only) + let uuids = unsafe { TCUuidList::take_from_arg(tcuuids, TCUuidList::null_value()) }; + TCUuidList::drop_vector(uuids); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_array_has_non_null_pointer() { + let tcuuids = TCUuidList::return_val(Vec::new()); + assert!(!tcuuids.items.is_null()); + assert_eq!(tcuuids.len, 0); + assert_eq!(tcuuids._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tcuuids = TCUuidList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_uuid_list_free(&mut tcuuids) }; + assert!(tcuuids.items.is_null()); + assert_eq!(tcuuids.len, 0); + assert_eq!(tcuuids._capacity, 0); + } +} diff --git a/lib/src/uuidlist.rs b/lib/src/uuidlist.rs deleted file mode 100644 index 139bb8bd3..000000000 --- a/lib/src/uuidlist.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::traits::*; -use crate::types::*; - -/// TCUuidList represents a list of uuids. -/// -/// The content of this struct must be treated as read-only. -#[repr(C)] -pub struct TCUuidList { - /// number of uuids in items - len: libc::size_t, - - /// total size of items (internal use only) - _capacity: libc::size_t, - - /// array of uuids. these remain owned by the TCUuidList instance and will be freed by - /// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList. - items: *const TCUuid, -} - -impl CArray for TCUuidList { - type Element = TCUuid; - - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { - TCUuidList { - len, - _capacity: cap, - items, - } - } - - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { - debug_assert!(!tcuuids.is_null()); - // SAFETY: - // - *tcuuids is a valid TCUuidList (caller promises to treat it as read-only) - let uuids = unsafe { TCUuidList::take_from_arg(tcuuids, TCUuidList::null_value()) }; - TCUuidList::drop_vector(uuids); -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_array_has_non_null_pointer() { - let tcuuids = TCUuidList::return_val(Vec::new()); - assert!(!tcuuids.items.is_null()); - assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcuuids = TCUuidList::return_val(Vec::new()); - // SAFETY: testing expected behavior - unsafe { tc_uuid_list_free(&mut tcuuids) }; - assert!(tcuuids.items.is_null()); - assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids._capacity, 0); - } -} From 7ebdaa761cd32b0f0d49a2bb16493fa78ef2d5a0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 12 Feb 2022 00:18:01 +0000 Subject: [PATCH 494/548] treat libc::time_t as a PassByValue --- lib/src/annotation.rs | 20 +++++++++++--------- lib/src/atomic.rs | 19 +++++++++++++++++++ lib/src/task.rs | 34 +++++++++++----------------------- lib/taskchampion.h | 6 +++--- 4 files changed, 44 insertions(+), 35 deletions(-) diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index 8c403327b..8dfeb1b73 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -1,14 +1,13 @@ use crate::traits::*; use crate::types::*; -use chrono::prelude::*; use taskchampion::Annotation; /// TCAnnotation contains the details of an annotation. #[repr(C)] pub struct TCAnnotation { - /// Time the annotation was made, as a UNIX epoch timestamp - pub entry: i64, - /// Content of the annotation + /// Time the annotation was made. Must be nonzero. + pub entry: libc::time_t, + /// Content of the annotation. Must not be NULL. pub description: *mut TCString<'static>, } @@ -16,13 +15,16 @@ impl PassByValue for TCAnnotation { type RustType = Annotation; unsafe fn from_ctype(self) -> Annotation { - let entry = Utc.timestamp(self.entry, 0); + // SAFETY: + // - any time_t value is valid + // - time_t is not zero, so unwrap is safe (see type docstring) + let entry = unsafe { self.entry.from_ctype() }.unwrap(); // SAFETY: // - self is owned, so we can take ownership of this TCString - // - self.description was created from a String so has valid UTF-8 - // - caller did not change it (promised by caller) + // - self.description is a valid, non-null TCString (see type docstring) let description = unsafe { TCString::take_from_arg(self.description) } .into_string() + // TODO: might not be valid utf-8 .unwrap(); Annotation { entry, description } } @@ -30,7 +32,7 @@ impl PassByValue for TCAnnotation { fn as_ctype(arg: Annotation) -> Self { let description: TCString = arg.description.into(); TCAnnotation { - entry: arg.entry.timestamp(), + entry: libc::time_t::as_ctype(Some(arg.entry)), // SAFETY: caller will later free this value via tc_annotation_free description: unsafe { description.return_val() }, } @@ -40,7 +42,7 @@ impl PassByValue for TCAnnotation { impl Default for TCAnnotation { fn default() -> Self { TCAnnotation { - entry: 0, + entry: 0 as libc::time_t, description: std::ptr::null_mut(), } } diff --git a/lib/src/atomic.rs b/lib/src/atomic.rs index db8315922..8a0d9e3d1 100644 --- a/lib/src/atomic.rs +++ b/lib/src/atomic.rs @@ -1,6 +1,7 @@ //! Trait implementations for a few atomic types use crate::traits::*; +use chrono::prelude::*; impl PassByValue for usize { type RustType = usize; @@ -13,3 +14,21 @@ impl PassByValue for usize { arg } } + +/// Convert an Option> to a libc::time_t, or zero if not set. +impl PassByValue for libc::time_t { + type RustType = Option>; + + unsafe fn from_ctype(self) -> Option> { + if self == 0 { + None + } else { + Some(Utc.timestamp(self as i64, 0)) + } + } + + fn as_ctype(arg: Option>) -> libc::time_t { + arg.map(|ts| ts.timestamp() as libc::time_t) + .unwrap_or(0 as libc::time_t) + } +} diff --git a/lib/src/task.rs b/lib/src/task.rs index b56a71498..717f5de78 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,7 +1,7 @@ use crate::traits::*; use crate::types::*; use crate::util::err_to_tcstring; -use chrono::{DateTime, TimeZone, Utc}; +use chrono::{TimeZone, Utc}; use std::convert::TryFrom; use std::ops::Deref; use std::ptr::NonNull; @@ -155,22 +155,6 @@ impl TryFrom> for Tag { } } -/// Convert a DateTime to a libc::time_t, or zero if not set. -fn to_time_t(timestamp: Option>) -> libc::time_t { - timestamp - .map(|ts| ts.timestamp() as libc::time_t) - .unwrap_or(0 as libc::time_t) -} - -/// Convert a libc::time_t to Option>, treating time zero as None -fn to_datetime(time: libc::time_t) -> Option> { - if time == 0 { - None - } else { - Some(Utc.timestamp(time as i64, 0)) - } -} - /// TCTaskList represents a list of tasks. /// /// The content of this struct must be treated as read-only. @@ -275,19 +259,19 @@ pub unsafe extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut /// Get the entry timestamp for a task (when it was created), or 0 if not set. #[no_mangle] pub unsafe extern "C" fn tc_task_get_entry<'a>(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| to_time_t(task.get_entry())) + wrap(task, |task| libc::time_t::as_ctype(task.get_entry())) } /// Get the wait timestamp for a task, or 0 if not set. #[no_mangle] pub unsafe extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| to_time_t(task.get_wait())) + wrap(task, |task| libc::time_t::as_ctype(task.get_wait())) } /// Get the modified timestamp for a task, or 0 if not set. #[no_mangle] pub unsafe extern "C" fn tc_task_get_modified<'a>(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| to_time_t(task.get_modified())) + wrap(task, |task| libc::time_t::as_ctype(task.get_modified())) } /// Check if a task is waiting. @@ -396,7 +380,8 @@ pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_ wrap_mut( task, |task| { - task.set_entry(to_datetime(entry))?; + // SAFETY: any time_t value is a valid timestamp + task.set_entry(unsafe { entry.from_ctype() })?; Ok(TCResult::Ok) }, TCResult::Error, @@ -409,7 +394,8 @@ pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) wrap_mut( task, |task| { - task.set_wait(to_datetime(wait))?; + // SAFETY: any time_t value is a valid timestamp + task.set_wait(unsafe { wait.from_ctype() })?; Ok(TCResult::Ok) }, TCResult::Error, @@ -426,7 +412,9 @@ pub unsafe extern "C" fn tc_task_set_modified( task, |task| { task.set_modified( - to_datetime(modified).ok_or_else(|| anyhow::anyhow!("modified cannot be zero"))?, + // SAFETY: any time_t value is a valid timestamp + unsafe { modified.from_ctype() } + .ok_or_else(|| anyhow::anyhow!("modified cannot be zero"))?, )?; Ok(TCResult::Ok) }, diff --git a/lib/taskchampion.h b/lib/taskchampion.h index a6ffc7644..4b993d8ff 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -122,11 +122,11 @@ typedef struct TCTask TCTask; */ typedef struct TCAnnotation { /** - * Time the annotation was made, as a UNIX epoch timestamp + * Time the annotation was made. Must be nonzero. */ - int64_t entry; + time_t entry; /** - * Content of the annotation + * Content of the annotation. Must not be NULL. */ struct TCString *description; } TCAnnotation; From 76cbc2880b6cef3781d6a2e67027d644306280cd Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 12 Feb 2022 00:26:57 +0000 Subject: [PATCH 495/548] refactor annotations to handle invalid strings --- lib/src/annotation.rs | 22 ++++++++++------------ lib/src/task.rs | 13 +++++++++---- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index 8dfeb1b73..167afc498 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -1,6 +1,6 @@ use crate::traits::*; use crate::types::*; -use taskchampion::Annotation; +use chrono::prelude::*; /// TCAnnotation contains the details of an annotation. #[repr(C)] @@ -12,9 +12,11 @@ pub struct TCAnnotation { } impl PassByValue for TCAnnotation { - type RustType = Annotation; + // NOTE: we cannot use `RustType = Annotation` here because conversion of the + // TCString to a String can fail. + type RustType = (DateTime, TCString<'static>); - unsafe fn from_ctype(self) -> Annotation { + unsafe fn from_ctype(self) -> Self::RustType { // SAFETY: // - any time_t value is valid // - time_t is not zero, so unwrap is safe (see type docstring) @@ -22,18 +24,14 @@ impl PassByValue for TCAnnotation { // SAFETY: // - self is owned, so we can take ownership of this TCString // - self.description is a valid, non-null TCString (see type docstring) - let description = unsafe { TCString::take_from_arg(self.description) } - .into_string() - // TODO: might not be valid utf-8 - .unwrap(); - Annotation { entry, description } + let description = unsafe { TCString::take_from_arg(self.description) }; + (entry, description) } - fn as_ctype(arg: Annotation) -> Self { - let description: TCString = arg.description.into(); + fn as_ctype((entry, description): Self::RustType) -> Self { TCAnnotation { - entry: libc::time_t::as_ctype(Some(arg.entry)), - // SAFETY: caller will later free this value via tc_annotation_free + entry: libc::time_t::as_ctype(Some(entry)), + // SAFETY: caller assumes ownership of this value description: unsafe { description.return_val() }, } } diff --git a/lib/src/task.rs b/lib/src/task.rs index 717f5de78..03b86d806 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; use std::ops::Deref; use std::ptr::NonNull; use std::str::FromStr; -use taskchampion::{Tag, Task, TaskMut}; +use taskchampion::{Annotation, Tag, Task, TaskMut}; /// A task, as publicly exposed by this library. /// @@ -331,7 +331,10 @@ pub unsafe extern "C" fn tc_task_get_annotations<'a>(task: *mut TCTask) -> TCAnn wrap(task, |task| { let vec: Vec = task .get_annotations() - .map(|a| TCAnnotation::as_ctype(a)) + .map(|a| { + let description = TCString::from(a.description); + TCAnnotation::as_ctype((a.entry, description)) + }) .collect(); TCAnnotationList::return_val(vec) }) @@ -513,11 +516,13 @@ pub unsafe extern "C" fn tc_task_add_annotation( annotation: *mut TCAnnotation, ) -> TCResult { // SAFETY: see TCAnnotation docstring - let ann = unsafe { TCAnnotation::take_from_arg(annotation, TCAnnotation::default()) }; + let (entry, description) = + unsafe { TCAnnotation::take_from_arg(annotation, TCAnnotation::default()) }; wrap_mut( task, |task| { - task.add_annotation(ann)?; + let description = description.into_string()?; + task.add_annotation(Annotation { entry, description })?; Ok(TCResult::Ok) }, TCResult::Error, From e9cd6adc5bc307e0bb5d93a0061fdccd94354b67 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 12 Feb 2022 01:15:32 +0000 Subject: [PATCH 496/548] fix memory leak, remove blanket pointer-by-value impls --- lib/src/annotation.rs | 9 ++- lib/src/string.rs | 8 +-- lib/src/task.rs | 8 +-- lib/src/traits.rs | 140 ++++++++++++++++++++---------------------- lib/src/uuid.rs | 8 +-- 5 files changed, 81 insertions(+), 92 deletions(-) diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index 167afc498..22a81653d 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -95,12 +95,11 @@ pub unsafe extern "C" fn tc_annotation_free(tcann: *mut TCAnnotation) { /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCAnnotationList. #[no_mangle] pub unsafe extern "C" fn tc_annotation_list_free(tcanns: *mut TCAnnotationList) { - debug_assert!(!tcanns.is_null()); // SAFETY: - // - *tcanns is a valid TCAnnotationList (caller promises to treat it as read-only) - let annotations = - unsafe { TCAnnotationList::take_from_arg(tcanns, TCAnnotationList::null_value()) }; - TCAnnotationList::drop_vector(annotations); + // - tcanns is not NULL and points to a valid TCAnnotationList (caller is not allowed to + // modify the list) + // - caller promises not to use the value after return + unsafe { drop_value_array(tcanns) } } #[cfg(test)] diff --git a/lib/src/string.rs b/lib/src/string.rs index d9fc90507..c02bdb1e6 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -321,11 +321,11 @@ pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. #[no_mangle] pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { - debug_assert!(!tcstrings.is_null()); // SAFETY: - // - *tcstrings is a valid TCStringList (caller promises to treat it as read-only) - let strings = unsafe { TCStringList::take_from_arg(tcstrings, TCStringList::null_value()) }; - TCStringList::drop_vector(strings); + // - tcstrings is not NULL and points to a valid TCStringList (caller is not allowed to + // modify the list) + // - caller promises not to use the value after return + unsafe { drop_pointer_array(tcstrings) }; } #[cfg(test)] diff --git a/lib/src/task.rs b/lib/src/task.rs index 03b86d806..87a64493d 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -586,11 +586,11 @@ pub unsafe extern "C" fn tc_task_free<'a>(task: *mut TCTask) { /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. #[no_mangle] pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { - debug_assert!(!tctasks.is_null()); // SAFETY: - // - *tctasks is a valid TCTaskList (caller promises to treat it as read-only) - let tasks = unsafe { TCTaskList::take_from_arg(tctasks, TCTaskList::null_value()) }; - TCTaskList::drop_vector(tasks); + // - tctasks is not NULL and points to a valid TCTaskList (caller is not allowed to + // modify the list) + // - caller promises not to use the value after return + unsafe { drop_pointer_array(tctasks) }; } #[cfg(test)] diff --git a/lib/src/traits.rs b/lib/src/traits.rs index 007d60ac3..fea6ddbca 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -6,6 +6,8 @@ use std::ptr::NonNull; /// /// The Rust and C types may differ, with from_ctype and as_ctype converting between them. /// Implement this trait for the C type. +/// +/// The RustType must be droppable (not containing raw pointers). pub(crate) trait PassByValue: Sized { type RustType; @@ -146,66 +148,6 @@ pub(crate) trait PassByPointer: Sized { } } -/// *mut P can be passed by value. -/// -/// # Safety -/// -/// The 'static bound means that the pointer is treated as an "owning" pointer, -/// and must not be copied (leading to a double-free) or dropped without dropping -/// its contents (a leak). -impl

      PassByValue for *mut P -where - P: PassByPointer + 'static, -{ - type RustType = &'static mut P; - - unsafe fn from_ctype(self) -> Self::RustType { - // SAFETY: self must be a valid *mut P (promised by caller) - unsafe { &mut *self } - } - - /// Convert a Rust value to a C value. - fn as_ctype(arg: Self::RustType) -> Self { - arg - } -} - -/// *const P can be passed by value. See the implementation for *mut P. -impl

      PassByValue for *const P -where - P: PassByPointer + 'static, -{ - type RustType = &'static P; - - unsafe fn from_ctype(self) -> Self::RustType { - // SAFETY: self must be a valid *const P (promised by caller) - unsafe { &*self } - } - - /// Convert a Rust value to a C value. - fn as_ctype(arg: Self::RustType) -> Self { - arg - } -} - -/// NonNull

      can be passed by value. See the implementation for NonNull

      . -impl

      PassByValue for NonNull

      -where - P: PassByPointer + 'static, -{ - type RustType = &'static mut P; - - unsafe fn from_ctype(mut self) -> Self::RustType { - // SAFETY: self must be a valid NonNull

      (promised by caller) - unsafe { self.as_mut() } - } - - /// Convert a Rust value to a C value. - fn as_ctype(arg: Self::RustType) -> Self { - NonNull::new(arg).expect("value must not be NULL") - } -} - /// Support for C arrays of objects referenced by value. /// /// The underlying C type should have three fields, containing items, length, and capacity. The @@ -213,10 +155,10 @@ where /// implemented automatically, converting between the C type and `Vec`. For most cases, /// it is only necessary to implement `tc_.._free` that first calls /// `PassByValue::take_from_arg(arg, CArray::null_value())` to take the existing value and -/// replace it with the null value; then `CArray::drop_value_vector(..)` to drop the resulting +/// replace it with the null value; then one of hte `drop_.._array(..)` functions to drop the resulting /// vector and all of the objects it points to. /// -/// This can be used for objects referenced by pointer, too, with an Element type of `*const T` +/// This can be used for objects referenced by pointer, too, with an Element type of `NonNull` /// /// # Safety /// @@ -225,7 +167,7 @@ where /// /// This class guarantees that the items pointer is non-NULL for any valid array (even when len=0). pub(crate) trait CArray: Sized { - type Element: PassByValue; + type Element; /// Create a new CArray from the given items, len, and capacity. /// @@ -247,20 +189,68 @@ pub(crate) trait CArray: Sized { // - satisfies the first case in from_raw_parts' safety documentation unsafe { Self::from_raw_parts(std::ptr::null(), 0, 0) } } +} - /// Drop a vector of elements. This is a convenience function for implementing - /// tc_.._free functions. - fn drop_vector(mut vec: Vec) { - // first, drop each of the elements in turn - for e in vec.drain(..) { - // SAFETY: - // - e is a valid Element (caller promisd not to change it) - // - Vec::drain has invalidated this entry (value is owned) - drop(unsafe { PassByValue::from_ctype(e) }); - } - // then drop the vector - drop(vec); +/// Given a CArray containing pass-by-value values, drop all of the values and +/// the array. +/// +/// This is a convenience function for `tc_.._list_free` functions. +/// +/// # Safety +/// +/// - Array must be non-NULL and point to a valid CA instance +/// - The caller must not use the value array points to after this function, as +/// it has been freed. It will be replaced with the null value. +pub(crate) unsafe fn drop_value_array(array: *mut CA) +where + CA: CArray, + T: PassByValue, +{ + debug_assert!(!array.is_null()); + + // SAFETY: + // - *array is a valid CA (caller promises to treat it as read-only) + let mut vec = unsafe { CA::take_from_arg(array, CA::null_value()) }; + + // first, drop each of the elements in turn + for e in vec.drain(..) { + // SAFETY: + // - e is a valid Element (caller promisd not to change it) + // - Vec::drain has invalidated this entry (value is owned) + drop(unsafe { PassByValue::from_arg(e) }); } + // then drop the vector + drop(vec); +} + +/// Given a CArray containing NonNull pointers, drop all of the pointed-to values and the array. +/// +/// This is a convenience function for `tc_.._list_free` functions. +/// +/// # Safety +/// +/// - Array must be non-NULL and point to a valid CA instance +/// - The caller must not use the value array points to after this function, as +/// it has been freed. It will be replaced with the null value. +pub(crate) unsafe fn drop_pointer_array(array: *mut CA) +where + CA: CArray>, + T: PassByPointer, +{ + debug_assert!(!array.is_null()); + // SAFETY: + // - *array is a valid CA (caller promises to treat it as read-only) + let mut vec = unsafe { CA::take_from_arg(array, CA::null_value()) }; + + // first, drop each of the elements in turn + for e in vec.drain(..) { + // SAFETY: + // - e is a valid Element (caller promised not to change it) + // - Vec::drain has invalidated this entry (value is owned) + drop(unsafe { PassByPointer::take_from_arg(e.as_ptr()) }); + } + // then drop the vector + drop(vec); } impl PassByValue for A diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index d140a9eed..56311152b 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -130,11 +130,11 @@ pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut T /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. #[no_mangle] pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { - debug_assert!(!tcuuids.is_null()); // SAFETY: - // - *tcuuids is a valid TCUuidList (caller promises to treat it as read-only) - let uuids = unsafe { TCUuidList::take_from_arg(tcuuids, TCUuidList::null_value()) }; - TCUuidList::drop_vector(uuids); + // - tcuuids is not NULL and points to a valid TCUuidList (caller is not allowed to + // modify the list) + // - caller promises not to use the value after return + unsafe { drop_value_array(tcuuids) }; } #[cfg(test)] From f81c4eec90d0d9e1a159047acc70eb0726ea7f69 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 12 Feb 2022 15:20:46 +0000 Subject: [PATCH 497/548] rename array to list in rust types --- lib/src/annotation.rs | 6 +++--- lib/src/replica.rs | 2 +- lib/src/string.rs | 6 +++--- lib/src/task.rs | 6 +++--- lib/src/traits.rs | 48 +++++++++++++++++++++---------------------- lib/src/uuid.rs | 6 +++--- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index 22a81653d..558f0d8c4 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -62,7 +62,7 @@ pub struct TCAnnotationList { items: *const TCAnnotation, } -impl CArray for TCAnnotationList { +impl CList for TCAnnotationList { type Element = TCAnnotation; unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { @@ -99,7 +99,7 @@ pub unsafe extern "C" fn tc_annotation_list_free(tcanns: *mut TCAnnotationList) // - tcanns is not NULL and points to a valid TCAnnotationList (caller is not allowed to // modify the list) // - caller promises not to use the value after return - unsafe { drop_value_array(tcanns) } + unsafe { drop_value_list(tcanns) } } #[cfg(test)] @@ -107,7 +107,7 @@ mod test { use super::*; #[test] - fn empty_array_has_non_null_pointer() { + fn empty_list_has_non_null_pointer() { let tcanns = TCAnnotationList::return_val(Vec::new()); assert!(!tcanns.items.is_null()); assert_eq!(tcanns.len, 0); diff --git a/lib/src/replica.rs b/lib/src/replica.rs index b946efb34..99f07639d 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -139,7 +139,7 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList rep, |rep| { // note that the Replica API returns a hashmap here, but we discard - // the keys and return a simple array. The task UUIDs are available + // the keys and return a simple list. The task UUIDs are available // from task.get_uuid(), so information is not lost. let tasks: Vec<_> = rep .all_tasks()? diff --git a/lib/src/string.rs b/lib/src/string.rs index c02bdb1e6..af40714d9 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -154,7 +154,7 @@ pub struct TCStringList { items: *const NonNull>, } -impl CArray for TCStringList { +impl CList for TCStringList { type Element = NonNull>; unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { @@ -325,7 +325,7 @@ pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { // - tcstrings is not NULL and points to a valid TCStringList (caller is not allowed to // modify the list) // - caller promises not to use the value after return - unsafe { drop_pointer_array(tcstrings) }; + unsafe { drop_pointer_list(tcstrings) }; } #[cfg(test)] @@ -334,7 +334,7 @@ mod test { use pretty_assertions::assert_eq; #[test] - fn empty_array_has_non_null_pointer() { + fn empty_list_has_non_null_pointer() { let tcstrings = TCStringList::return_val(Vec::new()); assert!(!tcstrings.items.is_null()); assert_eq!(tcstrings.len, 0); diff --git a/lib/src/task.rs b/lib/src/task.rs index 87a64493d..390386e93 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -172,7 +172,7 @@ pub struct TCTaskList { items: *const NonNull, } -impl CArray for TCTaskList { +impl CList for TCTaskList { type Element = NonNull; unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { @@ -590,7 +590,7 @@ pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { // - tctasks is not NULL and points to a valid TCTaskList (caller is not allowed to // modify the list) // - caller promises not to use the value after return - unsafe { drop_pointer_array(tctasks) }; + unsafe { drop_pointer_list(tctasks) }; } #[cfg(test)] @@ -598,7 +598,7 @@ mod test { use super::*; #[test] - fn empty_array_has_non_null_pointer() { + fn empty_list_has_non_null_pointer() { let tctasks = TCTaskList::return_val(Vec::new()); assert!(!tctasks.items.is_null()); assert_eq!(tctasks.len, 0); diff --git a/lib/src/traits.rs b/lib/src/traits.rs index fea6ddbca..6a4ad2735 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -148,14 +148,14 @@ pub(crate) trait PassByPointer: Sized { } } -/// Support for C arrays of objects referenced by value. +/// Support for C lists of objects referenced by value. /// /// The underlying C type should have three fields, containing items, length, and capacity. The /// required trait functions just fetch and set these fields. The PassByValue trait will be /// implemented automatically, converting between the C type and `Vec`. For most cases, /// it is only necessary to implement `tc_.._free` that first calls -/// `PassByValue::take_from_arg(arg, CArray::null_value())` to take the existing value and -/// replace it with the null value; then one of hte `drop_.._array(..)` functions to drop the resulting +/// `PassByValue::take_from_arg(arg, CList::null_value())` to take the existing value and +/// replace it with the null value; then one of hte `drop_.._list(..)` functions to drop the resulting /// vector and all of the objects it points to. /// /// This can be used for objects referenced by pointer, too, with an Element type of `NonNull` @@ -165,11 +165,11 @@ pub(crate) trait PassByPointer: Sized { /// The C type must be documented as read-only. None of the fields may be modified, nor anything /// in the `items` array. /// -/// This class guarantees that the items pointer is non-NULL for any valid array (even when len=0). -pub(crate) trait CArray: Sized { +/// This class guarantees that the items pointer is non-NULL for any valid list (even when len=0). +pub(crate) trait CList: Sized { type Element; - /// Create a new CArray from the given items, len, and capacity. + /// Create a new CList from the given items, len, and capacity. /// /// # Safety /// @@ -191,26 +191,26 @@ pub(crate) trait CArray: Sized { } } -/// Given a CArray containing pass-by-value values, drop all of the values and -/// the array. +/// Given a CList containing pass-by-value values, drop all of the values and +/// the list. /// /// This is a convenience function for `tc_.._list_free` functions. /// /// # Safety /// -/// - Array must be non-NULL and point to a valid CA instance +/// - List must be non-NULL and point to a valid CL instance /// - The caller must not use the value array points to after this function, as /// it has been freed. It will be replaced with the null value. -pub(crate) unsafe fn drop_value_array(array: *mut CA) +pub(crate) unsafe fn drop_value_list(list: *mut CL) where - CA: CArray, + CL: CList, T: PassByValue, { - debug_assert!(!array.is_null()); + debug_assert!(!list.is_null()); // SAFETY: - // - *array is a valid CA (caller promises to treat it as read-only) - let mut vec = unsafe { CA::take_from_arg(array, CA::null_value()) }; + // - *list is a valid CL (caller promises to treat it as read-only) + let mut vec = unsafe { CL::take_from_arg(list, CL::null_value()) }; // first, drop each of the elements in turn for e in vec.drain(..) { @@ -223,24 +223,24 @@ where drop(vec); } -/// Given a CArray containing NonNull pointers, drop all of the pointed-to values and the array. +/// Given a CList containing NonNull pointers, drop all of the pointed-to values and the list. /// /// This is a convenience function for `tc_.._list_free` functions. /// /// # Safety /// -/// - Array must be non-NULL and point to a valid CA instance +/// - List must be non-NULL and point to a valid CL instance /// - The caller must not use the value array points to after this function, as /// it has been freed. It will be replaced with the null value. -pub(crate) unsafe fn drop_pointer_array(array: *mut CA) +pub(crate) unsafe fn drop_pointer_list(list: *mut CL) where - CA: CArray>, + CL: CList>, T: PassByPointer, { - debug_assert!(!array.is_null()); + debug_assert!(!list.is_null()); // SAFETY: - // - *array is a valid CA (caller promises to treat it as read-only) - let mut vec = unsafe { CA::take_from_arg(array, CA::null_value()) }; + // - *list is a valid CL (caller promises to treat it as read-only) + let mut vec = unsafe { CL::take_from_arg(list, CL::null_value()) }; // first, drop each of the elements in turn for e in vec.drain(..) { @@ -255,7 +255,7 @@ where impl PassByValue for A where - A: CArray, + A: CList, { type RustType = Vec; @@ -263,9 +263,9 @@ where let (items, len, cap) = self.into_raw_parts(); debug_assert!(!items.is_null()); // SAFETY: - // - CArray::from_raw_parts requires that items, len, and cap be valid for + // - CList::from_raw_parts requires that items, len, and cap be valid for // Vec::from_raw_parts if not NULL, and they are not NULL (as promised by caller) - // - CArray::into_raw_parts returns precisely the values passed to from_raw_parts. + // - CList::into_raw_parts returns precisely the values passed to from_raw_parts. // - those parts are passed to Vec::from_raw_parts here. unsafe { Vec::from_raw_parts(items as *mut _, len, cap) } } diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 56311152b..8a26e4d11 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -56,7 +56,7 @@ pub struct TCUuidList { items: *const TCUuid, } -impl CArray for TCUuidList { +impl CList for TCUuidList { type Element = TCUuid; unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { @@ -134,7 +134,7 @@ pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { // - tcuuids is not NULL and points to a valid TCUuidList (caller is not allowed to // modify the list) // - caller promises not to use the value after return - unsafe { drop_value_array(tcuuids) }; + unsafe { drop_value_list(tcuuids) }; } #[cfg(test)] @@ -142,7 +142,7 @@ mod test { use super::*; #[test] - fn empty_array_has_non_null_pointer() { + fn empty_list_has_non_null_pointer() { let tcuuids = TCUuidList::return_val(Vec::new()); assert!(!tcuuids.items.is_null()); assert_eq!(tcuuids.len, 0); From ad560fdb79f9a9cb42cec088ac5b881c03eda106 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 12 Feb 2022 16:22:45 +0000 Subject: [PATCH 498/548] add UDA support --- integration-tests/src/bindings_tests/task.c | 135 +++++++++++++++ lib/src/lib.rs | 2 + lib/src/task.rs | 178 +++++++++++++++++++- lib/src/uda.rs | 148 ++++++++++++++++ lib/taskchampion.h | 105 ++++++++++++ 5 files changed, 560 insertions(+), 8 deletions(-) create mode 100644 lib/src/uda.rs diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 0eb1e45c0..5dab6c510 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -386,6 +386,140 @@ static void test_task_annotations(void) { tc_replica_free(rep); } +// UDA manipulation (add, remove, list, free) +static void test_task_udas(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + tc_task_to_mut(task, rep); + + TCString *value; + TCUDAList udas; + + TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1"))); + TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1"))); + + udas = tc_task_get_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(0, udas.len); + tc_uda_list_free(&udas); + + udas = tc_task_get_legacy_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(0, udas.len); + tc_uda_list_free(&udas); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, + tc_task_set_uda(task, + tc_string_borrow("ns"), + tc_string_borrow("u1"), + tc_string_borrow("vvv"))); + + value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")); + TEST_ASSERT_NOT_NULL(value); + TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(value)); + tc_string_free(value); + TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1"))); + + udas = tc_task_get_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(1, udas.len); + TEST_ASSERT_EQUAL_STRING("ns", tc_string_content(udas.items[0].ns)); + TEST_ASSERT_EQUAL_STRING("u1", tc_string_content(udas.items[0].key)); + TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(udas.items[0].value)); + tc_uda_list_free(&udas); + + udas = tc_task_get_legacy_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(1, udas.len); + TEST_ASSERT_NULL(udas.items[0].ns); + TEST_ASSERT_EQUAL_STRING("ns.u1", tc_string_content(udas.items[0].key)); + TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(udas.items[0].value)); + tc_uda_list_free(&udas); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, + tc_task_set_legacy_uda(task, + tc_string_borrow("leg1"), + tc_string_borrow("legv"))); + + value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")); + TEST_ASSERT_NOT_NULL(value); + TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(value)); + tc_string_free(value); + + value = tc_task_get_legacy_uda(task, tc_string_borrow("leg1")); + TEST_ASSERT_NOT_NULL(value); + TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(value)); + tc_string_free(value); + + udas = tc_task_get_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(2, udas.len); + tc_uda_list_free(&udas); + + udas = tc_task_get_legacy_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(2, udas.len); + tc_uda_list_free(&udas); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, + tc_task_remove_uda(task, + tc_string_borrow("ns"), + tc_string_borrow("u1"))); + + TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1"))); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, + tc_task_remove_uda(task, + tc_string_borrow("ns"), + tc_string_borrow("u1"))); + + udas = tc_task_get_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(1, udas.len); + TEST_ASSERT_EQUAL_STRING("", tc_string_content(udas.items[0].ns)); + TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(udas.items[0].key)); + TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(udas.items[0].value)); + tc_uda_list_free(&udas); + + udas = tc_task_get_legacy_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(1, udas.len); + TEST_ASSERT_NULL(udas.items[0].ns); + TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(udas.items[0].key)); + TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(udas.items[0].value)); + tc_uda_list_free(&udas); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, + tc_task_remove_legacy_uda(task, + tc_string_borrow("leg1"))); + + TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1"))); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, + tc_task_remove_legacy_uda(task, + tc_string_borrow("leg1"))); + + udas = tc_task_get_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(0, udas.len); + tc_uda_list_free(&udas); + + udas = tc_task_get_legacy_udas(task); + TEST_ASSERT_NOT_NULL(udas.items); + TEST_ASSERT_EQUAL(0, udas.len); + tc_uda_list_free(&udas); + + tc_task_free(task); + tc_replica_free(rep); +} + int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. @@ -401,5 +535,6 @@ int task_tests(void) { RUN_TEST(test_task_add_remove_has_tag); RUN_TEST(test_task_get_tags); RUN_TEST(test_task_annotations); + RUN_TEST(test_task_udas); return UNITY_END(); } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 41fa16005..5bc7629a3 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -12,6 +12,7 @@ pub mod result; pub mod status; pub mod string; pub mod task; +pub mod uda; pub mod uuid; pub(crate) mod types { @@ -21,5 +22,6 @@ pub(crate) mod types { pub(crate) use crate::status::TCStatus; pub(crate) use crate::string::{TCString, TCStringList}; pub(crate) use crate::task::{TCTask, TCTaskList}; + pub(crate) use crate::uda::{TCUDAList, TCUDA, UDA}; pub(crate) use crate::uuid::{TCUuid, TCUuidList}; } diff --git a/lib/src/task.rs b/lib/src/task.rs index 390386e93..a20b2049f 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -340,10 +340,89 @@ pub unsafe extern "C" fn tc_task_get_annotations<'a>(task: *mut TCTask) -> TCAnn }) } -// TODO: tc_task_get_uda -// TODO: tc_task_get_udas -// TODO: tc_task_get_legacy_uda -// TODO: tc_task_get_legacy_udas +/// Get the named UDA from the task. +/// +/// Returns NULL if the UDA does not exist. +#[no_mangle] +pub unsafe extern "C" fn tc_task_get_uda<'a>( + task: *mut TCTask, + ns: *mut TCString<'a>, + key: *mut TCString<'a>, +) -> *mut TCString<'static> { + wrap(task, |task| { + if let Ok(ns) = unsafe { TCString::take_from_arg(ns) }.as_str() { + if let Ok(key) = unsafe { TCString::take_from_arg(key) }.as_str() { + if let Some(value) = task.get_uda(ns, key) { + // SAFETY: + // - caller will free this string (caller promises) + return unsafe { TCString::return_val(value.into()) }; + } + } + } + std::ptr::null_mut() + }) +} + +/// Get the named legacy UDA from the task. +/// +/// Returns NULL if the UDA does not exist. +#[no_mangle] +pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>( + task: *mut TCTask, + key: *mut TCString<'a>, +) -> *mut TCString<'static> { + wrap(task, |task| { + if let Ok(key) = unsafe { TCString::take_from_arg(key) }.as_str() { + if let Some(value) = task.get_legacy_uda(key) { + // SAFETY: + // - caller will free this string (caller promises) + return unsafe { TCString::return_val(value.into()) }; + } + } + std::ptr::null_mut() + }) +} + +/// Get all UDAs for this task. +/// +/// Legacy UDAs are represented with an empty string in the ns field. +#[no_mangle] +pub unsafe extern "C" fn tc_task_get_udas<'a>(task: *mut TCTask) -> TCUDAList { + wrap(task, |task| { + let vec: Vec = task + .get_udas() + .map(|((ns, key), value)| { + TCUDA::return_val(UDA { + ns: Some(ns.into()), + key: key.into(), + value: value.into(), + }) + }) + .collect(); + TCUDAList::return_val(vec) + }) +} + +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_task_get_legacy_udas<'a>(task: *mut TCTask) -> TCUDAList { + wrap(task, |task| { + let vec: Vec = task + .get_legacy_udas() + .map(|(key, value)| { + TCUDA::return_val(UDA { + ns: None, + key: key.into(), + value: value.into(), + }) + }) + .collect(); + TCUDAList::return_val(vec) + }) +} /// Set a mutable task's status. #[no_mangle] @@ -542,10 +621,93 @@ pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64 ) } -// TODO: tc_task_set_uda -// TODO: tc_task_remove_uda -// TODO: tc_task_set_legacy_uda -// TODO: tc_task_remove_legacy_uda +/// Set a UDA on a mutable task. +#[no_mangle] +pub unsafe extern "C" fn tc_task_set_uda<'a>( + task: *mut TCTask, + ns: *mut TCString, + key: *mut TCString, + value: *mut TCString, +) -> TCResult { + // SAFETY: see TCString docstring + let ns = unsafe { TCString::take_from_arg(ns) }; + // SAFETY: see TCString docstring + let key = unsafe { TCString::take_from_arg(key) }; + // SAFETY: see TCString docstring + let value = unsafe { TCString::take_from_arg(value) }; + wrap_mut( + task, + |task| { + task.set_uda( + ns.as_str()?.to_string(), + key.as_str()?.to_string(), + value.as_str()?.to_string(), + )?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + +/// Remove a UDA fraom a mutable task. +#[no_mangle] +pub unsafe extern "C" fn tc_task_remove_uda<'a>( + task: *mut TCTask, + ns: *mut TCString, + key: *mut TCString, +) -> TCResult { + // SAFETY: see TCString docstring + let ns = unsafe { TCString::take_from_arg(ns) }; + // SAFETY: see TCString docstring + let key = unsafe { TCString::take_from_arg(key) }; + wrap_mut( + task, + |task| { + task.remove_uda(ns.as_str()?.to_string(), key.as_str()?.to_string())?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + +/// Set a legacy UDA on a mutable task. +#[no_mangle] +pub unsafe extern "C" fn tc_task_set_legacy_uda<'a>( + task: *mut TCTask, + key: *mut TCString, + value: *mut TCString, +) -> TCResult { + // SAFETY: see TCString docstring + let key = unsafe { TCString::take_from_arg(key) }; + // SAFETY: see TCString docstring + let value = unsafe { TCString::take_from_arg(value) }; + wrap_mut( + task, + |task| { + task.set_legacy_uda(key.as_str()?.to_string(), value.as_str()?.to_string())?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + +/// Remove a UDA fraom a mutable task. +#[no_mangle] +pub unsafe extern "C" fn tc_task_remove_legacy_uda<'a>( + task: *mut TCTask, + key: *mut TCString, +) -> TCResult { + // SAFETY: see TCString docstring + let key = unsafe { TCString::take_from_arg(key) }; + wrap_mut( + task, + |task| { + task.remove_legacy_uda(key.as_str()?.to_string())?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} /// Get the latest error for a task, or NULL 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 diff --git a/lib/src/uda.rs b/lib/src/uda.rs new file mode 100644 index 000000000..27134d80e --- /dev/null +++ b/lib/src/uda.rs @@ -0,0 +1,148 @@ +use crate::traits::*; +use crate::types::*; + +/// TCUDA contains the details of a UDA. +#[repr(C)] +pub struct TCUDA { + /// Namespace of the UDA. For legacy UDAs, this is NULL. + pub ns: *mut TCString<'static>, + /// UDA key. Must not be NULL. + pub key: *mut TCString<'static>, + /// Content of the UDA. Must not be NULL. + pub value: *mut TCString<'static>, +} + +pub(crate) struct UDA { + pub ns: Option>, + pub key: TCString<'static>, + pub value: TCString<'static>, +} + +impl PassByValue for TCUDA { + type RustType = UDA; + + unsafe fn from_ctype(self) -> Self::RustType { + UDA { + ns: if self.ns.is_null() { + None + } else { + // SAFETY: + // - self is owned, so we can take ownership of this TCString + // - self.ns is a valid, non-null TCString (NULL just checked) + Some(unsafe { TCString::take_from_arg(self.ns) }) + }, + // SAFETY: + // - self is owned, so we can take ownership of this TCString + // - self.key is a valid, non-null TCString (see type docstring) + key: unsafe { TCString::take_from_arg(self.key) }, + // SAFETY: + // - self is owned, so we can take ownership of this TCString + // - self.value is a valid, non-null TCString (see type docstring) + value: unsafe { TCString::take_from_arg(self.value) }, + } + } + + fn as_ctype(uda: UDA) -> Self { + TCUDA { + // SAFETY: caller assumes ownership of this value + ns: if let Some(ns) = uda.ns { + unsafe { ns.return_val() } + } else { + std::ptr::null_mut() + }, + // SAFETY: caller assumes ownership of this value + key: unsafe { uda.key.return_val() }, + // SAFETY: caller assumes ownership of this value + value: unsafe { uda.value.return_val() }, + } + } +} + +impl Default for TCUDA { + fn default() -> Self { + TCUDA { + ns: std::ptr::null_mut(), + key: std::ptr::null_mut(), + value: std::ptr::null_mut(), + } + } +} + +/// TCUDAList represents a list of UDAs. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCUDAList { + /// number of UDAs in items + len: libc::size_t, + + /// 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 + /// tc_uda_list_free. This pointer is never NULL for a valid TCUDAList. + items: *const TCUDA, +} + +impl CList for TCUDAList { + type Element = TCUDA; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCUDAList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} + +/// Free a TCUDA instance. The instance, and the TCStrings it contains, must not be used +/// after this call. +#[no_mangle] +pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUDA) { + debug_assert!(!tcuda.is_null()); + // SAFETY: + // - *tcuda is a valid TCUDA (caller promises to treat it as read-only) + let uda = unsafe { TCUDA::take_from_arg(tcuda, TCUDA::default()) }; + drop(uda); +} + +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_uda_list_free(tcudas: *mut TCUDAList) { + // SAFETY: + // - tcudas is not NULL and points to a valid TCUDAList (caller is not allowed to + // modify the list) + // - caller promises not to use the value after return + unsafe { drop_value_list(tcudas) } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_list_has_non_null_pointer() { + let tcudas = TCUDAList::return_val(Vec::new()); + assert!(!tcudas.items.is_null()); + assert_eq!(tcudas.len, 0); + assert_eq!(tcudas._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tcudas = TCUDAList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_uda_list_free(&mut tcudas) }; + assert!(tcudas.items.is_null()); + assert_eq!(tcudas.len, 0); + assert_eq!(tcudas._capacity, 0); + } +} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 4b993d8ff..bc61b1e40 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -226,6 +226,45 @@ typedef struct TCStringList { struct TCString *const *items; } TCStringList; +/** + * TCUDA contains the details of a UDA. + */ +typedef struct TCUDA { + /** + * Namespace of the UDA. For legacy UDAs, this is NULL. + */ + 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. + */ + const struct TCUDA *items; +} TCUDAList; + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -491,6 +530,35 @@ struct TCStringList tc_task_get_tags(struct TCTask *task); */ struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); +/** + * Get the named UDA from the task. + * + * Returns NULL if the UDA does not exist. + */ +struct TCString *tc_task_get_uda(struct TCTask *task, struct TCString *ns, struct TCString *key); + +/** + * 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 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 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. + */ +struct TCUDAList tc_task_get_legacy_udas(struct TCTask *task); + /** * Set a mutable task's status. */ @@ -557,6 +625,29 @@ TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annota */ TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); +/** + * Set a UDA on a mutable task. + */ +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 legacy UDA on a mutable task. + */ +TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString *key, struct TCString *value); + +/** + * Remove a UDA fraom a mutable task. + */ +TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString *key); + /** * Get the latest error for a task, or NULL 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 @@ -580,6 +671,20 @@ void tc_task_free(struct TCTask *task); */ void tc_task_list_free(struct TCTaskList *tctasks); +/** + * 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); + +/** + * 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); + /** * Create a new, randomly-generated UUID. */ From 1488355b89e8cf44a81b8e90d8454d43b60a1a3f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 12 Feb 2022 22:19:09 +0000 Subject: [PATCH 499/548] add working-set support --- .../src/bindings_tests/replica.c | 68 ++++++++++++++- lib/src/lib.rs | 2 + lib/src/replica.rs | 16 +++- lib/src/workingset.rs | 87 +++++++++++++++++++ lib/taskchampion.h | 44 ++++++++++ taskchampion/src/workingset.rs | 20 +++++ 6 files changed, 233 insertions(+), 4 deletions(-) create mode 100644 lib/src/workingset.rs diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 7e598695b..423d8dc25 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -40,12 +40,74 @@ static void test_replica_add_undo_point(void) { tc_replica_free(rep); } -// rebuilding working set succeeds -static void test_replica_rebuild_working_set(void) { +// working set operations succeed +static void test_replica_working_set(void) { + TCWorkingSet *ws; + TCTask *task1, *task2, *task3; + TCUuid uuid, uuid1, uuid2, uuid3; + TCReplica *rep = tc_replica_new_in_memory(); TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true)); TEST_ASSERT_NULL(tc_replica_error(rep)); + + ws = tc_replica_working_set(rep); + TEST_ASSERT_EQUAL(0, tc_working_set_len(ws)); + tc_working_set_free(ws); + + task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task1")); + TEST_ASSERT_NOT_NULL(task1); + uuid1 = tc_task_get_uuid(task1); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true)); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task2")); + TEST_ASSERT_NOT_NULL(task2); + uuid2 = tc_task_get_uuid(task2); + + task3 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task3")); + TEST_ASSERT_NOT_NULL(task3); + uuid3 = tc_task_get_uuid(task3); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false)); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + // finish task2 to leave a "hole" + tc_task_to_mut(task2, rep); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_done(task2)); + tc_task_to_immut(task2); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false)); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + tc_task_free(task1); + tc_task_free(task2); + tc_task_free(task3); + + // working set should now be + // 0 -> None + // 1 -> uuid1 + // 2 -> None + // 3 -> uuid3 + ws = tc_replica_working_set(rep); + TEST_ASSERT_EQUAL(2, tc_working_set_len(ws)); + TEST_ASSERT_EQUAL(3, tc_working_set_largest_index(ws)); + + TEST_ASSERT_FALSE(tc_working_set_by_index(ws, 0, &uuid)); + TEST_ASSERT_TRUE(tc_working_set_by_index(ws, 1, &uuid)); + TEST_ASSERT_EQUAL_MEMORY(uuid1.bytes, uuid.bytes, sizeof(uuid)); + TEST_ASSERT_FALSE(tc_working_set_by_index(ws, 2, &uuid)); + TEST_ASSERT_TRUE(tc_working_set_by_index(ws, 3, &uuid)); + TEST_ASSERT_EQUAL_MEMORY(uuid3.bytes, uuid.bytes, sizeof(uuid)); + + TEST_ASSERT_EQUAL(1, tc_working_set_by_uuid(ws, uuid1)); + TEST_ASSERT_EQUAL(0, tc_working_set_by_uuid(ws, uuid2)); + TEST_ASSERT_EQUAL(3, tc_working_set_by_uuid(ws, uuid3)); + + tc_working_set_free(ws); + tc_replica_free(rep); } @@ -209,7 +271,7 @@ int replica_tests(void) { RUN_TEST(test_replica_creation_disk); RUN_TEST(test_replica_undo_empty); RUN_TEST(test_replica_add_undo_point); - RUN_TEST(test_replica_rebuild_working_set); + RUN_TEST(test_replica_working_set); RUN_TEST(test_replica_undo_empty_null_undone_out); RUN_TEST(test_replica_task_creation); RUN_TEST(test_replica_all_tasks); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 5bc7629a3..b49acf77c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -14,6 +14,7 @@ pub mod string; pub mod task; pub mod uda; pub mod uuid; +pub mod workingset; pub(crate) mod types { pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList}; @@ -24,4 +25,5 @@ pub(crate) mod types { pub(crate) use crate::task::{TCTask, TCTaskList}; pub(crate) use crate::uda::{TCUDAList, TCUDA, UDA}; pub(crate) use crate::uuid::{TCUuid, TCUuidList}; + pub(crate) use crate::workingset::TCWorkingSet; } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 99f07639d..b89df1091 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -177,7 +177,21 @@ pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUui ) } -// TODO: tc_replica_working_set +/// Get the current working set for this replica. +/// +/// Returns NULL on error. +#[no_mangle] +pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCWorkingSet { + wrap( + rep, + |rep| { + let ws = rep.working_set()?; + // SAFETY: caller promises to free this task + Ok(unsafe { TCWorkingSet::return_val(ws.into()) }) + }, + std::ptr::null_mut(), + ) +} /// Get an existing task by its UUID. /// diff --git a/lib/src/workingset.rs b/lib/src/workingset.rs new file mode 100644 index 000000000..91f73bbd0 --- /dev/null +++ b/lib/src/workingset.rs @@ -0,0 +1,87 @@ +use crate::traits::*; +use crate::types::*; +use taskchampion::{Uuid, WorkingSet}; + +/// 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. +pub struct TCWorkingSet(WorkingSet); + +impl PassByPointer for TCWorkingSet {} + +impl From for TCWorkingSet { + fn from(ws: WorkingSet) -> TCWorkingSet { + TCWorkingSet(ws) + } +} + +/// Utility function to get a shared reference to the underlying WorkingSet. +fn wrap<'a, T, F>(ws: *mut TCWorkingSet, f: F) -> T +where + F: FnOnce(&WorkingSet) -> T, +{ + // SAFETY: + // - ws is not null (promised by caller) + // - ws outlives 'a (promised by caller) + let tcws: &'a TCWorkingSet = unsafe { TCWorkingSet::from_arg_ref(ws) }; + f(&tcws.0) +} + +/// Get the working set's length, or the number of UUIDs it contains. +#[no_mangle] +pub unsafe extern "C" fn tc_working_set_len(ws: *mut TCWorkingSet) -> usize { + wrap(ws, |ws| ws.len()) +} + +/// Get the working set's largest index. +#[no_mangle] +pub unsafe extern "C" fn tc_working_set_largest_index(ws: *mut TCWorkingSet) -> usize { + wrap(ws, |ws| ws.largest_index()) +} + +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_working_set_by_index( + ws: *mut TCWorkingSet, + index: usize, + uuid_out: *mut TCUuid, +) -> bool { + debug_assert!(!uuid_out.is_null()); + wrap(ws, |ws| { + if let Some(uuid) = ws.by_index(index) { + // SAFETY: + // - uuid_out is not NULL (promised by caller) + // - alignment is not required + unsafe { TCUuid::to_arg_out(uuid, uuid_out) }; + true + } else { + false + } + }) +} + +/// Get the working set index for the task with the given UUID. Returns 0 if the task is not in +/// the working set. +#[no_mangle] +pub unsafe extern "C" fn tc_working_set_by_uuid(ws: *mut TCWorkingSet, uuid: TCUuid) -> usize { + wrap(ws, |ws| { + // SAFETY: + // - tcuuid is a valid TCUuid (all byte patterns are valid) + let uuid: Uuid = unsafe { TCUuid::from_arg(uuid) }; + ws.by_uuid(uuid).unwrap_or(0) + }) +} + +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_working_set_free(ws: *mut TCWorkingSet) { + // SAFETY: + // - rep is not NULL (promised by caller) + // - caller will not use the TCWorkingSet after this (promised by caller) + let ws = unsafe { TCWorkingSet::take_from_arg(ws) }; + drop(ws); +} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index bc61b1e40..79b25ba35 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -117,6 +117,15 @@ typedef struct TCString TCString; */ 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. + */ +typedef struct TCWorkingSet TCWorkingSet; + /** * TCAnnotation contains the details of an annotation. */ @@ -309,6 +318,13 @@ struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); */ struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); +/** + * Get the current working set for this replica. + * + * Returns NULL on error. + */ +struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep); + /** * Get an existing task by its UUID. * @@ -721,6 +737,34 @@ TCResult tc_uuid_from_str(struct TCString *s, struct TCUuid *uuid_out); */ 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 diff --git a/taskchampion/src/workingset.rs b/taskchampion/src/workingset.rs index ea746a72b..8bd0cce53 100644 --- a/taskchampion/src/workingset.rs +++ b/taskchampion/src/workingset.rs @@ -38,6 +38,11 @@ impl WorkingSet { self.by_index.iter().filter(|e| e.is_some()).count() } + /// Get the largest index in the working set, or zero if the set is empty. + pub fn largest_index(&self) -> usize { + self.by_index.len().saturating_sub(1) + } + /// True if the length is zero pub fn is_empty(&self) -> bool { self.by_index.iter().all(|e| e.is_none()) @@ -103,6 +108,21 @@ mod test { assert_eq!(ws.is_empty(), true); } + #[test] + fn test_largest_index() { + let (uuid1, uuid2, ws) = make(); + assert_eq!(ws.largest_index(), 0); + + let ws = WorkingSet::new(vec![None, Some(uuid1)]); + assert_eq!(ws.largest_index(), 1); + + let ws = WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2)]); + assert_eq!(ws.largest_index(), 3); + + let ws = WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2), None]); + assert_eq!(ws.largest_index(), 4); + } + #[test] fn test_by_index() { let (uuid1, uuid2, ws) = make(); From 213da88b27d8a7a5fa33a2c21f2a21468cffda3a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 02:05:05 +0000 Subject: [PATCH 500/548] add tc_task_get_taskmap --- integration-tests/src/bindings_tests/task.c | 42 ++++++++ lib/src/kv.rs | 103 ++++++++++++++++++++ lib/src/lib.rs | 2 + lib/src/task.rs | 19 +++- lib/taskchampion.h | 47 +++++++++ 5 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 lib/src/kv.rs diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 5dab6c510..7f89ddc51 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -520,6 +520,47 @@ static void test_task_udas(void) { tc_replica_free(rep); } +static void tckvlist_assert_key(TCKVList *list, char *key, char *value) { + TEST_ASSERT_NOT_NULL(list); + for (size_t i = 0; i < list->len; i++) { + if (0 == strcmp(tc_string_content(list->items[i].key), key)) { + TEST_ASSERT_EQUAL_STRING(value, tc_string_content(list->items[i].value)); + return; + } + } + TEST_FAIL_MESSAGE("key not found"); +} + +// get_tags returns the list of tags +static void test_task_taskmap(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + tc_task_to_mut(task, rep); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); + + TCAnnotation ann; + ann.entry = 1644623411; + ann.description = tc_string_borrow("ann1"); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann)); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 3643679997)); // 2085 + + TCKVList taskmap = tc_task_get_taskmap(task); + tckvlist_assert_key(&taskmap, "annotation_1644623411", "ann1"); + tckvlist_assert_key(&taskmap, "tag_next", ""); + tckvlist_assert_key(&taskmap, "status", "pending"); + tckvlist_assert_key(&taskmap, "description", "my task"); + tc_kv_list_free(&taskmap); + + tc_task_free(task); + tc_replica_free(rep); +} + int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. @@ -536,5 +577,6 @@ int task_tests(void) { RUN_TEST(test_task_get_tags); RUN_TEST(test_task_annotations); RUN_TEST(test_task_udas); + RUN_TEST(test_task_taskmap); return UNITY_END(); } diff --git a/lib/src/kv.rs b/lib/src/kv.rs new file mode 100644 index 000000000..3b24e25cf --- /dev/null +++ b/lib/src/kv.rs @@ -0,0 +1,103 @@ +use crate::traits::*; +use crate::types::*; + +/// 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. +#[repr(C)] +pub struct TCKV { + pub key: *mut TCString<'static>, + pub value: *mut TCString<'static>, +} + +impl PassByValue for TCKV { + type RustType = (TCString<'static>, TCString<'static>); + + unsafe fn from_ctype(self) -> Self::RustType { + // SAFETY: + // - self is owned, so we can take ownership of this TCString + // - self.key is a valid, non-null TCString (see type docstring) + let key = unsafe { TCString::take_from_arg(self.key) }; + // SAFETY: (same) + let value = unsafe { TCString::take_from_arg(self.value) }; + (key, value) + } + + fn as_ctype((key, value): Self::RustType) -> Self { + TCKV { + // SAFETY: caller assumes ownership of this value + key: unsafe { key.return_val() }, + // SAFETY: caller assumes ownership of this value + value: unsafe { value.return_val() }, + } + } +} + +/// TCKVList represents a list of key/value pairs. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCKVList { + /// number of key/value pairs in items + len: libc::size_t, + + /// 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 + /// tc_kv_list_free. This pointer is never NULL for a valid TCKVList. + items: *const TCKV, +} + +impl CList for TCKVList { + type Element = TCKV; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCKVList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} + +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_kv_list_free(tckvs: *mut TCKVList) { + // SAFETY: + // - tckvs is not NULL and points to a valid TCKVList (caller is not allowed to + // modify the list) + // - caller promises not to use the value after return + unsafe { drop_value_list(tckvs) } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_list_has_non_null_pointer() { + let tckvs = TCKVList::return_val(Vec::new()); + assert!(!tckvs.items.is_null()); + assert_eq!(tckvs.len, 0); + assert_eq!(tckvs._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tckvs = TCKVList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_kv_list_free(&mut tckvs) }; + assert!(tckvs.items.is_null()); + assert_eq!(tckvs.len, 0); + assert_eq!(tckvs._capacity, 0); + } +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b49acf77c..4c66ec76d 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -7,6 +7,7 @@ mod util; pub mod annotation; pub mod atomic; +pub mod kv; pub mod replica; pub mod result; pub mod status; @@ -18,6 +19,7 @@ pub mod workingset; pub(crate) mod types { pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList}; + pub(crate) use crate::kv::{TCKVList, TCKV}; pub(crate) use crate::replica::TCReplica; pub(crate) use crate::result::TCResult; pub(crate) use crate::status::TCStatus; diff --git a/lib/src/task.rs b/lib/src/task.rs index a20b2049f..421072373 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -243,7 +243,24 @@ pub unsafe extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { wrap(task, |task| task.get_status().into()) } -// TODO: tc_task_get_taskmap (?? then we have to wrap a map..) +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { + wrap(task, |task| { + let vec: Vec = task + .get_taskmap() + .iter() + .map(|(k, v)| { + let key = TCString::from(k.as_ref()); + let value = TCString::from(v.as_ref()); + TCKV::as_ctype((key, value)) + }) + .collect(); + TCKVList::return_val(vec) + }) +} /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 79b25ba35..bb41d4e5a 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -161,6 +161,38 @@ typedef struct TCAnnotationList { const 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. + */ +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. + */ +typedef struct TCKVList { + /** + * 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. + */ + const struct TCKV *items; +} TCKVList; + /** * TCTaskList represents a list of tasks. * @@ -292,6 +324,14 @@ void tc_annotation_free(struct TCAnnotation *tcann); */ void tc_annotation_list_free(struct TCAnnotationList *tcanns); +/** + * 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. @@ -493,6 +533,13 @@ struct TCUuid tc_task_get_uuid(struct TCTask *task); */ enum TCStatus tc_task_get_status(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. + */ +struct TCKVList tc_task_get_taskmap(struct TCTask *task); + /** * Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it * contains embedded NUL characters). From c22182cc19d57a3d441032a1688e77cfbc31e3a5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 02:30:17 +0000 Subject: [PATCH 501/548] rename trait methods to avoid ambiguity --- lib/src/annotation.rs | 6 ++--- lib/src/kv.rs | 8 +++--- lib/src/replica.rs | 34 ++++++++++++------------ lib/src/string.rs | 14 +++++----- lib/src/task.rs | 60 +++++++++++++++++++++---------------------- lib/src/traits.rs | 42 +++++++++++++++--------------- lib/src/uda.rs | 14 +++++----- lib/src/uuid.rs | 10 ++++---- lib/src/workingset.rs | 8 +++--- 9 files changed, 98 insertions(+), 98 deletions(-) diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index 558f0d8c4..b05b8992a 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -24,7 +24,7 @@ impl PassByValue for TCAnnotation { // SAFETY: // - self is owned, so we can take ownership of this TCString // - self.description is a valid, non-null TCString (see type docstring) - let description = unsafe { TCString::take_from_arg(self.description) }; + let description = unsafe { TCString::take_from_ptr_arg(self.description) }; (entry, description) } @@ -32,7 +32,7 @@ impl PassByValue for TCAnnotation { TCAnnotation { entry: libc::time_t::as_ctype(Some(entry)), // SAFETY: caller assumes ownership of this value - description: unsafe { description.return_val() }, + description: unsafe { description.return_ptr() }, } } } @@ -85,7 +85,7 @@ pub unsafe extern "C" fn tc_annotation_free(tcann: *mut TCAnnotation) { debug_assert!(!tcann.is_null()); // SAFETY: // - *tcann is a valid TCAnnotation (caller promises to treat it as read-only) - let annotation = unsafe { TCAnnotation::take_from_arg(tcann, TCAnnotation::default()) }; + let annotation = unsafe { TCAnnotation::take_val_from_arg(tcann, TCAnnotation::default()) }; drop(annotation); } diff --git a/lib/src/kv.rs b/lib/src/kv.rs index 3b24e25cf..ce79a0df9 100644 --- a/lib/src/kv.rs +++ b/lib/src/kv.rs @@ -18,18 +18,18 @@ impl PassByValue for TCKV { // SAFETY: // - self is owned, so we can take ownership of this TCString // - self.key is a valid, non-null TCString (see type docstring) - let key = unsafe { TCString::take_from_arg(self.key) }; + let key = unsafe { TCString::take_from_ptr_arg(self.key) }; // SAFETY: (same) - let value = unsafe { TCString::take_from_arg(self.value) }; + let value = unsafe { TCString::take_from_ptr_arg(self.value) }; (key, value) } fn as_ctype((key, value): Self::RustType) -> Self { TCKV { // SAFETY: caller assumes ownership of this value - key: unsafe { key.return_val() }, + key: unsafe { key.return_ptr() }, // SAFETY: caller assumes ownership of this value - value: unsafe { value.return_val() }, + value: unsafe { value.return_ptr() }, } } } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index b89df1091..655120669 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -75,7 +75,7 @@ where F: FnOnce(&mut Replica) -> anyhow::Result, { // SAFETY: see type docstring - let rep: &mut TCReplica = unsafe { TCReplica::from_arg_ref_mut(rep) }; + let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; if rep.mut_borrowed { panic!("replica is borrowed and cannot be used"); } @@ -97,7 +97,7 @@ pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { .into_storage() .expect("in-memory always succeeds"); // SAFETY: see type docstring - unsafe { TCReplica::from(Replica::new(storage)).return_val() } + unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } } /// Create a new TCReplica with an on-disk database having the given filename. On error, a string @@ -108,7 +108,7 @@ pub unsafe extern "C" fn tc_replica_new_on_disk<'a>( error_out: *mut *mut TCString, ) -> *mut TCReplica { // SAFETY: see TCString docstring - let path = unsafe { TCString::take_from_arg(path) }; + let path = unsafe { TCString::take_from_ptr_arg(path) }; let storage_res = StorageConfig::OnDisk { taskdb_dir: path.to_path_buf(), } @@ -119,7 +119,7 @@ pub unsafe extern "C" fn tc_replica_new_on_disk<'a>( Err(e) => { if !error_out.is_null() { unsafe { - *error_out = err_to_tcstring(e).return_val(); + *error_out = err_to_tcstring(e).return_ptr(); } } return std::ptr::null_mut(); @@ -127,7 +127,7 @@ pub unsafe extern "C" fn tc_replica_new_on_disk<'a>( }; // SAFETY: see type docstring - unsafe { TCReplica::from(Replica::new(storage)).return_val() } + unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } } /// Get a list of all tasks in the replica. @@ -147,9 +147,9 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList .map(|(_uuid, t)| { NonNull::new( // SAFETY: see TCTask docstring - unsafe { TCTask::from(t).return_val() }, + unsafe { TCTask::from(t).return_ptr() }, ) - .expect("TCTask::return_val returned NULL") + .expect("TCTask::return_ptr returned NULL") }) .collect(); Ok(TCTaskList::return_val(tasks)) @@ -187,7 +187,7 @@ pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCW |rep| { let ws = rep.working_set()?; // SAFETY: caller promises to free this task - Ok(unsafe { TCWorkingSet::return_val(ws.into()) }) + Ok(unsafe { TCWorkingSet::return_ptr(ws.into()) }) }, std::ptr::null_mut(), ) @@ -203,10 +203,10 @@ pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid rep, |rep| { // SAFETY: see TCUuid docstring - let uuid = unsafe { TCUuid::from_arg(tcuuid) }; + let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; if let Some(task) = rep.get_task(uuid)? { // SAFETY: caller promises to free this task - Ok(unsafe { TCTask::from(task).return_val() }) + Ok(unsafe { TCTask::from(task).return_ptr() }) } else { Ok(std::ptr::null_mut()) } @@ -225,13 +225,13 @@ pub unsafe extern "C" fn tc_replica_new_task( description: *mut TCString, ) -> *mut TCTask { // SAFETY: see TCString docstring - let description = unsafe { TCString::take_from_arg(description) }; + let description = unsafe { TCString::take_from_ptr_arg(description) }; wrap( rep, |rep| { let task = rep.new_task(status.into(), description.as_str()?.to_string())?; // SAFETY: caller promises to free this task - Ok(unsafe { TCTask::from(task).return_val() }) + Ok(unsafe { TCTask::from(task).return_ptr() }) }, std::ptr::null_mut(), ) @@ -249,10 +249,10 @@ pub unsafe extern "C" fn tc_replica_import_task_with_uuid( rep, |rep| { // SAFETY: see TCUuid docstring - let uuid = unsafe { TCUuid::from_arg(tcuuid) }; + let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; let task = rep.import_task_with_uuid(uuid)?; // SAFETY: caller promises to free this task - Ok(unsafe { TCTask::from(task).return_val() }) + Ok(unsafe { TCTask::from(task).return_ptr() }) }, std::ptr::null_mut(), ) @@ -325,10 +325,10 @@ pub unsafe extern "C" fn tc_replica_rebuild_working_set( #[no_mangle] pub unsafe extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'static> { // SAFETY: see type docstring - let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref_mut(rep) }; + let rep: &'a mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; if let Some(tcstring) = rep.error.take() { // SAFETY: see TCString docstring - unsafe { tcstring.return_val() } + unsafe { tcstring.return_ptr() } } else { std::ptr::null_mut() } @@ -339,7 +339,7 @@ pub unsafe extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCStr #[no_mangle] pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) { // SAFETY: see type docstring - let replica = unsafe { TCReplica::take_from_arg(rep) }; + let replica = unsafe { TCReplica::take_from_ptr_arg(rep) }; if replica.mut_borrowed { panic!("replica is borrowed and cannot be freed"); } diff --git a/lib/src/string.rs b/lib/src/string.rs index af40714d9..442ffd0e9 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -194,7 +194,7 @@ pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCS // - cstr's content will not change before it is destroyed (promised by caller) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; // SAFETY: see docstring - unsafe { TCString::CStr(cstr).return_val() } + unsafe { TCString::CStr(cstr).return_ptr() } } /// Create a new TCString by cloning the content of the given C string. The resulting TCString @@ -209,7 +209,7 @@ pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCSt // - cstr's content will not change before it is destroyed (by C convention) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; // SAFETY: see docstring - unsafe { TCString::CString(cstr.into()).return_val() } + unsafe { TCString::CString(cstr.into()).return_ptr() } } /// Create a new TCString containing the given string with the given length. This allows creation @@ -246,7 +246,7 @@ pub unsafe extern "C" fn tc_string_clone_with_len( }; // SAFETY: see docstring - unsafe { tcstring.return_val() } + unsafe { tcstring.return_ptr() } } /// Get the content of the string as a regular C string. The given string must not be NULL. The @@ -263,7 +263,7 @@ pub unsafe extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const li // - tcstring is not NULL (promised by caller) // - lifetime of tcstring outlives the lifetime of this function // - lifetime of tcstring outlives the lifetime of the returned pointer (promised by caller) - let tcstring = unsafe { TCString::from_arg_ref_mut(tcstring) }; + let tcstring = unsafe { TCString::from_ptr_arg_ref_mut(tcstring) }; // if we have a String, we need to consume it and turn it into // a CString. @@ -293,7 +293,7 @@ pub unsafe extern "C" fn tc_string_content_with_len( // - tcstring is not NULL (promised by caller) // - lifetime of tcstring outlives the lifetime of this function // - lifetime of tcstring outlives the lifetime of the returned pointer (promised by caller) - let tcstring = unsafe { TCString::from_arg_ref(tcstring) }; + let tcstring = unsafe { TCString::from_ptr_arg_ref(tcstring) }; let bytes = tcstring.as_bytes(); @@ -301,7 +301,7 @@ pub unsafe extern "C" fn tc_string_content_with_len( // - len_out is not NULL (promised by caller) // - len_out points to valid memory (promised by caller) // - len_out is properly aligned (C convention) - unsafe { usize::to_arg_out(bytes.len(), len_out) }; + unsafe { usize::val_to_arg_out(bytes.len(), len_out) }; bytes.as_ptr() as *const libc::c_char } @@ -312,7 +312,7 @@ pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { // SAFETY: // - tcstring is not NULL (promised by caller) // - caller is exclusive owner of tcstring (promised by caller) - drop(unsafe { TCString::take_from_arg(tcstring) }); + drop(unsafe { TCString::take_from_ptr_arg(tcstring) }); } /// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after diff --git a/lib/src/task.rs b/lib/src/task.rs index 421072373..24dc1fcfc 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -63,7 +63,7 @@ impl TCTask { // - tcreplica is not null (promised by caller) // - tcreplica outlives the pointer in this variant (promised by caller) let tcreplica_ref: &mut TCReplica = - unsafe { TCReplica::from_arg_ref_mut(tcreplica) }; + unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) }; let rep_ref = tcreplica_ref.borrow_mut(); Inner::Mutable(task.into_mut(rep_ref), tcreplica) } @@ -83,7 +83,7 @@ impl TCTask { // variant) // - tcreplica is still alive (promised by caller of to_mut) let tcreplica_ref: &mut TCReplica = - unsafe { TCReplica::from_arg_ref_mut(tcreplica) }; + unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) }; tcreplica_ref.release_borrow(); Inner::Immutable(task.into_immut()) } @@ -110,7 +110,7 @@ where // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let tctask: &'a mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; let task: &'a Task = match &tctask.inner { Inner::Immutable(t) => t, Inner::Mutable(t, _) => t.deref(), @@ -130,7 +130,7 @@ where // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let tctask: &'a mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; let task: &'a mut TaskMut = match tctask.inner { Inner::Immutable(_) => panic!("Task is immutable"), Inner::Mutable(ref mut t, _) => t, @@ -209,7 +209,7 @@ pub unsafe extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut T // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let tctask: &'a 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, @@ -227,7 +227,7 @@ pub unsafe extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let tctask: &'a mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; tctask.to_immut(); } @@ -269,7 +269,7 @@ pub unsafe extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut wrap(task, |task| { let descr: TCString = task.get_description().into(); // SAFETY: see TCString docstring - unsafe { descr.return_val() } + unsafe { descr.return_ptr() } }) } @@ -308,7 +308,7 @@ pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { #[no_mangle] pub unsafe extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { // SAFETY: see TCString docstring - let tcstring = unsafe { TCString::take_from_arg(tag) }; + let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap(task, |task| { if let Ok(tag) = Tag::try_from(tcstring) { task.has_tag(&tag) @@ -330,9 +330,9 @@ pub unsafe extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStringList .map(|t| { NonNull::new( // SAFETY: see TCString docstring - unsafe { TCString::from(t.as_ref()).return_val() }, + unsafe { TCString::from(t.as_ref()).return_ptr() }, ) - .expect("TCString::return_val() returned NULL") + .expect("TCString::return_ptr() returned NULL") }) .collect(); TCStringList::return_val(vec) @@ -367,12 +367,12 @@ pub unsafe extern "C" fn tc_task_get_uda<'a>( key: *mut TCString<'a>, ) -> *mut TCString<'static> { wrap(task, |task| { - if let Ok(ns) = unsafe { TCString::take_from_arg(ns) }.as_str() { - if let Ok(key) = unsafe { TCString::take_from_arg(key) }.as_str() { + if let Ok(ns) = unsafe { TCString::take_from_ptr_arg(ns) }.as_str() { + if let Ok(key) = unsafe { TCString::take_from_ptr_arg(key) }.as_str() { if let Some(value) = task.get_uda(ns, key) { // SAFETY: // - caller will free this string (caller promises) - return unsafe { TCString::return_val(value.into()) }; + return unsafe { TCString::return_ptr(value.into()) }; } } } @@ -389,11 +389,11 @@ pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>( key: *mut TCString<'a>, ) -> *mut TCString<'static> { wrap(task, |task| { - if let Ok(key) = unsafe { TCString::take_from_arg(key) }.as_str() { + if let Ok(key) = unsafe { TCString::take_from_ptr_arg(key) }.as_str() { if let Some(value) = task.get_legacy_uda(key) { // SAFETY: // - caller will free this string (caller promises) - return unsafe { TCString::return_val(value.into()) }; + return unsafe { TCString::return_ptr(value.into()) }; } } std::ptr::null_mut() @@ -461,7 +461,7 @@ pub unsafe extern "C" fn tc_task_set_description<'a>( description: *mut TCString, ) -> TCResult { // SAFETY: see TCString docstring - let description = unsafe { TCString::take_from_arg(description) }; + let description = unsafe { TCString::take_from_ptr_arg(description) }; wrap_mut( task, |task| { @@ -577,7 +577,7 @@ pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { #[no_mangle] pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { // SAFETY: see TCString docstring - let tcstring = unsafe { TCString::take_from_arg(tag) }; + let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap_mut( task, |task| { @@ -593,7 +593,7 @@ pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) #[no_mangle] pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { // SAFETY: see TCString docstring - let tcstring = unsafe { TCString::take_from_arg(tag) }; + let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap_mut( task, |task| { @@ -613,7 +613,7 @@ pub unsafe extern "C" fn tc_task_add_annotation( ) -> TCResult { // SAFETY: see TCAnnotation docstring let (entry, description) = - unsafe { TCAnnotation::take_from_arg(annotation, TCAnnotation::default()) }; + unsafe { TCAnnotation::take_val_from_arg(annotation, TCAnnotation::default()) }; wrap_mut( task, |task| { @@ -647,11 +647,11 @@ pub unsafe extern "C" fn tc_task_set_uda<'a>( value: *mut TCString, ) -> TCResult { // SAFETY: see TCString docstring - let ns = unsafe { TCString::take_from_arg(ns) }; + let ns = unsafe { TCString::take_from_ptr_arg(ns) }; // SAFETY: see TCString docstring - let key = unsafe { TCString::take_from_arg(key) }; + let key = unsafe { TCString::take_from_ptr_arg(key) }; // SAFETY: see TCString docstring - let value = unsafe { TCString::take_from_arg(value) }; + let value = unsafe { TCString::take_from_ptr_arg(value) }; wrap_mut( task, |task| { @@ -674,9 +674,9 @@ pub unsafe extern "C" fn tc_task_remove_uda<'a>( key: *mut TCString, ) -> TCResult { // SAFETY: see TCString docstring - let ns = unsafe { TCString::take_from_arg(ns) }; + let ns = unsafe { TCString::take_from_ptr_arg(ns) }; // SAFETY: see TCString docstring - let key = unsafe { TCString::take_from_arg(key) }; + let key = unsafe { TCString::take_from_ptr_arg(key) }; wrap_mut( task, |task| { @@ -695,9 +695,9 @@ pub unsafe extern "C" fn tc_task_set_legacy_uda<'a>( value: *mut TCString, ) -> TCResult { // SAFETY: see TCString docstring - let key = unsafe { TCString::take_from_arg(key) }; + let key = unsafe { TCString::take_from_ptr_arg(key) }; // SAFETY: see TCString docstring - let value = unsafe { TCString::take_from_arg(value) }; + let value = unsafe { TCString::take_from_ptr_arg(value) }; wrap_mut( task, |task| { @@ -715,7 +715,7 @@ pub unsafe extern "C" fn tc_task_remove_legacy_uda<'a>( key: *mut TCString, ) -> TCResult { // SAFETY: see TCString docstring - let key = unsafe { TCString::take_from_arg(key) }; + let key = unsafe { TCString::take_from_ptr_arg(key) }; wrap_mut( task, |task| { @@ -734,9 +734,9 @@ pub unsafe extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<' // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let task: &'a mut TCTask = unsafe { TCTask::from_arg_ref_mut(task) }; + let task: &'a mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; if let Some(tcstring) = task.error.take() { - unsafe { tcstring.return_val() } + unsafe { tcstring.return_ptr() } } else { std::ptr::null_mut() } @@ -751,7 +751,7 @@ pub unsafe extern "C" fn tc_task_free<'a>(task: *mut TCTask) { // SAFETY: // - rep is not NULL (promised by caller) // - caller will not use the TCTask after this (promised by caller) - let mut tctask = unsafe { TCTask::take_from_arg(task) }; + let mut tctask = unsafe { TCTask::take_from_ptr_arg(task) }; // convert to immut if it was mutable tctask.to_immut(); diff --git a/lib/src/traits.rs b/lib/src/traits.rs index 6a4ad2735..5cd5746ff 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -27,7 +27,7 @@ pub(crate) trait PassByValue: Sized { /// /// `self` must be a valid instance of Self. This is typically ensured either by requiring /// that C code not modify it, or by defining the valid values in C comments. - unsafe fn from_arg(arg: Self) -> Self::RustType { + unsafe fn val_from_arg(arg: Self) -> Self::RustType { // SAFETY: // - arg is a valid CType (promised by caller) unsafe { arg.from_ctype() } @@ -38,15 +38,15 @@ pub(crate) trait PassByValue: Sized { /// /// # Safety /// - /// `*arg` must be a valid CType, as with [`from_arg`]. - unsafe fn take_from_arg(arg: *mut Self, mut replacement: Self) -> Self::RustType { + /// `*arg` must be a valid CType, as with [`val_from_arg`]. + unsafe fn take_val_from_arg(arg: *mut Self, mut replacement: Self) -> Self::RustType { // SAFETY: // - arg is valid (promised by caller) // - replacement is valid (guaranteed by Rust) unsafe { std::ptr::swap(arg, &mut replacement) }; // SAFETY: // - replacement (formerly *arg) is a valid CType (promised by caller) - unsafe { PassByValue::from_arg(replacement) } + unsafe { PassByValue::val_from_arg(replacement) } } /// Return a value to C @@ -60,7 +60,7 @@ pub(crate) trait PassByValue: Sized { /// /// `arg_out` must not be NULL and must be properly aligned and pointing to valid memory /// of the size of CType. - unsafe fn to_arg_out(val: Self::RustType, arg_out: *mut Self) { + unsafe fn val_to_arg_out(val: Self::RustType, arg_out: *mut Self) { debug_assert!(!arg_out.is_null()); // SAFETY: // - arg_out is not NULL (promised by caller, asserted) @@ -81,13 +81,13 @@ pub(crate) trait PassByValue: Sized { /// - the pointer must not be NULL; /// - the pointer must be one previously returned from Rust; and /// - the memory addressed by the pointer must never be modified by C code. -/// - For `from_arg_ref`, the value must not be modified during the call to the Rust function -/// - For `from_arg_ref_mut`, the value must not be accessed (read or write) during the call +/// - For `from_ptr_arg_ref`, the value must not be modified during the call to the Rust function +/// - For `from_ptr_arg_ref_mut`, the value must not be accessed (read or write) during the call /// (these last two points are trivially ensured by all TC… types being non-threadsafe) -/// - For `take_from_arg`, the pointer becomes invalid and must not be used in _any way_ after it +/// - For `take_from_ptr_arg`, the pointer becomes invalid and must not be used in _any way_ after it /// is passed to the Rust function. -/// - For `return_val` and `to_arg_out`, it is the C caller's responsibility to later free the value. -/// - For `to_arg_out`, `arg_out` must not be NULL and must be properly aligned and pointing to +/// - For `return_ptr` and `ptr_to_arg_out`, it is the C caller's responsibility to later free the value. +/// - For `ptr_to_arg_out`, `arg_out` must not be NULL and must be properly aligned and pointing to /// valid memory. /// /// These requirements should be expressed in the C documentation for the type implementing this @@ -98,7 +98,7 @@ pub(crate) trait PassByPointer: Sized { /// # Safety /// /// See trait documentation. - unsafe fn take_from_arg(arg: *mut Self) -> Self { + unsafe fn take_from_ptr_arg(arg: *mut Self) -> Self { debug_assert!(!arg.is_null()); // SAFETY: see trait documentation unsafe { *(Box::from_raw(arg)) } @@ -109,7 +109,7 @@ pub(crate) trait PassByPointer: Sized { /// # Safety /// /// See trait documentation. - unsafe fn from_arg_ref<'a>(arg: *const Self) -> &'a Self { + unsafe fn from_ptr_arg_ref<'a>(arg: *const Self) -> &'a Self { debug_assert!(!arg.is_null()); // SAFETY: see trait documentation unsafe { &*arg } @@ -120,7 +120,7 @@ pub(crate) trait PassByPointer: Sized { /// # Safety /// /// See trait documentation. - unsafe fn from_arg_ref_mut<'a>(arg: *mut Self) -> &'a mut Self { + unsafe fn from_ptr_arg_ref_mut<'a>(arg: *mut Self) -> &'a mut Self { debug_assert!(!arg.is_null()); // SAFETY: see trait documentation unsafe { &mut *arg } @@ -131,7 +131,7 @@ pub(crate) trait PassByPointer: Sized { /// # Safety /// /// See trait documentation. - unsafe fn return_val(self) -> *mut Self { + unsafe fn return_ptr(self) -> *mut Self { Box::into_raw(Box::new(self)) } @@ -140,10 +140,10 @@ pub(crate) trait PassByPointer: Sized { /// # Safety /// /// See trait documentation. - unsafe fn to_arg_out(self, arg_out: *mut *mut Self) { + unsafe fn ptr_to_arg_out(self, arg_out: *mut *mut Self) { // SAFETY: see trait documentation unsafe { - *arg_out = self.return_val(); + *arg_out = self.return_ptr(); } } } @@ -154,7 +154,7 @@ pub(crate) trait PassByPointer: Sized { /// required trait functions just fetch and set these fields. The PassByValue trait will be /// implemented automatically, converting between the C type and `Vec`. For most cases, /// it is only necessary to implement `tc_.._free` that first calls -/// `PassByValue::take_from_arg(arg, CList::null_value())` to take the existing value and +/// `PassByValue::take_val_from_arg(arg, CList::null_value())` to take the existing value and /// replace it with the null value; then one of hte `drop_.._list(..)` functions to drop the resulting /// vector and all of the objects it points to. /// @@ -210,14 +210,14 @@ where // SAFETY: // - *list is a valid CL (caller promises to treat it as read-only) - let mut vec = unsafe { CL::take_from_arg(list, CL::null_value()) }; + let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; // first, drop each of the elements in turn for e in vec.drain(..) { // SAFETY: // - e is a valid Element (caller promisd not to change it) // - Vec::drain has invalidated this entry (value is owned) - drop(unsafe { PassByValue::from_arg(e) }); + drop(unsafe { PassByValue::val_from_arg(e) }); } // then drop the vector drop(vec); @@ -240,14 +240,14 @@ where debug_assert!(!list.is_null()); // SAFETY: // - *list is a valid CL (caller promises to treat it as read-only) - let mut vec = unsafe { CL::take_from_arg(list, CL::null_value()) }; + let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; // first, drop each of the elements in turn for e in vec.drain(..) { // SAFETY: // - e is a valid Element (caller promised not to change it) // - Vec::drain has invalidated this entry (value is owned) - drop(unsafe { PassByPointer::take_from_arg(e.as_ptr()) }); + drop(unsafe { PassByPointer::take_from_ptr_arg(e.as_ptr()) }); } // then drop the vector drop(vec); diff --git a/lib/src/uda.rs b/lib/src/uda.rs index 27134d80e..4c0fe03f2 100644 --- a/lib/src/uda.rs +++ b/lib/src/uda.rs @@ -29,16 +29,16 @@ impl PassByValue for TCUDA { // SAFETY: // - self is owned, so we can take ownership of this TCString // - self.ns is a valid, non-null TCString (NULL just checked) - Some(unsafe { TCString::take_from_arg(self.ns) }) + Some(unsafe { TCString::take_from_ptr_arg(self.ns) }) }, // SAFETY: // - self is owned, so we can take ownership of this TCString // - self.key is a valid, non-null TCString (see type docstring) - key: unsafe { TCString::take_from_arg(self.key) }, + key: unsafe { TCString::take_from_ptr_arg(self.key) }, // SAFETY: // - self is owned, so we can take ownership of this TCString // - self.value is a valid, non-null TCString (see type docstring) - value: unsafe { TCString::take_from_arg(self.value) }, + value: unsafe { TCString::take_from_ptr_arg(self.value) }, } } @@ -46,14 +46,14 @@ impl PassByValue for TCUDA { TCUDA { // SAFETY: caller assumes ownership of this value ns: if let Some(ns) = uda.ns { - unsafe { ns.return_val() } + unsafe { ns.return_ptr() } } else { std::ptr::null_mut() }, // SAFETY: caller assumes ownership of this value - key: unsafe { uda.key.return_val() }, + key: unsafe { uda.key.return_ptr() }, // SAFETY: caller assumes ownership of this value - value: unsafe { uda.value.return_val() }, + value: unsafe { uda.value.return_ptr() }, } } } @@ -107,7 +107,7 @@ pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUDA) { debug_assert!(!tcuda.is_null()); // SAFETY: // - *tcuda is a valid TCUDA (caller promises to treat it as read-only) - let uda = unsafe { TCUDA::take_from_arg(tcuda, TCUDA::default()) }; + let uda = unsafe { TCUDA::take_val_from_arg(tcuda, TCUDA::default()) }; drop(uda); } diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 8a26e4d11..f3d0528b3 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -88,7 +88,7 @@ pub unsafe extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_ch }; // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { TCUuid::from_arg(tcuuid) }; + let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; uuid.to_hyphenated().encode_lower(buf); } @@ -98,10 +98,10 @@ pub unsafe extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_ch pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { TCUuid::from_arg(tcuuid) }; + let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; let s = uuid.to_string(); // SAFETY: see TCString docstring - unsafe { TCString::from(s).return_val() } + unsafe { TCString::from(s).return_ptr() } } /// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given @@ -111,13 +111,13 @@ pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut T debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null()); // SAFETY: see TCString docstring - let s = unsafe { TCString::take_from_arg(s) }; + let s = unsafe { TCString::take_from_ptr_arg(s) }; if let Ok(s) = s.as_str() { if let Ok(u) = Uuid::parse_str(s) { // SAFETY: // - uuid_out is not NULL (promised by caller) // - alignment is not required - unsafe { TCUuid::to_arg_out(u, uuid_out) }; + unsafe { TCUuid::val_to_arg_out(u, uuid_out) }; return TCResult::Ok; } } diff --git a/lib/src/workingset.rs b/lib/src/workingset.rs index 91f73bbd0..02780e50f 100644 --- a/lib/src/workingset.rs +++ b/lib/src/workingset.rs @@ -25,7 +25,7 @@ where // SAFETY: // - ws is not null (promised by caller) // - ws outlives 'a (promised by caller) - let tcws: &'a TCWorkingSet = unsafe { TCWorkingSet::from_arg_ref(ws) }; + let tcws: &'a TCWorkingSet = unsafe { TCWorkingSet::from_ptr_arg_ref(ws) }; f(&tcws.0) } @@ -55,7 +55,7 @@ pub unsafe extern "C" fn tc_working_set_by_index( // SAFETY: // - uuid_out is not NULL (promised by caller) // - alignment is not required - unsafe { TCUuid::to_arg_out(uuid, uuid_out) }; + unsafe { TCUuid::val_to_arg_out(uuid, uuid_out) }; true } else { false @@ -70,7 +70,7 @@ pub unsafe extern "C" fn tc_working_set_by_uuid(ws: *mut TCWorkingSet, uuid: TCU wrap(ws, |ws| { // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { TCUuid::from_arg(uuid) }; + let uuid: Uuid = unsafe { TCUuid::val_from_arg(uuid) }; ws.by_uuid(uuid).unwrap_or(0) }) } @@ -82,6 +82,6 @@ pub unsafe extern "C" fn tc_working_set_free(ws: *mut TCWorkingSet) { // SAFETY: // - rep is not NULL (promised by caller) // - caller will not use the TCWorkingSet after this (promised by caller) - let ws = unsafe { TCWorkingSet::take_from_arg(ws) }; + let ws = unsafe { TCWorkingSet::take_from_ptr_arg(ws) }; drop(ws); } From bbb7b64842eff9dbb6bb13af6f0d94ee2224756f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 03:19:11 +0000 Subject: [PATCH 502/548] review safety comments --- lib/src/annotation.rs | 13 +++--- lib/src/kv.rs | 9 ++-- lib/src/traits.rs | 96 +++++++++++++++++++------------------------ 3 files changed, 57 insertions(+), 61 deletions(-) diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index b05b8992a..a1fe78da5 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -19,11 +19,12 @@ impl PassByValue for TCAnnotation { unsafe fn from_ctype(self) -> Self::RustType { // SAFETY: // - any time_t value is valid - // - time_t is not zero, so unwrap is safe (see type docstring) - let entry = unsafe { self.entry.from_ctype() }.unwrap(); + // - time_t is copy, so ownership is not important + let entry = unsafe { self.entry.val_from_arg() }.unwrap(); // SAFETY: + // - self.description is not NULL (field docstring) + // - self.description came from return_ptr in as_ctype // - self is owned, so we can take ownership of this TCString - // - self.description is a valid, non-null TCString (see type docstring) let description = unsafe { TCString::take_from_ptr_arg(self.description) }; (entry, description) } @@ -31,7 +32,8 @@ impl PassByValue for TCAnnotation { fn as_ctype((entry, description): Self::RustType) -> Self { TCAnnotation { entry: libc::time_t::as_ctype(Some(entry)), - // SAFETY: caller assumes ownership of this value + // SAFETY: + // - ownership of the TCString tied to ownership of Self description: unsafe { description.return_ptr() }, } } @@ -84,7 +86,8 @@ impl CList for TCAnnotationList { pub unsafe extern "C" fn tc_annotation_free(tcann: *mut TCAnnotation) { debug_assert!(!tcann.is_null()); // SAFETY: - // - *tcann is a valid TCAnnotation (caller promises to treat it as read-only) + // - tcann is not NULL + // - *tcann is a valid TCAnnotation (caller promised to treat it as read-only) let annotation = unsafe { TCAnnotation::take_val_from_arg(tcann, TCAnnotation::default()) }; drop(annotation); } diff --git a/lib/src/kv.rs b/lib/src/kv.rs index ce79a0df9..1a6f61c00 100644 --- a/lib/src/kv.rs +++ b/lib/src/kv.rs @@ -16,8 +16,9 @@ impl PassByValue for TCKV { unsafe fn from_ctype(self) -> Self::RustType { // SAFETY: + // - self.key is not NULL (field docstring) + // - self.key came from return_ptr in as_ctype // - self is owned, so we can take ownership of this TCString - // - self.key is a valid, non-null TCString (see type docstring) let key = unsafe { TCString::take_from_ptr_arg(self.key) }; // SAFETY: (same) let value = unsafe { TCString::take_from_ptr_arg(self.value) }; @@ -26,9 +27,11 @@ impl PassByValue for TCKV { fn as_ctype((key, value): Self::RustType) -> Self { TCKV { - // SAFETY: caller assumes ownership of this value + // SAFETY: + // - ownership of the TCString tied to ownership of Self key: unsafe { key.return_ptr() }, - // SAFETY: caller assumes ownership of this value + // SAFETY: + // - ownership of the TCString tied to ownership of Self value: unsafe { value.return_ptr() }, } } diff --git a/lib/src/traits.rs b/lib/src/traits.rs index 5cd5746ff..57537d212 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -25,8 +25,10 @@ pub(crate) trait PassByValue: Sized { /// /// # Safety /// - /// `self` must be a valid instance of Self. This is typically ensured either by requiring - /// that C code not modify it, or by defining the valid values in C comments. + /// - `self` must be a valid instance of the C type. This is typically ensured either by + /// requiring that C code not modify it, or by defining the valid values in C comments. + /// - if RustType is not Copy, then arg must not be used by the caller after calling this + /// function unsafe fn val_from_arg(arg: Self) -> Self::RustType { // SAFETY: // - arg is a valid CType (promised by caller) @@ -38,11 +40,12 @@ pub(crate) trait PassByValue: Sized { /// /// # Safety /// - /// `*arg` must be a valid CType, as with [`val_from_arg`]. + /// - arg must not be NULL + /// - *arg must be a valid, properly aligned instance of the C type unsafe fn take_val_from_arg(arg: *mut Self, mut replacement: Self) -> Self::RustType { // SAFETY: // - arg is valid (promised by caller) - // - replacement is valid (guaranteed by Rust) + // - replacement is valid and aligned (guaranteed by Rust) unsafe { std::ptr::swap(arg, &mut replacement) }; // SAFETY: // - replacement (formerly *arg) is a valid CType (promised by caller) @@ -58,8 +61,8 @@ pub(crate) trait PassByValue: Sized { /// /// # Safety /// - /// `arg_out` must not be NULL and must be properly aligned and pointing to valid memory - /// of the size of CType. + /// - `arg_out` must not be NULL and must be properly aligned and pointing to valid memory + /// of the size of CType. unsafe fn val_to_arg_out(val: Self::RustType, arg_out: *mut Self) { debug_assert!(!arg_out.is_null()); // SAFETY: @@ -71,36 +74,17 @@ pub(crate) trait PassByValue: Sized { /// Support for values passed to Rust by pointer. These are represented as opaque structs in C, /// and always handled as pointers. -/// -/// # Safety -/// -/// The functions provided by this trait are used directly in C interface functions, and make the -/// following expectations of the C code: -/// -/// - When passing a value to Rust (via the `…arg…` functions), -/// - the pointer must not be NULL; -/// - the pointer must be one previously returned from Rust; and -/// - the memory addressed by the pointer must never be modified by C code. -/// - For `from_ptr_arg_ref`, the value must not be modified during the call to the Rust function -/// - For `from_ptr_arg_ref_mut`, the value must not be accessed (read or write) during the call -/// (these last two points are trivially ensured by all TC… types being non-threadsafe) -/// - For `take_from_ptr_arg`, the pointer becomes invalid and must not be used in _any way_ after it -/// is passed to the Rust function. -/// - For `return_ptr` and `ptr_to_arg_out`, it is the C caller's responsibility to later free the value. -/// - For `ptr_to_arg_out`, `arg_out` must not be NULL and must be properly aligned and pointing to -/// valid memory. -/// -/// These requirements should be expressed in the C documentation for the type implementing this -/// trait. pub(crate) trait PassByPointer: Sized { /// Take a value from C as an argument. /// /// # Safety /// - /// See trait documentation. + /// - arg must not be NULL + /// - arg must be a value returned from Box::into_raw (via return_ptr or ptr_to_arg_out) + /// - arg becomes invalid and must not be used after this call unsafe fn take_from_ptr_arg(arg: *mut Self) -> Self { debug_assert!(!arg.is_null()); - // SAFETY: see trait documentation + // SAFETY: see docstring unsafe { *(Box::from_raw(arg)) } } @@ -108,10 +92,13 @@ pub(crate) trait PassByPointer: Sized { /// /// # Safety /// - /// See trait documentation. + /// - arg must not be NULL + /// - *arg must be a valid instance of Self + /// - arg must be valid for the lifetime assigned by the caller + /// - arg must not be modified by anything else during that lifetime unsafe fn from_ptr_arg_ref<'a>(arg: *const Self) -> &'a Self { debug_assert!(!arg.is_null()); - // SAFETY: see trait documentation + // SAFETY: see docstring unsafe { &*arg } } @@ -119,10 +106,13 @@ pub(crate) trait PassByPointer: Sized { /// /// # Safety /// - /// See trait documentation. + /// - arg must not be NULL + /// - *arg must be a valid instance of Self + /// - arg must be valid for the lifetime assigned by the caller + /// - arg must not be accessed by anything else during that lifetime unsafe fn from_ptr_arg_ref_mut<'a>(arg: *mut Self) -> &'a mut Self { debug_assert!(!arg.is_null()); - // SAFETY: see trait documentation + // SAFETY: see docstring unsafe { &mut *arg } } @@ -130,7 +120,7 @@ pub(crate) trait PassByPointer: Sized { /// /// # Safety /// - /// See trait documentation. + /// - the caller must ensure that the value is eventually freed unsafe fn return_ptr(self) -> *mut Self { Box::into_raw(Box::new(self)) } @@ -139,31 +129,31 @@ pub(crate) trait PassByPointer: Sized { /// /// # Safety /// - /// See trait documentation. + /// - the caller must ensure that the value is eventually freed + /// - arg_out must not be NULL + /// - arg_out must point to valid, properly aligned memory for a pointer value unsafe fn ptr_to_arg_out(self, arg_out: *mut *mut Self) { - // SAFETY: see trait documentation - unsafe { - *arg_out = self.return_ptr(); - } + debug_assert!(!arg_out.is_null()); + // SAFETY: see docstring + unsafe { *arg_out = self.return_ptr() }; } } /// Support for C lists of objects referenced by value. /// /// The underlying C type should have three fields, containing items, length, and capacity. The -/// required trait functions just fetch and set these fields. The PassByValue trait will be -/// implemented automatically, converting between the C type and `Vec`. For most cases, -/// it is only necessary to implement `tc_.._free` that first calls -/// `PassByValue::take_val_from_arg(arg, CList::null_value())` to take the existing value and -/// replace it with the null value; then one of hte `drop_.._list(..)` functions to drop the resulting -/// vector and all of the objects it points to. +/// required trait functions just fetch and set these fields. /// -/// This can be used for objects referenced by pointer, too, with an Element type of `NonNull` +/// The PassByValue trait will be implemented automatically, converting between the C type and +/// `Vec`. +/// +/// For most cases, it is only necessary to implement `tc_.._free` that calls either +/// drop_value_list (if Element is PassByValue) or drop_pointer_list (if element is PassByPointer). /// /// # Safety /// /// The C type must be documented as read-only. None of the fields may be modified, nor anything -/// in the `items` array. +/// accessible via the `items` array. /// /// This class guarantees that the items pointer is non-NULL for any valid list (even when len=0). pub(crate) trait CList: Sized { @@ -209,14 +199,14 @@ where debug_assert!(!list.is_null()); // SAFETY: - // - *list is a valid CL (caller promises to treat it as read-only) + // - *list is a valid CL (promised by caller) let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; // first, drop each of the elements in turn for e in vec.drain(..) { // SAFETY: - // - e is a valid Element (caller promisd not to change it) - // - Vec::drain has invalidated this entry (value is owned) + // - e is a valid Element (promised by caller) + // - e is owned drop(unsafe { PassByValue::val_from_arg(e) }); } // then drop the vector @@ -239,14 +229,14 @@ where { debug_assert!(!list.is_null()); // SAFETY: - // - *list is a valid CL (caller promises to treat it as read-only) + // - *list is a valid CL (promised by caller) let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; // first, drop each of the elements in turn for e in vec.drain(..) { // SAFETY: - // - e is a valid Element (caller promised not to change it) - // - Vec::drain has invalidated this entry (value is owned) + // - e is a valid Element (promised by caller) + // - e is owned drop(unsafe { PassByPointer::take_from_ptr_arg(e.as_ptr()) }); } // then drop the vector From 51a854cfeff998c4a6dd4dd54687bf9caf476384 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 03:30:02 +0000 Subject: [PATCH 503/548] address some clippy lints --- lib/src/annotation.rs | 2 +- lib/src/lib.rs | 4 +++ lib/src/replica.rs | 13 ++++----- lib/src/task.rs | 62 +++++++++++++++++++++---------------------- lib/src/uuid.rs | 6 ++--- lib/src/workingset.rs | 4 +-- 6 files changed, 46 insertions(+), 45 deletions(-) diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index a1fe78da5..dede87431 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -20,7 +20,7 @@ impl PassByValue for TCAnnotation { // SAFETY: // - any time_t value is valid // - time_t is copy, so ownership is not important - let entry = unsafe { self.entry.val_from_arg() }.unwrap(); + let entry = unsafe { libc::time_t::val_from_arg(self.entry) }.unwrap(); // SAFETY: // - self.description is not NULL (field docstring) // - self.description came from return_ptr in as_ctype diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 4c66ec76d..b6a8f2eec 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -2,6 +2,10 @@ // Not working yet in stable - https://github.com/rust-lang/rust-clippy/issues/8020 // #![warn(clippy::undocumented_unsafe_blocks)] +// docstrings for extern "C" functions are reflected into C, and do not benefit +// from safety docs. +#![allow(clippy::missing_safety_doc)] + mod traits; mod util; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 655120669..314f8d375 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -103,7 +103,7 @@ pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { /// 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. #[no_mangle] -pub unsafe extern "C" fn tc_replica_new_on_disk<'a>( +pub unsafe extern "C" fn tc_replica_new_on_disk( path: *mut TCString, error_out: *mut *mut TCString, ) -> *mut TCReplica { @@ -169,7 +169,7 @@ pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUui let uuids: Vec<_> = rep .all_task_uuids()? .drain(..) - .map(|uuid| TCUuid::return_val(uuid)) + .map(TCUuid::return_val) .collect(); Ok(TCUuidList::return_val(uuids)) }, @@ -265,10 +265,7 @@ pub unsafe extern "C" fn tc_replica_import_task_with_uuid( /// 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. #[no_mangle] -pub unsafe extern "C" fn tc_replica_undo<'a>( - rep: *mut TCReplica, - undone_out: *mut i32, -) -> TCResult { +pub unsafe extern "C" fn tc_replica_undo(rep: *mut TCReplica, undone_out: *mut i32) -> TCResult { wrap( rep, |rep| { @@ -323,9 +320,9 @@ pub unsafe extern "C" fn tc_replica_rebuild_working_set( /// to this function will return NULL. The rep pointer must not be NULL. The caller must free the /// returned string. #[no_mangle] -pub unsafe extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> *mut TCString<'static> { // SAFETY: see type docstring - let rep: &'a mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; + let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; if let Some(tcstring) = rep.error.take() { // SAFETY: see TCString docstring unsafe { tcstring.return_ptr() } diff --git a/lib/src/task.rs b/lib/src/task.rs index 24dc1fcfc..5a7a446fc 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -103,15 +103,15 @@ impl From for TCTask { /// Utility function to get a shared reference to the underlying Task. All Task getters /// are error-free, so this does not handle errors. -fn wrap<'a, T, F>(task: *mut TCTask, f: F) -> T +fn wrap(task: *mut TCTask, f: F) -> T where F: FnOnce(&Task) -> T, { // SAFETY: // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - let task: &'a Task = match &tctask.inner { + // - task outlives this function (promised by caller) + let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; + let task: &Task = match &tctask.inner { Inner::Immutable(t) => t, Inner::Mutable(t, _) => t.deref(), Inner::Invalid => unreachable!(), @@ -123,15 +123,15 @@ where /// Utility function to get a mutable reference to the underlying Task. The /// TCTask must be mutable. The inner function may use `?` syntax to return an /// error, which will be represented with the `err_value` returned to C. -fn wrap_mut<'a, T, F>(task: *mut TCTask, f: F, err_value: T) -> T +fn wrap_mut(task: *mut TCTask, f: F, err_value: T) -> T where F: FnOnce(&mut TaskMut) -> anyhow::Result, { // SAFETY: // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let tctask: &'a mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - let task: &'a mut TaskMut = match tctask.inner { + // - task outlives this function (promised by caller) + let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; + let task: &mut TaskMut = match tctask.inner { Inner::Immutable(_) => panic!("Task is immutable"), Inner::Mutable(ref mut t, _) => t, Inner::Invalid => unreachable!(), @@ -205,11 +205,11 @@ impl CList for TCTaskList { /// if (!success) { ... } /// ``` #[no_mangle] -pub unsafe extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut TCReplica) { +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: &'a mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; + 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, @@ -223,11 +223,11 @@ pub unsafe extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut T /// /// 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<'a>(task: *mut TCTask) { +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: &'a mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; + let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; tctask.to_immut(); } @@ -239,7 +239,7 @@ pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { /// Get a task's status. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { +pub unsafe extern "C" fn tc_task_get_status(task: *mut TCTask) -> TCStatus { wrap(task, |task| task.get_status().into()) } @@ -265,7 +265,7 @@ pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). #[no_mangle] -pub unsafe extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> *mut TCString<'static> { wrap(task, |task| { let descr: TCString = task.get_description().into(); // SAFETY: see TCString docstring @@ -275,19 +275,19 @@ pub unsafe extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut /// Get the entry timestamp for a task (when it was created), or 0 if not set. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_entry<'a>(task: *mut TCTask) -> libc::time_t { +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())) } /// Get the wait timestamp for a task, or 0 if not set. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t { +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())) } /// Get the modified timestamp for a task, or 0 if not set. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_modified<'a>(task: *mut TCTask) -> libc::time_t { +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())) } @@ -306,7 +306,7 @@ pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { /// 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`. #[no_mangle] -pub unsafe extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { +pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: *mut TCString) -> bool { // SAFETY: see TCString docstring let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap(task, |task| { @@ -323,7 +323,7 @@ pub unsafe extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCStri /// 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. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStringList { +pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList { wrap(task, |task| { let vec: Vec>> = task .get_tags() @@ -344,7 +344,7 @@ pub unsafe extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStringList /// 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. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_annotations<'a>(task: *mut TCTask) -> TCAnnotationList { +pub unsafe extern "C" fn tc_task_get_annotations(task: *mut TCTask) -> TCAnnotationList { wrap(task, |task| { let vec: Vec = task .get_annotations() @@ -404,7 +404,7 @@ pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>( /// /// Legacy UDAs are represented with an empty string in the ns field. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_udas<'a>(task: *mut TCTask) -> TCUDAList { +pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUDAList { wrap(task, |task| { let vec: Vec = task .get_udas() @@ -425,7 +425,7 @@ pub unsafe extern "C" fn tc_task_get_udas<'a>(task: *mut TCTask) -> TCUDAList { /// All TCUDAs in this list have a NULL ns field. The entire UDA key is /// included in the key field. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_legacy_udas<'a>(task: *mut TCTask) -> TCUDAList { +pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUDAList { wrap(task, |task| { let vec: Vec = task .get_legacy_udas() @@ -443,7 +443,7 @@ pub unsafe extern "C" fn tc_task_get_legacy_udas<'a>(task: *mut TCTask) -> TCUDA /// Set a mutable task's status. #[no_mangle] -pub unsafe extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> TCResult { +pub unsafe extern "C" fn tc_task_set_status(task: *mut TCTask, status: TCStatus) -> TCResult { wrap_mut( task, |task| { @@ -456,7 +456,7 @@ pub unsafe extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCSta /// Set a mutable task's description. #[no_mangle] -pub unsafe extern "C" fn tc_task_set_description<'a>( +pub unsafe extern "C" fn tc_task_set_description( task: *mut TCTask, description: *mut TCString, ) -> TCResult { @@ -640,7 +640,7 @@ pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64 /// Set a UDA on a mutable task. #[no_mangle] -pub unsafe extern "C" fn tc_task_set_uda<'a>( +pub unsafe extern "C" fn tc_task_set_uda( task: *mut TCTask, ns: *mut TCString, key: *mut TCString, @@ -668,7 +668,7 @@ pub unsafe extern "C" fn tc_task_set_uda<'a>( /// Remove a UDA fraom a mutable task. #[no_mangle] -pub unsafe extern "C" fn tc_task_remove_uda<'a>( +pub unsafe extern "C" fn tc_task_remove_uda( task: *mut TCTask, ns: *mut TCString, key: *mut TCString, @@ -689,7 +689,7 @@ pub unsafe extern "C" fn tc_task_remove_uda<'a>( /// Set a legacy UDA on a mutable task. #[no_mangle] -pub unsafe extern "C" fn tc_task_set_legacy_uda<'a>( +pub unsafe extern "C" fn tc_task_set_legacy_uda( task: *mut TCTask, key: *mut TCString, value: *mut TCString, @@ -710,7 +710,7 @@ pub unsafe extern "C" fn tc_task_set_legacy_uda<'a>( /// Remove a UDA fraom a mutable task. #[no_mangle] -pub unsafe extern "C" fn tc_task_remove_legacy_uda<'a>( +pub unsafe extern "C" fn tc_task_remove_legacy_uda( task: *mut TCTask, key: *mut TCString, ) -> TCResult { @@ -730,11 +730,11 @@ pub unsafe extern "C" fn tc_task_remove_legacy_uda<'a>( /// to this function will return NULL. The task pointer must not be NULL. The caller must free the /// returned string. #[no_mangle] -pub unsafe extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> *mut TCString<'static> { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) - let task: &'a mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; + let task: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; if let Some(tcstring) = task.error.take() { unsafe { tcstring.return_ptr() } } else { @@ -747,7 +747,7 @@ pub unsafe extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<' /// /// If the task is currently mutable, it will first be made immutable. #[no_mangle] -pub unsafe extern "C" fn tc_task_free<'a>(task: *mut TCTask) { +pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) { // SAFETY: // - rep is not NULL (promised by caller) // - caller will not use the TCTask after this (promised by caller) diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index f3d0528b3..9496e8b0b 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -75,7 +75,7 @@ impl CList for TCUuidList { /// 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. #[no_mangle] -pub unsafe extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { +pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) { debug_assert!(!buf.is_null()); // SAFETY: // - buf is valid for len bytes (by C convention) @@ -83,7 +83,7 @@ pub unsafe extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_ch // - content of buf will not be mutated during the lifetime of this slice (lifetime // does not outlive this function call) // - the length of the buffer is less than isize::MAX (promised by caller) - let buf: &'a mut [u8] = unsafe { + let buf: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, ::uuid::adapter::Hyphenated::LENGTH) }; // SAFETY: @@ -107,7 +107,7 @@ pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static /// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given /// string is not valid. #[no_mangle] -pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> TCResult { +pub unsafe extern "C" fn tc_uuid_from_str(s: *mut TCString, uuid_out: *mut TCUuid) -> TCResult { debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null()); // SAFETY: see TCString docstring diff --git a/lib/src/workingset.rs b/lib/src/workingset.rs index 02780e50f..6e1e15b0f 100644 --- a/lib/src/workingset.rs +++ b/lib/src/workingset.rs @@ -18,14 +18,14 @@ impl From for TCWorkingSet { } /// Utility function to get a shared reference to the underlying WorkingSet. -fn wrap<'a, T, F>(ws: *mut TCWorkingSet, f: F) -> T +fn wrap(ws: *mut TCWorkingSet, f: F) -> T where F: FnOnce(&WorkingSet) -> T, { // SAFETY: // - ws is not null (promised by caller) // - ws outlives 'a (promised by caller) - let tcws: &'a TCWorkingSet = unsafe { TCWorkingSet::from_ptr_arg_ref(ws) }; + let tcws: &TCWorkingSet = unsafe { TCWorkingSet::from_ptr_arg_ref(ws) }; f(&tcws.0) } From ad464c4779b54c07931b9d57b335db9bc4f9c1e9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 03:33:43 +0000 Subject: [PATCH 504/548] use Uda instead of UDA --- integration-tests/src/bindings_tests/task.c | 2 +- lib/src/lib.rs | 2 +- lib/src/task.rs | 18 +++---- lib/src/uda.rs | 56 ++++++++++----------- lib/taskchampion.h | 34 ++++++------- 5 files changed, 56 insertions(+), 56 deletions(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 7f89ddc51..b2412f3c3 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -400,7 +400,7 @@ static void test_task_udas(void) { tc_task_to_mut(task, rep); TCString *value; - TCUDAList udas; + TCUdaList udas; TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1"))); TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1"))); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b6a8f2eec..39fab8534 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -29,7 +29,7 @@ pub(crate) mod types { pub(crate) use crate::status::TCStatus; pub(crate) use crate::string::{TCString, TCStringList}; pub(crate) use crate::task::{TCTask, TCTaskList}; - pub(crate) use crate::uda::{TCUDAList, TCUDA, UDA}; + pub(crate) use crate::uda::{TCUda, TCUdaList, Uda}; pub(crate) use crate::uuid::{TCUuid, TCUuidList}; pub(crate) use crate::workingset::TCWorkingSet; } diff --git a/lib/src/task.rs b/lib/src/task.rs index 5a7a446fc..1a4b66157 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -404,40 +404,40 @@ pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>( /// /// Legacy UDAs are represented with an empty string in the ns field. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUDAList { +pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUdaList { wrap(task, |task| { - let vec: Vec = task + let vec: Vec = task .get_udas() .map(|((ns, key), value)| { - TCUDA::return_val(UDA { + TCUda::return_val(Uda { ns: Some(ns.into()), key: key.into(), value: value.into(), }) }) .collect(); - TCUDAList::return_val(vec) + TCUdaList::return_val(vec) }) } /// Get all UDAs for this task. /// -/// All TCUDAs in this list have a NULL ns field. The entire UDA key is +/// All TCUdas in this list have a NULL ns field. The entire UDA key is /// included in the key field. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUDAList { +pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUdaList { wrap(task, |task| { - let vec: Vec = task + let vec: Vec = task .get_legacy_udas() .map(|(key, value)| { - TCUDA::return_val(UDA { + TCUda::return_val(Uda { ns: None, key: key.into(), value: value.into(), }) }) .collect(); - TCUDAList::return_val(vec) + TCUdaList::return_val(vec) }) } diff --git a/lib/src/uda.rs b/lib/src/uda.rs index 4c0fe03f2..3712932ad 100644 --- a/lib/src/uda.rs +++ b/lib/src/uda.rs @@ -1,9 +1,9 @@ use crate::traits::*; use crate::types::*; -/// TCUDA contains the details of a UDA. +/// TCUda contains the details of a UDA. #[repr(C)] -pub struct TCUDA { +pub struct TCUda { /// Namespace of the UDA. For legacy UDAs, this is NULL. pub ns: *mut TCString<'static>, /// UDA key. Must not be NULL. @@ -12,17 +12,17 @@ pub struct TCUDA { pub value: *mut TCString<'static>, } -pub(crate) struct UDA { +pub(crate) struct Uda { pub ns: Option>, pub key: TCString<'static>, pub value: TCString<'static>, } -impl PassByValue for TCUDA { - type RustType = UDA; +impl PassByValue for TCUda { + type RustType = Uda; unsafe fn from_ctype(self) -> Self::RustType { - UDA { + Uda { ns: if self.ns.is_null() { None } else { @@ -42,8 +42,8 @@ impl PassByValue for TCUDA { } } - fn as_ctype(uda: UDA) -> Self { - TCUDA { + fn as_ctype(uda: Uda) -> Self { + TCUda { // SAFETY: caller assumes ownership of this value ns: if let Some(ns) = uda.ns { unsafe { ns.return_ptr() } @@ -58,9 +58,9 @@ impl PassByValue for TCUDA { } } -impl Default for TCUDA { +impl Default for TCUda { fn default() -> Self { - TCUDA { + TCUda { ns: std::ptr::null_mut(), key: std::ptr::null_mut(), value: std::ptr::null_mut(), @@ -68,27 +68,27 @@ impl Default for TCUDA { } } -/// TCUDAList represents a list of UDAs. +/// TCUdaList represents a list of UDAs. /// /// The content of this struct must be treated as read-only. #[repr(C)] -pub struct TCUDAList { +pub struct TCUdaList { /// number of UDAs in items len: libc::size_t, /// 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 - /// tc_uda_list_free. This pointer is never NULL for a valid TCUDAList. - items: *const TCUDA, + /// 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: *const TCUda, } -impl CList for TCUDAList { - type Element = TCUDA; +impl CList for TCUdaList { + type Element = TCUda; unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { - TCUDAList { + TCUdaList { len, _capacity: cap, items, @@ -100,25 +100,25 @@ impl CList for TCUDAList { } } -/// Free a TCUDA instance. The instance, and the TCStrings it contains, must not be used +/// Free a TCUda instance. The instance, and the TCStrings it contains, must not be used /// after this call. #[no_mangle] -pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUDA) { +pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUda) { debug_assert!(!tcuda.is_null()); // SAFETY: - // - *tcuda is a valid TCUDA (caller promises to treat it as read-only) - let uda = unsafe { TCUDA::take_val_from_arg(tcuda, TCUDA::default()) }; + // - *tcuda is a valid TCUda (caller promises to treat it as read-only) + let uda = unsafe { TCUda::take_val_from_arg(tcuda, TCUda::default()) }; drop(uda); } -/// Free a TCUDAList instance. The instance, and all TCUDAs it contains, must not be used after +/// 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. +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList. #[no_mangle] -pub unsafe extern "C" fn tc_uda_list_free(tcudas: *mut TCUDAList) { +pub unsafe extern "C" fn tc_uda_list_free(tcudas: *mut TCUdaList) { // SAFETY: - // - tcudas is not NULL and points to a valid TCUDAList (caller is not allowed to + // - tcudas is not NULL and points to a valid TCUdaList (caller is not allowed to // modify the list) // - caller promises not to use the value after return unsafe { drop_value_list(tcudas) } @@ -130,7 +130,7 @@ mod test { #[test] fn empty_list_has_non_null_pointer() { - let tcudas = TCUDAList::return_val(Vec::new()); + let tcudas = TCUdaList::return_val(Vec::new()); assert!(!tcudas.items.is_null()); assert_eq!(tcudas.len, 0); assert_eq!(tcudas._capacity, 0); @@ -138,7 +138,7 @@ mod test { #[test] fn free_sets_null_pointer() { - let mut tcudas = TCUDAList::return_val(Vec::new()); + let mut tcudas = TCUdaList::return_val(Vec::new()); // SAFETY: testing expected behavior unsafe { tc_uda_list_free(&mut tcudas) }; assert!(tcudas.items.is_null()); diff --git a/lib/taskchampion.h b/lib/taskchampion.h index bb41d4e5a..d5f787a88 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -268,9 +268,9 @@ typedef struct TCStringList { } TCStringList; /** - * TCUDA contains the details of a UDA. + * TCUda contains the details of a UDA. */ -typedef struct TCUDA { +typedef struct TCUda { /** * Namespace of the UDA. For legacy UDAs, this is NULL. */ @@ -283,14 +283,14 @@ typedef struct TCUDA { * Content of the UDA. Must not be NULL. */ struct TCString *value; -} TCUDA; +} TCUda; /** - * TCUDAList represents a list of UDAs. + * TCUdaList represents a list of UDAs. * * The content of this struct must be treated as read-only. */ -typedef struct TCUDAList { +typedef struct TCUdaList { /** * number of UDAs in items */ @@ -300,11 +300,11 @@ typedef struct TCUDAList { */ 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. + * 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. */ - const struct TCUDA *items; -} TCUDAList; + const struct TCUda *items; +} TCUdaList; #ifdef __cplusplus extern "C" { @@ -612,15 +612,15 @@ struct TCString *tc_task_get_legacy_uda(struct TCTask *task, struct TCString *ke * * Legacy UDAs are represented with an empty string in the ns field. */ -struct TCUDAList tc_task_get_udas(struct TCTask *task); +struct TCUdaList tc_task_get_udas(struct TCTask *task); /** * Get all UDAs for this task. * - * All TCUDAs in this list have a NULL ns field. The entire UDA key is + * All TCUdas in this list have a NULL ns field. The entire UDA key is * included in the key field. */ -struct TCUDAList tc_task_get_legacy_udas(struct TCTask *task); +struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); /** * Set a mutable task's status. @@ -735,18 +735,18 @@ void tc_task_free(struct TCTask *task); void tc_task_list_free(struct TCTaskList *tctasks); /** - * Free a TCUDA instance. The instance, and the TCStrings it contains, must not be used + * 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); +void tc_uda_free(struct TCUda *tcuda); /** - * Free a TCUDAList instance. The instance, and all TCUDAs it contains, must not be used after + * 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. + * When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList. */ -void tc_uda_list_free(struct TCUDAList *tcudas); +void tc_uda_list_free(struct TCUdaList *tcudas); /** * Create a new, randomly-generated UUID. From fc73911cde7c1562316a4abe48c1e1bee19edb81 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 03:37:50 +0000 Subject: [PATCH 505/548] fix some clippy::wrong_self_convention --- lib/src/string.rs | 26 +++++++++++++------------- lib/src/task.rs | 1 + lib/src/traits.rs | 1 + 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/src/string.rs b/lib/src/string.rs index 442ffd0e9..758f1d6e0 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -95,7 +95,7 @@ impl<'a> TCString<'a> { /// Convert the TCString, in place, into one of the C variants. If this is not /// possible, such as if the string contains an embedded NUL, then the string /// remains unchanged. - fn to_c_string(&mut self) { + fn to_c_string_mut(&mut self) { if matches!(self, TCString::String(_)) { // we must take ownership of the String in order to try converting it, // leaving the underlying TCString as its default (None) @@ -267,11 +267,11 @@ pub unsafe extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const li // if we have a String, we need to consume it and turn it into // a CString. - tcstring.to_c_string(); + tcstring.to_c_string_mut(); match tcstring { TCString::CString(cstring) => cstring.as_ptr(), - TCString::String(_) => std::ptr::null(), // to_c_string failed + TCString::String(_) => std::ptr::null(), // to_c_string_mut failed TCString::CStr(cstr) => cstr.as_ptr(), TCString::InvalidUtf8(_, _) => std::ptr::null(), TCString::None => unreachable!(), @@ -427,37 +427,37 @@ mod test { } #[test] - fn cstring_to_c_string() { + fn cstring_to_c_string_mut() { let mut tcstring = make_cstring(); - tcstring.to_c_string(); + tcstring.to_c_string_mut(); assert_eq!(tcstring, make_cstring()); // unchanged } #[test] - fn cstr_to_c_string() { + fn cstr_to_c_string_mut() { let mut tcstring = make_cstr(); - tcstring.to_c_string(); + tcstring.to_c_string_mut(); assert_eq!(tcstring, make_cstr()); // unchanged } #[test] - fn string_to_c_string() { + fn string_to_c_string_mut() { let mut tcstring = make_string(); - tcstring.to_c_string(); + tcstring.to_c_string_mut(); assert_eq!(tcstring, make_cstring()); // converted to CString, same content } #[test] - fn string_with_nul_to_c_string() { + fn string_with_nul_to_c_string_mut() { let mut tcstring = make_string_with_nul(); - tcstring.to_c_string(); + tcstring.to_c_string_mut(); assert_eq!(tcstring, make_string_with_nul()); // unchanged } #[test] - fn invalid_to_c_string() { + fn invalid_to_c_string_mut() { let mut tcstring = make_invalid(); - tcstring.to_c_string(); + tcstring.to_c_string_mut(); assert_eq!(tcstring, make_invalid()); // unchanged } } diff --git a/lib/src/task.rs b/lib/src/task.rs index 1a4b66157..47dbbf3d1 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -74,6 +74,7 @@ impl TCTask { /// Make an mutable TCTask into a immutable TCTask. Does nothing if the task /// is already immutable. + #[allow(clippy::wrong_self_convention)] // to_immut_mut is not better! fn to_immut(&mut self) { self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) { Inner::Immutable(task) => Inner::Immutable(task), diff --git a/lib/src/traits.rs b/lib/src/traits.rs index 57537d212..e71ab0b9f 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -16,6 +16,7 @@ pub(crate) trait PassByValue: Sized { /// # Safety /// /// `self` must be a valid CType. + #[allow(clippy::wrong_self_convention)] unsafe fn from_ctype(self) -> Self::RustType; /// Convert a Rust value to a C value. From c0403f3f3823cbf67980e9dafd98abd10d3cdf8e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 16:18:17 +0000 Subject: [PATCH 506/548] fix bad test --- taskchampion/src/workingset.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/taskchampion/src/workingset.rs b/taskchampion/src/workingset.rs index 8bd0cce53..15a509753 100644 --- a/taskchampion/src/workingset.rs +++ b/taskchampion/src/workingset.rs @@ -110,7 +110,10 @@ mod test { #[test] fn test_largest_index() { - let (uuid1, uuid2, ws) = make(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + let ws = WorkingSet::new(vec![]); assert_eq!(ws.largest_index(), 0); let ws = WorkingSet::new(vec![None, Some(uuid1)]); From 41a578ab2bf2554f940029d3eba847019342373a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 20:18:07 +0000 Subject: [PATCH 507/548] add server support --- integration-tests/.gitignore | 1 + .../src/bindings_tests/replica.c | 45 ++++++ lib/src/lib.rs | 2 + lib/src/replica.rs | 31 +++- lib/src/server.rs | 139 ++++++++++++++++++ lib/src/string.rs | 2 +- lib/taskchampion.h | 53 ++++++- 7 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 lib/src/server.rs diff --git a/integration-tests/.gitignore b/integration-tests/.gitignore index e525de89a..140a35ffe 100644 --- a/integration-tests/.gitignore +++ b/integration-tests/.gitignore @@ -1 +1,2 @@ test-db +test-sync-server diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 423d8dc25..bd15adfa9 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -1,4 +1,5 @@ #include +#include #include #include "unity.h" #include "taskchampion.h" @@ -153,6 +154,48 @@ static void test_replica_task_creation(void) { tc_replica_free(rep); } +// When tc_replica_undo is passed NULL for undone_out, it still succeeds +static void test_replica_sync_local(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + mkdir("test-sync-server", 0755); // ignore error, if dir already exists + + TCString *err = NULL; + TCServer *server = tc_server_new_local(tc_string_borrow("test-sync-server"), &err); + TEST_ASSERT_NOT_NULL(server); + TEST_ASSERT_NULL(err); + + int rv = tc_replica_sync(rep, server, false); + TEST_ASSERT_EQUAL(TC_RESULT_OK, rv); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + tc_server_free(server); + tc_replica_free(rep); + + // test error handling + server = tc_server_new_local(tc_string_borrow("/no/such/directory"), &err); + TEST_ASSERT_NULL(server); + TEST_ASSERT_NOT_NULL(err); + tc_string_free(err); +} + +// When tc_replica_undo is passed NULL for undone_out, it still succeeds +static void test_replica_remote_server(void) { + TCString *err = NULL; + TCServer *server = tc_server_new_remote( + tc_string_borrow("tc.freecinc.com"), + tc_uuid_new_v4(), + tc_string_borrow("\xf0\x28\x8c\x28"), // NOTE: not utf-8 + &err); + TEST_ASSERT_NOT_NULL(server); + TEST_ASSERT_NULL(err); + + // can't actually do anything with this server! + + tc_server_free(server); +} + // a replica with tasks in it returns an appropriate list of tasks and list of uuids static void test_replica_all_tasks(void) { TCReplica *rep = tc_replica_new_in_memory(); @@ -274,6 +317,8 @@ int replica_tests(void) { RUN_TEST(test_replica_working_set); RUN_TEST(test_replica_undo_empty_null_undone_out); RUN_TEST(test_replica_task_creation); + RUN_TEST(test_replica_sync_local); + RUN_TEST(test_replica_remote_server); RUN_TEST(test_replica_all_tasks); RUN_TEST(test_replica_task_import); RUN_TEST(test_replica_get_task_not_found); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 39fab8534..614d57f7b 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -14,6 +14,7 @@ pub mod atomic; pub mod kv; pub mod replica; pub mod result; +pub mod server; pub mod status; pub mod string; pub mod task; @@ -26,6 +27,7 @@ pub(crate) mod types { pub(crate) use crate::kv::{TCKVList, TCKV}; pub(crate) use crate::replica::TCReplica; pub(crate) use crate::result::TCResult; + pub(crate) use crate::server::TCServer; pub(crate) use crate::status::TCStatus; pub(crate) use crate::string::{TCString, TCStringList}; pub(crate) use crate::task::{TCTask, TCTaskList}; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 314f8d375..423810645 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -100,8 +100,9 @@ pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } } -/// 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. +/// 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. #[no_mangle] pub unsafe extern "C" fn tc_replica_new_on_disk( path: *mut TCString, @@ -258,7 +259,31 @@ pub unsafe extern "C" fn tc_replica_import_task_with_uuid( ) } -// TODO: tc_replica_sync +/// Synchronize this replica with a server. +/// +/// The `server` argument remains owned by the caller, and must be freed explicitly. +#[no_mangle] +pub unsafe extern "C" fn tc_replica_sync( + rep: *mut TCReplica, + server: *mut TCServer, + avoid_snapshots: bool, +) -> TCResult { + wrap( + rep, + |rep| { + debug_assert!(!server.is_null()); + // SAFETY: + // - server is not NULL + // - *server is a valid TCServer (promised by caller) + // - server is valid for the lifetime of tc_replica_sync (not threadsafe) + // - server will not be accessed simultaneously (not threadsafe) + let server = unsafe { TCServer::from_ptr_arg_ref_mut(server) }; + rep.sync(server.as_mut(), avoid_snapshots)?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} /// Undo local operations until the most recent UndoPoint. /// diff --git a/lib/src/server.rs b/lib/src/server.rs new file mode 100644 index 000000000..d50650ec9 --- /dev/null +++ b/lib/src/server.rs @@ -0,0 +1,139 @@ +use crate::traits::*; +use crate::types::*; +use crate::util::err_to_tcstring; +use taskchampion::{Server, ServerConfig}; + +/// 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. +pub struct TCServer(Box); + +impl PassByPointer for TCServer {} + +impl From> for TCServer { + fn from(server: Box) -> TCServer { + TCServer(server) + } +} + +impl AsMut> for TCServer { + fn as_mut(&mut self) -> &mut Box { + &mut self.0 + } +} + +/// Utility function to allow using `?` notation to return an error value. +fn wrap(f: F, error_out: *mut *mut TCString, err_value: T) -> T +where + F: FnOnce() -> anyhow::Result, +{ + match f() { + Ok(v) => v, + Err(e) => { + if !error_out.is_null() { + // SAFETY: + // - error_out is not NULL (checked) + // - ..and points to a valid pointer (promised by caller) + // - caller will free this string (promised by caller) + unsafe { + *error_out = err_to_tcstring(e).return_ptr(); + } + } + err_value + } + } +} + +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_server_new_local( + server_dir: *mut TCString, + error_out: *mut *mut TCString, +) -> *mut TCServer { + wrap( + || { + // SAFETY: see TCString docstring + let server_dir = unsafe { TCString::take_from_ptr_arg(server_dir) }; + let server_config = ServerConfig::Local { + server_dir: server_dir.to_path_buf(), + }; + let server = server_config.into_server()?; + // SAFETY: caller promises to free this server. + Ok(unsafe { TCServer::return_ptr(server.into()) }) + }, + error_out, + std::ptr::null_mut(), + ) +} + +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_server_new_remote( + origin: *mut TCString, + client_key: TCUuid, + encryption_secret: *mut TCString, + error_out: *mut *mut TCString, +) -> *mut TCServer { + wrap( + || { + debug_assert!(!origin.is_null()); + debug_assert!(!encryption_secret.is_null()); + // SAFETY: + // - origin is not NULL + // - origin is valid (promised by caller) + // - origin ownership is transferred to this function + let origin = unsafe { TCString::take_from_ptr_arg(origin) }.into_string()?; + + // SAFETY: + // - client_key is a valid Uuid (any 8-byte sequence counts) + + let client_key = unsafe { TCUuid::val_from_arg(client_key) }; + // SAFETY: + // - encryption_secret is not NULL + // - encryption_secret is valid (promised by caller) + // - encryption_secret ownership is transferred to this function + let encryption_secret = unsafe { TCString::take_from_ptr_arg(encryption_secret) } + .as_bytes() + .to_vec(); + + let server_config = ServerConfig::Remote { + origin, + client_key, + encryption_secret, + }; + let server = server_config.into_server()?; + // SAFETY: caller promises to free this server. + Ok(unsafe { TCServer::return_ptr(server.into()) }) + }, + error_out, + std::ptr::null_mut(), + ) +} + +/// Free a server. The server may not be used after this function returns and must not be freed +/// more than once. +#[no_mangle] +pub unsafe extern "C" fn tc_server_free(server: *mut TCServer) { + debug_assert!(!server.is_null()); + // SAFETY: + // - server is not NULL + // - server came from tc_server_new_.., which used return_ptr + // - server will not be used after (promised by caller) + let server = unsafe { TCServer::take_from_ptr_arg(server) }; + drop(server); +} diff --git a/lib/src/string.rs b/lib/src/string.rs index 758f1d6e0..f21f05c95 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -82,7 +82,7 @@ impl<'a> TCString<'a> { } } - fn as_bytes(&self) -> &[u8] { + pub(crate) fn as_bytes(&self) -> &[u8] { match self { TCString::CString(cstring) => cstring.as_bytes(), TCString::CStr(cstr) => cstr.to_bytes(), diff --git a/lib/taskchampion.h b/lib/taskchampion.h index d5f787a88..41edeedff 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -63,6 +63,16 @@ typedef enum TCStatus { */ 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; + /** * TCString supports passing strings into and out of the TaskChampion API. * @@ -339,8 +349,9 @@ void tc_kv_list_free(struct TCKVList *tckvs); 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. + * 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, struct TCString **error_out); @@ -389,6 +400,13 @@ struct TCTask *tc_replica_new_task(struct TCReplica *rep, */ 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. * @@ -425,6 +443,37 @@ struct TCString *tc_replica_error(struct TCReplica *rep); */ 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, + 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); + /** * 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 From ca904d6288ed49e748b02b8981d079370b7a20e3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 21:02:18 +0000 Subject: [PATCH 508/548] improve output of C tests --- integration-tests/build.rs | 5 ++++ integration-tests/src/bindings_tests/mod.rs | 18 ++++++++++++-- .../src/bindings_tests/replica.c | 2 +- integration-tests/src/bindings_tests/test.c | 24 +++++++++++++++++++ integration-tests/tests/bindings.rs | 15 ++++++++++-- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/integration-tests/build.rs b/integration-tests/build.rs index 5182784b3..e1c5c2fbe 100644 --- a/integration-tests/build.rs +++ b/integration-tests/build.rs @@ -18,6 +18,11 @@ fn build_libtaskchampion(suites: &[&'static str]) { build.object(libtaskchampion); build.include("../lib"); build.include("src/bindings_tests/unity"); + build.define("UNITY_OUTPUT_CHAR", "test_output"); + build.define( + "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()]; diff --git a/integration-tests/src/bindings_tests/mod.rs b/integration-tests/src/bindings_tests/mod.rs index 4eba5b421..327ca83fe 100644 --- a/integration-tests/src/bindings_tests/mod.rs +++ b/integration-tests/src/bindings_tests/mod.rs @@ -1,14 +1,28 @@ +use std::fs; + +extern "C" { + // set up to send test output to TEST-OUTPUT + fn setup_output(); + // close the output file + fn finish_output(); +} + // Each suite is represented by a _tests C function in .c. // All of these C files are built into a library that is linked to the crate -- but not to test // crates. So, this macro produces a "glue function" that calls the C function, and that can be // called from test crates. macro_rules! suite( { $s:ident } => { - pub fn $s() -> i32 { + pub fn $s() -> (i32, String) { extern "C" { fn $s() -> i32; } - unsafe { $s() } + unsafe { setup_output() }; + let res = unsafe { $s() }; + unsafe { finish_output() }; + let output = fs::read_to_string("TEST-OUTPUT") + .unwrap_or_else(|e| format!("could not open TEST-OUTPUT: {}", e)); + (res, output) } }; ); diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index bd15adfa9..282d2948b 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -1,8 +1,8 @@ #include #include #include -#include "unity.h" #include "taskchampion.h" +#include "unity.h" // creating an in-memory replica does not crash static void test_replica_creation(void) { diff --git a/integration-tests/src/bindings_tests/test.c b/integration-tests/src/bindings_tests/test.c index 609943fc4..5afa236ca 100644 --- a/integration-tests/src/bindings_tests/test.c +++ b/integration-tests/src/bindings_tests/test.c @@ -1,6 +1,30 @@ +#include #include "unity.h" // these functions are shared between all test "suites" // and cannot be customized per-suite. void setUp(void) { } void tearDown(void) { } + +static FILE *output = NULL; + +// Set up for test_output, writing output to "TEST-OUTPUT" in the +// current directory. The Rust test harness reads this file to get +// the output and display it only on failure. This is called by +// the Rust test harness +void setup_output(void) { + output = fopen("TEST-OUTPUT", "w"); +} + +// Close the output file. Called by the Rust test harness. +void finish_output(void) { + fclose(output); + output = NULL; +} + +// this replaces UNITY_OUTPUT_CHAR, and writes output to +// TEST-OUTPUT in the current directory; the Rust test harness +// will read this data if the test fails. +void test_output(char c) { + fputc(c, output); +} diff --git a/integration-tests/tests/bindings.rs b/integration-tests/tests/bindings.rs index 9f14ec47f..a121dd721 100644 --- a/integration-tests/tests/bindings.rs +++ b/integration-tests/tests/bindings.rs @@ -1,5 +1,6 @@ use lazy_static::lazy_static; use std::sync::Mutex; +use tempfile::TempDir; lazy_static! { // the C library running the tests is not reentrant, so we use a mutex to ensure that only one @@ -11,8 +12,18 @@ macro_rules! suite( { $s:ident } => { #[test] fn $s() { - let _guard = MUTEX.lock().unwrap(); - assert_eq!(integration_tests::bindings_tests::$s(), 0); + let tmp_dir = TempDir::new().expect("TempDir failed"); + let (res, output) = { + let _guard = MUTEX.lock().unwrap(); + // run the tests in the temp dir (NOTE: this must be inside + // the mutex guard!) + std::env::set_current_dir(tmp_dir.as_ref()).unwrap(); + integration_tests::bindings_tests::$s() + }; + println!("{}", output); + if res != 0 { + assert!(false, "test failed"); + } } }; ); From 8e34c107d5cdd934fe61b0516a40c67376bb17ee Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Feb 2022 22:21:07 +0000 Subject: [PATCH 509/548] update safety comments --- lib/src/annotation.rs | 14 +++++++ lib/src/replica.rs | 72 ++++++++++++++++++++++++++--------- lib/src/server.rs | 5 ++- lib/src/string.rs | 13 ++++--- lib/src/task.rs | 88 ++++++++++++++++++++++++++++++++++--------- lib/src/uuid.rs | 12 ++++-- lib/taskchampion.h | 45 ++++++++++++++++++---- 7 files changed, 196 insertions(+), 53 deletions(-) diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index dede87431..7142eff62 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -3,6 +3,20 @@ use crate::types::*; use chrono::prelude::*; /// 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. #[repr(C)] pub struct TCAnnotation { /// Time the annotation was made. Must be nonzero. diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 423810645..a35260b51 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -23,7 +23,7 @@ use taskchampion::{Replica, StorageConfig}; /// - 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` becmes invalid and must not be used again. +/// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. /// /// TCReplicas are not threadsafe. pub struct TCReplica { @@ -74,7 +74,12 @@ fn wrap(rep: *mut TCReplica, f: F, err_value: T) -> T where F: FnOnce(&mut Replica) -> anyhow::Result, { - // SAFETY: see type docstring + debug_assert!(!rep.is_null()); + // SAFETY: + // - rep is not NULL (promised by caller) + // - *rep is a valid TCReplica (promised by caller) + // - rep is valid for the duration of this function + // - rep is not modified by anything else (not threadsafe) let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; if rep.mut_borrowed { panic!("replica is borrowed and cannot be used"); @@ -90,13 +95,14 @@ where } /// Create a new TCReplica with an in-memory database. The contents of the database will be -/// lost when it is freed. +/// lost when it is freed with tc_replica_free. #[no_mangle] pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { let storage = StorageConfig::InMemory .into_storage() .expect("in-memory always succeeds"); - // SAFETY: see type docstring + // SAFETY: + // - caller promises to free this value unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } } @@ -108,7 +114,10 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( path: *mut TCString, error_out: *mut *mut TCString, ) -> *mut TCReplica { - // SAFETY: see TCString docstring + // SAFETY: + // - path is not NULL (promised by caller) + // - path is return from a tc_string_.. so is valid + // - caller will not use path after this call (convention) let path = unsafe { TCString::take_from_ptr_arg(path) }; let storage_res = StorageConfig::OnDisk { taskdb_dir: path.to_path_buf(), @@ -120,6 +129,11 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( Err(e) => { if !error_out.is_null() { unsafe { + // SAFETY: + // - return_ptr: caller promises to free this string + // - *error_out: + // - error_out is not NULL (checked) + // - error_out points to a valid place for a pointer (caller promises) *error_out = err_to_tcstring(e).return_ptr(); } } @@ -127,7 +141,8 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( } }; - // SAFETY: see type docstring + // SAFETY: + // - caller promises to free this value unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } } @@ -147,7 +162,8 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList .drain() .map(|(_uuid, t)| { NonNull::new( - // SAFETY: see TCTask docstring + // SAFETY: + // - caller promises to free this value (via freeing the list) unsafe { TCTask::from(t).return_ptr() }, ) .expect("TCTask::return_ptr returned NULL") @@ -178,7 +194,8 @@ pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUui ) } -/// Get the current working set for this replica. +/// Get the current working set for this replica. The resulting value must be freed +/// with tc_working_set_free. /// /// Returns NULL on error. #[no_mangle] @@ -187,7 +204,8 @@ pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCW rep, |rep| { let ws = rep.working_set()?; - // SAFETY: caller promises to free this task + // SAFETY: + // - caller promises to free this value Ok(unsafe { TCWorkingSet::return_ptr(ws.into()) }) }, std::ptr::null_mut(), @@ -203,10 +221,13 @@ pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid wrap( rep, |rep| { - // SAFETY: see TCUuid docstring + // SAFETY: + // - tcuuid is a valid TCUuid (all bytes are valid) + // - tcuuid is Copy so ownership doesn't matter let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; if let Some(task) = rep.get_task(uuid)? { - // SAFETY: caller promises to free this task + // SAFETY: + // - caller promises to free this task Ok(unsafe { TCTask::from(task).return_ptr() }) } else { Ok(std::ptr::null_mut()) @@ -225,13 +246,17 @@ pub unsafe extern "C" fn tc_replica_new_task( status: TCStatus, description: *mut TCString, ) -> *mut TCTask { - // SAFETY: see TCString docstring + // SAFETY: + // - description is not NULL (promised by caller) + // - description is return from a tc_string_.. so is valid + // - caller will not use description after this call (convention) let description = unsafe { TCString::take_from_ptr_arg(description) }; wrap( rep, |rep| { let task = rep.new_task(status.into(), description.as_str()?.to_string())?; - // SAFETY: caller promises to free this task + // SAFETY: + // - caller promises to free this task Ok(unsafe { TCTask::from(task).return_ptr() }) }, std::ptr::null_mut(), @@ -249,10 +274,13 @@ pub unsafe extern "C" fn tc_replica_import_task_with_uuid( wrap( rep, |rep| { - // SAFETY: see TCUuid docstring + // SAFETY: + // - tcuuid is a valid TCUuid (all bytes are valid) + // - tcuuid is Copy so ownership doesn't matter let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; let task = rep.import_task_with_uuid(uuid)?; - // SAFETY: caller promises to free this task + // SAFETY: + // - caller promises to free this task Ok(unsafe { TCTask::from(task).return_ptr() }) }, std::ptr::null_mut(), @@ -346,10 +374,15 @@ pub unsafe extern "C" fn tc_replica_rebuild_working_set( /// returned string. #[no_mangle] pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> *mut TCString<'static> { - // SAFETY: see type docstring + // SAFETY: + // - rep is not NULL (promised by caller) + // - *rep is a valid TCReplica (promised by caller) + // - rep is valid for the duration of this function + // - rep is not modified by anything else (not threadsafe) let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; if let Some(tcstring) = rep.error.take() { - // SAFETY: see TCString docstring + // SAFETY: + // - caller promises to free this string unsafe { tcstring.return_ptr() } } else { std::ptr::null_mut() @@ -360,7 +393,10 @@ pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> *mut TCString< /// more than once. #[no_mangle] pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) { - // SAFETY: see type docstring + // SAFETY: + // - replica is not NULL (promised by caller) + // - replica is valid (promised by caller) + // - caller will not use description after this call (promised by caller) let replica = unsafe { TCReplica::take_from_ptr_arg(rep) }; if replica.mut_borrowed { panic!("replica is borrowed and cannot be freed"); diff --git a/lib/src/server.rs b/lib/src/server.rs index d50650ec9..74feff34f 100644 --- a/lib/src/server.rs +++ b/lib/src/server.rs @@ -61,7 +61,10 @@ pub unsafe extern "C" fn tc_server_new_local( ) -> *mut TCServer { wrap( || { - // SAFETY: see TCString docstring + // SAFETY: + // - server_dir is not NULL (promised by caller) + // - server_dir is return from a tc_string_.. so is valid + // - caller will not use server_dir after this call (convention) let server_dir = unsafe { TCString::take_from_ptr_arg(server_dir) }; let server_config = ServerConfig::Local { server_dir: server_dir.to_path_buf(), diff --git a/lib/src/string.rs b/lib/src/string.rs index f21f05c95..3e0d4069e 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -23,7 +23,7 @@ use std::str::Utf8Error; /// # Safety /// /// When a `*TCString` appears as a return value or output argument, ownership is passed to the -/// caller. The caller must pass that ownerhsip back to another function or free the string. +/// caller. The caller must pass that ownership back to another function or free the string. /// /// Any function taking a `*TCReplica` requires: /// - the pointer must not be NUL; @@ -32,7 +32,7 @@ use std::str::Utf8Error; /// /// Unless specified otherwise, TaskChampion functions take ownership of a `*TCString` when it is /// given as a function argument, and the pointer is invalid when the function returns. Callers -/// must not use or free TCStringList after passing them to such API functions. +/// must not use or free TCStrings after passing them to such API functions. /// /// TCString is not threadsafe. #[derive(PartialEq, Debug)] @@ -193,7 +193,8 @@ pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCS // - cstr contains a valid NUL terminator (promised by caller) // - cstr's content will not change before it is destroyed (promised by caller) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - // SAFETY: see docstring + // SAFETY: + // - caller promises to free this string unsafe { TCString::CStr(cstr).return_ptr() } } @@ -208,7 +209,8 @@ pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCSt // - cstr contains a valid NUL terminator (promised by caller) // - cstr's content will not change before it is destroyed (by C convention) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - // SAFETY: see docstring + // SAFETY: + // - caller promises to free this string unsafe { TCString::CString(cstr.into()).return_ptr() } } @@ -245,7 +247,8 @@ pub unsafe extern "C" fn tc_string_clone_with_len( } }; - // SAFETY: see docstring + // SAFETY: + // - caller promises to free this string unsafe { tcstring.return_ptr() } } diff --git a/lib/src/task.rs b/lib/src/task.rs index 47dbbf3d1..ccea9fa20 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -23,6 +23,19 @@ use taskchampion::{Annotation, Tag, Task, TaskMut}; /// 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. pub struct TCTask { /// The wrapped Task or TaskMut @@ -269,7 +282,8 @@ pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> *mut TCString<'static> { wrap(task, |task| { let descr: TCString = task.get_description().into(); - // SAFETY: see TCString docstring + // SAFETY: + // - caller promises to free this string unsafe { descr.return_ptr() } }) } @@ -308,7 +322,10 @@ pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { /// that (invalid) tag is not present. No error will be reported via `tc_task_error`. #[no_mangle] pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: *mut TCString) -> bool { - // SAFETY: see TCString docstring + // SAFETY: + // - tag is not NULL (promised by caller) + // - tag is return from a tc_string_.. so is valid + // - caller will not use tag after this call (convention) let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap(task, |task| { if let Ok(tag) = Tag::try_from(tcstring) { @@ -330,7 +347,8 @@ pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList { .get_tags() .map(|t| { NonNull::new( - // SAFETY: see TCString docstring + // SAFETY: + // - this TCString will be freed via tc_string_list_free. unsafe { TCString::from(t.as_ref()).return_ptr() }, ) .expect("TCString::return_ptr() returned NULL") @@ -368,7 +386,12 @@ pub unsafe extern "C" fn tc_task_get_uda<'a>( key: *mut TCString<'a>, ) -> *mut TCString<'static> { wrap(task, |task| { + // SAFETY: + // - ns is not NULL (promised by caller) + // - ns is return from a tc_string_.. so is valid + // - caller will not use ns after this call (convention) if let Ok(ns) = unsafe { TCString::take_from_ptr_arg(ns) }.as_str() { + // SAFETY: same if let Ok(key) = unsafe { TCString::take_from_ptr_arg(key) }.as_str() { if let Some(value) = task.get_uda(ns, key) { // SAFETY: @@ -390,6 +413,10 @@ pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>( key: *mut TCString<'a>, ) -> *mut TCString<'static> { wrap(task, |task| { + // SAFETY: + // - key is not NULL (promised by caller) + // - key is return from a tc_string_.. so is valid + // - caller will not use key after this call (convention) if let Ok(key) = unsafe { TCString::take_from_ptr_arg(key) }.as_str() { if let Some(value) = task.get_legacy_uda(key) { // SAFETY: @@ -461,7 +488,10 @@ pub unsafe extern "C" fn tc_task_set_description( task: *mut TCTask, description: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // SAFETY: + // - description is not NULL (promised by caller) + // - description is return from a tc_string_.. so is valid + // - caller will not use description after this call (convention) let description = unsafe { TCString::take_from_ptr_arg(description) }; wrap_mut( task, @@ -577,7 +607,10 @@ pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { /// Add a tag to a mutable task. #[no_mangle] pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { - // SAFETY: see TCString docstring + // SAFETY: + // - tag is not NULL (promised by caller) + // - tag is return from a tc_string_.. so is valid + // - caller will not use tag after this call (convention) let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap_mut( task, @@ -593,7 +626,10 @@ pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) /// Remove a tag from a mutable task. #[no_mangle] pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { - // SAFETY: see TCString docstring + // SAFETY: + // - tag is not NULL (promised by caller) + // - tag is return from a tc_string_.. so is valid + // - caller will not use tag after this call (convention) let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap_mut( task, @@ -606,13 +642,17 @@ pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCStrin ) } -/// Add an annotation to a mutable task. +/// Add an annotation to a mutable task. This call takes ownership of the +/// passed annotation, which must not be used after the call returns. #[no_mangle] pub unsafe extern "C" fn tc_task_add_annotation( task: *mut TCTask, annotation: *mut TCAnnotation, ) -> TCResult { - // SAFETY: see TCAnnotation docstring + // SAFETY: + // - annotation is not NULL (promised by caller) + // - annotation is return from a tc_string_.. so is valid + // - caller will not use annotation after this call let (entry, description) = unsafe { TCAnnotation::take_val_from_arg(annotation, TCAnnotation::default()) }; wrap_mut( @@ -647,11 +687,14 @@ pub unsafe extern "C" fn tc_task_set_uda( key: *mut TCString, value: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // safety: + // - ns is not null (promised by caller) + // - ns is return from a tc_string_.. so is valid + // - caller will not use ns after this call (convention) let ns = unsafe { TCString::take_from_ptr_arg(ns) }; - // SAFETY: see TCString docstring + // SAFETY: same let key = unsafe { TCString::take_from_ptr_arg(key) }; - // SAFETY: see TCString docstring + // SAFETY: same let value = unsafe { TCString::take_from_ptr_arg(value) }; wrap_mut( task, @@ -674,9 +717,12 @@ pub unsafe extern "C" fn tc_task_remove_uda( ns: *mut TCString, key: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // safety: + // - ns is not null (promised by caller) + // - ns is return from a tc_string_.. so is valid + // - caller will not use ns after this call (convention) let ns = unsafe { TCString::take_from_ptr_arg(ns) }; - // SAFETY: see TCString docstring + // SAFETY: same let key = unsafe { TCString::take_from_ptr_arg(key) }; wrap_mut( task, @@ -695,9 +741,12 @@ pub unsafe extern "C" fn tc_task_set_legacy_uda( key: *mut TCString, value: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // safety: + // - key is not null (promised by caller) + // - key is return from a tc_string_.. so is valid + // - caller will not use key after this call (convention) let key = unsafe { TCString::take_from_ptr_arg(key) }; - // SAFETY: see TCString docstring + // SAFETY: same let value = unsafe { TCString::take_from_ptr_arg(value) }; wrap_mut( task, @@ -715,7 +764,10 @@ pub unsafe extern "C" fn tc_task_remove_legacy_uda( task: *mut TCTask, key: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // safety: + // - key is not null (promised by caller) + // - key is return from a tc_string_.. so is valid + // - caller will not use key after this call (convention) let key = unsafe { TCString::take_from_ptr_arg(key) }; wrap_mut( task, @@ -737,6 +789,8 @@ pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> *mut TCString<'stat // - task outlives 'a (promised by caller) let task: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; if let Some(tcstring) = task.error.take() { + // SAFETY: + // - caller promises to free this value unsafe { tcstring.return_ptr() } } else { std::ptr::null_mut() @@ -750,7 +804,7 @@ pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> *mut TCString<'stat #[no_mangle] pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) { // SAFETY: - // - rep is not NULL (promised by caller) + // - task is not NULL (promised by caller) // - caller will not use the TCTask after this (promised by caller) let mut tctask = unsafe { TCTask::take_from_ptr_arg(task) }; diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 9496e8b0b..1bc300cf1 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -92,15 +92,16 @@ pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) uuid.to_hyphenated().encode_lower(buf); } -/// 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. +/// Return the hyphenated string representation of a TCUuid. The returned string +/// must be freed with tc_string_free. #[no_mangle] pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; let s = uuid.to_string(); - // SAFETY: see TCString docstring + // SAFETY: + // - caller promises to free this value. unsafe { TCString::from(s).return_ptr() } } @@ -110,7 +111,10 @@ pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static pub unsafe extern "C" fn tc_uuid_from_str(s: *mut TCString, uuid_out: *mut TCUuid) -> TCResult { debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null()); - // SAFETY: see TCString docstring + // SAFETY: + // - s is not NULL (promised by caller) + // - s is return from a tc_string_.. so is valid + // - caller will not use s after this call (convention) let s = unsafe { TCString::take_from_ptr_arg(s) }; if let Ok(s) = s.as_str() { if let Ok(u) = Uuid::parse_str(s) { diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 41edeedff..0fd61ad9a 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -57,7 +57,7 @@ typedef enum TCStatus { * - 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` becmes invalid and must not be used again. + * Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. * * TCReplicas are not threadsafe. */ @@ -92,7 +92,7 @@ typedef struct TCServer TCServer; * # Safety * * When a `*TCString` appears as a return value or output argument, ownership is passed to the - * caller. The caller must pass that ownerhsip back to another function or free the string. + * caller. The caller must pass that ownership back to another function or free the string. * * Any function taking a `*TCReplica` requires: * - the pointer must not be NUL; @@ -101,7 +101,7 @@ typedef struct TCServer TCServer; * * Unless specified otherwise, TaskChampion functions take ownership of a `*TCString` when it is * given as a function argument, and the pointer is invalid when the function returns. Callers - * must not use or free TCStringList after passing them to such API functions. + * must not use or free TCStrings after passing them to such API functions. * * TCString is not threadsafe. */ @@ -123,6 +123,19 @@ typedef struct TCString TCString; * 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; @@ -138,6 +151,20 @@ typedef struct TCWorkingSet TCWorkingSet; /** * 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 { /** @@ -344,7 +371,7 @@ 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. + * lost when it is freed with tc_replica_free. */ struct TCReplica *tc_replica_new_in_memory(void); @@ -370,7 +397,8 @@ struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); /** - * Get the current working set for this replica. + * Get the current working set for this replica. The resulting value must be freed + * with tc_working_set_free. * * Returns NULL on error. */ @@ -728,7 +756,8 @@ TCResult tc_task_add_tag(struct TCTask *task, struct TCString *tag); TCResult tc_task_remove_tag(struct TCTask *task, struct TCString *tag); /** - * Add an annotation to a mutable task. + * 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); @@ -814,8 +843,8 @@ struct TCUuid tc_uuid_nil(void); void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); /** - * 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. + * 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); From 02055b122ea3bd935ded5aa8128eb9a5b83303c9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 16 Feb 2022 00:07:30 +0000 Subject: [PATCH 510/548] find shared library on macos as well --- integration-tests/build.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/integration-tests/build.rs b/integration-tests/build.rs index e1c5c2fbe..b5217f5a0 100644 --- a/integration-tests/build.rs +++ b/integration-tests/build.rs @@ -11,7 +11,11 @@ fn build_libtaskchampion(suites: &[&'static str]) { libtaskchampion.push("target"); libtaskchampion.push(env::var("PROFILE").unwrap()); libtaskchampion.push("deps"); - libtaskchampion.push("libtaskchampion.so"); + libtaskchampion.push(if cfg!(target_os = "macos") { + "libtaskchampion.dylib" + } else { + "libtaskchampion.so" + }); let mut build = cc::Build::new(); build.shared_flag(true); From b1d537ac8703b5340b435e27a6c8d76ec29103a6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 16 Feb 2022 00:28:07 +0000 Subject: [PATCH 511/548] use codegen, instead of build.rs, to build header file --- .cargo/config | 2 ++ Cargo.lock | 11 ++++++++--- Cargo.toml | 1 + README.md | 10 ++++++++-- lib/Cargo.toml | 4 ---- lib/build.rs | 26 -------------------------- xtask/Cargo.toml | 10 ++++++++++ xtask/src/main.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 76 insertions(+), 35 deletions(-) create mode 100644 .cargo/config delete mode 100644 lib/build.rs create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/main.rs diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 000000000..35049cbcb --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" diff --git a/Cargo.lock b/Cargo.lock index ca9160c27..3bdb7dc0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3035,12 +3035,9 @@ name = "taskchampion-lib" version = "0.1.0" dependencies = [ "anyhow", - "cbindgen", "chrono", "libc", - "pretty_assertions", "taskchampion", - "uuid", ] [[package]] @@ -3797,6 +3794,14 @@ dependencies = [ "time 0.1.43", ] +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "anyhow", + "cbindgen", +] + [[package]] name = "zeroize" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 18399678b..701f3839e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ members = [ "sync-server", "lib", "integration-tests", + "xtask", ] diff --git a/README.md b/README.md index 10e119460..6bf44be76 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,18 @@ But, if you just want to get some practice with Rust, we'd be happy to have you. ## Structure -There are four crates here: +There are five crates here: * [taskchampion](./taskchampion) - the core of the tool * [taskchampion-cli](./cli) - the command-line binary * [taskchampion-sync-server](./sync-server) - the server against which `task sync` operates - * [replica-server-tests](./replica-server-tests) - integration tests covering both _taskchampion-cli_ and _taskchampion-sync-server_ + * [taskchampion-lib](./lib) - glue code to use _taskchampion_ from C + * [integration-tests](./integration-tests) - integration tests covering _taskchampion-cli_, _taskchampion-sync-server_, and _taskchampion-lib_. + +## Code Generation + +The _taskchampion_lib_ crate uses a bit of code generation to create the `lib/taskchampion.h` header file. +To regenerate this file, run `cargo xtask codegen`. ## Documentation Generation diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 86a2ec735..564f28590 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -2,7 +2,6 @@ name = "taskchampion-lib" version = "0.1.0" edition = "2018" -build = "build.rs" [lib] name = "taskchampion" @@ -17,6 +16,3 @@ anyhow = "1.0" [dev-dependencies] pretty_assertions = "1" - -[build-dependencies] -cbindgen = "0.20.0" diff --git a/lib/build.rs b/lib/build.rs deleted file mode 100644 index f8f75d3b5..000000000 --- a/lib/build.rs +++ /dev/null @@ -1,26 +0,0 @@ -use cbindgen::*; - -use std::env; - -fn main() { - let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - - Builder::new() - .with_crate(crate_dir) - .with_config(Config { - language: Language::C, - 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("taskchampion.h"); -} diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 000000000..fe6c3dda6 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +cbindgen = "0.20.0" diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 000000000..036802e9a --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,47 @@ +//! This executable defines the `cargo xtask` subcommands. +//! +//! 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::path::PathBuf; + +pub fn main() -> anyhow::Result<()> { + let arg = env::args().nth(1); + match arg.as_ref().map(|arg| arg.as_str()) { + Some("codegen") => codegen(), + Some(arg) => anyhow::bail!("unknown xtask {}", arg), + _ => anyhow::bail!("unknown xtask"), + } +} + +/// `cargo xtask codegen` +/// +/// This uses cbindgen 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 { + language: Language::C, + 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")); + + Ok(()) +} From 741cb84430d0ff0e288879ed1fb863eef566a2c4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 16 Feb 2022 01:01:35 +0000 Subject: [PATCH 512/548] better docs for C --- Cargo.lock | 2 + lib/header-intro.h | 76 ++++++++++++++++++++++++++++++++++ lib/src/string.rs | 2 +- lib/src/workingset.rs | 16 ++++++++ lib/taskchampion.h | 96 ++++++++++++++++++++++++++++++++++++++++++- xtask/src/main.rs | 1 + 6 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 lib/header-intro.h diff --git a/Cargo.lock b/Cargo.lock index 3bdb7dc0f..256a68b3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3037,7 +3037,9 @@ dependencies = [ "anyhow", "chrono", "libc", + "pretty_assertions", "taskchampion", + "uuid", ] [[package]] diff --git a/lib/header-intro.h b/lib/header-intro.h new file mode 100644 index 000000000..c0dd01153 --- /dev/null +++ b/lib/header-intro.h @@ -0,0 +1,76 @@ +/** + * 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/lib/src/string.rs b/lib/src/string.rs index 3e0d4069e..70e4795ee 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -25,7 +25,7 @@ use std::str::Utf8Error; /// 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 `*TCReplica` requires: +/// 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. diff --git a/lib/src/workingset.rs b/lib/src/workingset.rs index 6e1e15b0f..672194886 100644 --- a/lib/src/workingset.rs +++ b/lib/src/workingset.rs @@ -7,6 +7,22 @@ use taskchampion::{Uuid, WorkingSet}; /// 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. pub struct TCWorkingSet(WorkingSet); impl PassByPointer for TCWorkingSet {} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 0fd61ad9a..702e24446 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -1,3 +1,81 @@ +/** + * 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. + */ + + #include #include #include @@ -94,7 +172,7 @@ typedef struct TCServer TCServer; * 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 `*TCReplica` requires: + * 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. @@ -146,6 +224,22 @@ typedef struct TCTask TCTask; * 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; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 036802e9a..3990a4f39 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -27,6 +27,7 @@ fn codegen() -> anyhow::Result<()> { Builder::new() .with_crate(&lib_crate_dir) .with_config(Config { + header: Some(include_str!("../../lib/header-intro.h").into()), language: Language::C, cpp_compat: true, sys_includes: vec!["stdbool.h".into(), "stdint.h".into(), "time.h".into()], From 2eee7616448593ad749df97b61b522e65c3642e3 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 16 Feb 2022 01:03:46 +0000 Subject: [PATCH 513/548] fix xtask Cargo.toml --- xtask/Cargo.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index fe6c3dda6..6deb10d58 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -1,9 +1,7 @@ [package] name = "xtask" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +version = "0.4.1" +edition = "2018" [dependencies] anyhow = "1.0" From 471119dbdf4d6a3c0870c5309e12df13517f8e06 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 17 Feb 2022 04:11:06 +0000 Subject: [PATCH 514/548] TCString as PassByValue --- Cargo.lock | 2 +- .../src/bindings_tests/replica.c | 76 +-- integration-tests/src/bindings_tests/string.c | 67 +-- integration-tests/src/bindings_tests/task.c | 140 ++--- integration-tests/src/bindings_tests/uuid.c | 6 +- lib/src/annotation.rs | 22 +- lib/src/kv.rs | 18 +- lib/src/lib.rs | 2 +- lib/src/replica.rs | 65 ++- lib/src/server.rs | 41 +- lib/src/string.rs | 498 +++++++++++++----- lib/src/task.rs | 219 ++++---- lib/src/traits.rs | 8 +- lib/src/uda.rs | 38 +- lib/src/util.rs | 15 +- lib/src/uuid.rs | 23 +- lib/taskchampion.h | 182 ++++--- 17 files changed, 853 insertions(+), 569 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 256a68b3e..fed2a869a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3798,7 +3798,7 @@ dependencies = [ [[package]] name = "xtask" -version = "0.1.0" +version = "0.4.1" dependencies = [ "anyhow", "cbindgen", diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 282d2948b..cd02d3a30 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -8,7 +8,7 @@ static void test_replica_creation(void) { TCReplica *rep = tc_replica_new_in_memory(); TEST_ASSERT_NOT_NULL(rep); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); tc_replica_free(rep); } @@ -16,28 +16,28 @@ static void test_replica_creation(void) { static void test_replica_creation_disk(void) { TCReplica *rep = tc_replica_new_on_disk(tc_string_borrow("test-db"), NULL); TEST_ASSERT_NOT_NULL(rep); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); tc_replica_free(rep); } // undo on an empty in-memory TCReplica does nothing static void test_replica_undo_empty(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); int undone; int rv = tc_replica_undo(rep, &undone); TEST_ASSERT_EQUAL(TC_RESULT_OK, rv); TEST_ASSERT_EQUAL(0, undone); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); tc_replica_free(rep); } // adding an undo point succeeds static void test_replica_add_undo_point(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_add_undo_point(rep, true)); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); tc_replica_free(rep); } @@ -48,10 +48,10 @@ static void test_replica_working_set(void) { TCUuid uuid, uuid1, uuid2, uuid3; TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true)); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); ws = tc_replica_working_set(rep); TEST_ASSERT_EQUAL(0, tc_working_set_len(ws)); @@ -62,7 +62,7 @@ static void test_replica_working_set(void) { uuid1 = tc_task_get_uuid(task1); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true)); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task2")); TEST_ASSERT_NOT_NULL(task2); @@ -73,7 +73,7 @@ static void test_replica_working_set(void) { uuid3 = tc_task_get_uuid(task3); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false)); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); // finish task2 to leave a "hole" tc_task_to_mut(task2, rep); @@ -81,7 +81,7 @@ static void test_replica_working_set(void) { tc_task_to_immut(task2); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false)); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); tc_task_free(task1); tc_task_free(task2); @@ -115,17 +115,17 @@ static void test_replica_working_set(void) { // When tc_replica_undo is passed NULL for undone_out, it still succeeds static void test_replica_undo_empty_null_undone_out(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); int rv = tc_replica_undo(rep, NULL); TEST_ASSERT_EQUAL(TC_RESULT_OK, rv); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); tc_replica_free(rep); } // creating a task succeeds and the resulting task looks good static void test_replica_task_creation(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -136,10 +136,10 @@ static void test_replica_task_creation(void) { TCUuid uuid = tc_task_get_uuid(task); TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - TCString *desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc); - TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(desc)); - tc_string_free(desc); + TCString desc = tc_task_get_description(task); + TEST_ASSERT_NOT_NULL(desc.ptr); + TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(&desc)); + tc_string_free(&desc); tc_task_free(task); @@ -157,18 +157,18 @@ static void test_replica_task_creation(void) { // When tc_replica_undo is passed NULL for undone_out, it still succeeds static void test_replica_sync_local(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); mkdir("test-sync-server", 0755); // ignore error, if dir already exists - TCString *err = NULL; + TCString err; TCServer *server = tc_server_new_local(tc_string_borrow("test-sync-server"), &err); TEST_ASSERT_NOT_NULL(server); - TEST_ASSERT_NULL(err); + TEST_ASSERT_NULL(err.ptr); int rv = tc_replica_sync(rep, server, false); TEST_ASSERT_EQUAL(TC_RESULT_OK, rv); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); tc_server_free(server); tc_replica_free(rep); @@ -176,20 +176,20 @@ static void test_replica_sync_local(void) { // test error handling server = tc_server_new_local(tc_string_borrow("/no/such/directory"), &err); TEST_ASSERT_NULL(server); - TEST_ASSERT_NOT_NULL(err); - tc_string_free(err); + TEST_ASSERT_NOT_NULL(err.ptr); + tc_string_free(&err); } // When tc_replica_undo is passed NULL for undone_out, it still succeeds static void test_replica_remote_server(void) { - TCString *err = NULL; + TCString err; TCServer *server = tc_server_new_remote( tc_string_borrow("tc.freecinc.com"), tc_uuid_new_v4(), tc_string_borrow("\xf0\x28\x8c\x28"), // NOTE: not utf-8 &err); TEST_ASSERT_NOT_NULL(server); - TEST_ASSERT_NULL(err); + TEST_ASSERT_NULL(err.ptr); // can't actually do anything with this server! @@ -199,7 +199,7 @@ static void test_replica_remote_server(void) { // a replica with tasks in it returns an appropriate list of tasks and list of uuids static void test_replica_all_tasks(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task1 = tc_replica_new_task( rep, @@ -225,13 +225,13 @@ static void test_replica_all_tasks(void) { bool seen1, seen2 = false; for (size_t i = 0; i < tasks.len; i++) { TCTask *task = tasks.items[i]; - TCString *descr = tc_task_get_description(task); - if (0 == strcmp(tc_string_content(descr), "task1")) { + TCString descr = tc_task_get_description(task); + if (0 == strcmp(tc_string_content(&descr), "task1")) { seen1 = true; - } else if (0 == strcmp(tc_string_content(descr), "task2")) { + } else if (0 == strcmp(tc_string_content(&descr), "task2")) { seen2 = true; } - tc_string_free(descr); + tc_string_free(&descr); } TEST_ASSERT_TRUE(seen1); TEST_ASSERT_TRUE(seen2); @@ -267,7 +267,7 @@ static void test_replica_all_tasks(void) { // importing a task succeeds and the resulting task looks good static void test_replica_task_import(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCUuid uuid; TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); @@ -277,10 +277,10 @@ static void test_replica_task_import(void) { TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes)); TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - TCString *desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc); - TEST_ASSERT_EQUAL_STRING("", tc_string_content(desc)); // default value - tc_string_free(desc); + TCString desc = tc_task_get_description(task); + TEST_ASSERT_NOT_NULL(desc.ptr); + TEST_ASSERT_EQUAL_STRING("", tc_string_content(&desc)); // default value + tc_string_free(&desc); tc_task_free(task); @@ -298,13 +298,13 @@ static void test_replica_task_import(void) { // importing a task succeeds and the resulting task looks good static void test_replica_get_task_not_found(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCUuid uuid; TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); TCTask *task = tc_replica_get_task(rep, uuid); TEST_ASSERT_NULL(task); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); } int replica_tests(void) { diff --git a/integration-tests/src/bindings_tests/string.c b/integration-tests/src/bindings_tests/string.c index 0eebec8f7..2bd2749c0 100644 --- a/integration-tests/src/bindings_tests/string.c +++ b/integration-tests/src/bindings_tests/string.c @@ -5,103 +5,110 @@ // creating strings does not crash static void test_string_creation(void) { - TCString *s = tc_string_borrow("abcdef"); - tc_string_free(s); + TCString s = tc_string_borrow("abcdef"); + tc_string_free(&s); + TEST_ASSERT_NULL(s.ptr); } // creating cloned strings does not crash static void test_string_cloning(void) { char *abcdef = strdup("abcdef"); - TCString *s = tc_string_clone(abcdef); - TEST_ASSERT_NOT_NULL(s); + TCString s = tc_string_clone(abcdef); + TEST_ASSERT_NOT_NULL(s.ptr); free(abcdef); - TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(s)); - tc_string_free(s); + TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(&s)); + tc_string_free(&s); + TEST_ASSERT_NULL(s.ptr); } // creating cloned strings with invalid utf-8 does not crash // ..but content is NULL and content_and_len returns the value static void test_string_cloning_invalid_utf8(void) { - TCString *s = tc_string_clone("\xf0\x28\x8c\x28"); - TEST_ASSERT_NOT_NULL(s); + TCString s = tc_string_clone("\xf0\x28\x8c\x28"); + TEST_ASSERT_NOT_NULL(s.ptr); // NOTE: this is not one of the cases where invalid UTF-8 results in NULL, // but that may change. size_t len; - const char *buf = tc_string_content_with_len(s, &len); + const char *buf = tc_string_content_with_len(&s, &len); TEST_ASSERT_NOT_NULL(buf); TEST_ASSERT_EQUAL(4, len); TEST_ASSERT_EQUAL_MEMORY("\xf0\x28\x8c\x28", buf, len); - tc_string_free(s); + tc_string_free(&s); + TEST_ASSERT_NULL(s.ptr); } // borrowed strings echo back their content static void test_string_borrowed_strings_echo(void) { - TCString *s = tc_string_borrow("abcdef"); - TEST_ASSERT_NOT_NULL(s); + TCString s = tc_string_borrow("abcdef"); + TEST_ASSERT_NOT_NULL(s.ptr); - TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(s)); + TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(&s)); size_t len; - const char *buf = tc_string_content_with_len(s, &len); + const char *buf = tc_string_content_with_len(&s, &len); TEST_ASSERT_NOT_NULL(buf); TEST_ASSERT_EQUAL(6, len); TEST_ASSERT_EQUAL_MEMORY("abcdef", buf, len); - tc_string_free(s); + tc_string_free(&s); + TEST_ASSERT_NULL(s.ptr); } // cloned strings echo back their content static void test_string_cloned_strings_echo(void) { char *orig = strdup("abcdef"); - TCString *s = tc_string_clone(orig); - TEST_ASSERT_NOT_NULL(s); + TCString s = tc_string_clone(orig); + TEST_ASSERT_NOT_NULL(s.ptr); free(orig); - TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(s)); + TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(&s)); size_t len; - const char *buf = tc_string_content_with_len(s, &len); + const char *buf = tc_string_content_with_len(&s, &len); TEST_ASSERT_NOT_NULL(buf); TEST_ASSERT_EQUAL(6, len); TEST_ASSERT_EQUAL_MEMORY("abcdef", buf, len); - tc_string_free(s); + tc_string_free(&s); + TEST_ASSERT_NULL(s.ptr); } // tc_clone_with_len can have NULs, and tc_string_content returns NULL for // strings containing embedded NULs static void test_string_content_null_for_embedded_nuls(void) { - TCString *s = tc_string_clone_with_len("ab\0de", 5); - TEST_ASSERT_NOT_NULL(s); + TCString s = tc_string_clone_with_len("ab\0de", 5); + TEST_ASSERT_NOT_NULL(s.ptr); - TEST_ASSERT_NULL(tc_string_content(s)); + TEST_ASSERT_NULL(tc_string_content(&s)); size_t len; - const char *buf = tc_string_content_with_len(s, &len); + const char *buf = tc_string_content_with_len(&s, &len); TEST_ASSERT_NOT_NULL(buf); TEST_ASSERT_EQUAL(5, len); TEST_ASSERT_EQUAL_MEMORY("ab\0de", buf, len); - tc_string_free(s); + tc_string_free(&s); + TEST_ASSERT_NULL(s.ptr); } // tc_string_clone_with_len will accept invalid utf-8, but then tc_string_content // returns NULL. static void test_string_clone_with_len_invalid_utf8(void) { - TCString *s = tc_string_clone_with_len("\xf0\x28\x8c\x28", 4); - TEST_ASSERT_NOT_NULL(s); + TCString s = tc_string_clone_with_len("\xf0\x28\x8c\x28", 4); + TEST_ASSERT_NOT_NULL(s.ptr); - TEST_ASSERT_NULL(tc_string_content(s)); + TEST_ASSERT_NULL(tc_string_content(&s)); size_t len; - const char *buf = tc_string_content_with_len(s, &len); + const char *buf = tc_string_content_with_len(&s, &len); TEST_ASSERT_NOT_NULL(buf); TEST_ASSERT_EQUAL(4, len); TEST_ASSERT_EQUAL_MEMORY("\xf0\x28\x8c\x28", buf, len); - tc_string_free(s); + tc_string_free(&s); + TEST_ASSERT_NULL(s.ptr); } int string_tests(void) { diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index b2412f3c3..f3ff29ae0 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -6,7 +6,7 @@ // creating a task succeeds and the resulting task looks good static void test_task_creation(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -16,10 +16,10 @@ static void test_task_creation(void) { TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - TCString *desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc); - TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(desc)); - tc_string_free(desc); + TCString desc = tc_task_get_description(task); + TEST_ASSERT_NOT_NULL(desc.ptr); + TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(&desc)); + tc_string_free(&desc); tc_task_free(task); @@ -29,7 +29,7 @@ static void test_task_creation(void) { // freeing a mutable task works, marking it immutable static void test_task_free_mutable_task(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -57,7 +57,7 @@ static void test_task_free_mutable_task(void) { // updating status on a task works static void test_task_get_set_status(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -81,7 +81,7 @@ static void test_task_get_set_status(void) { // updating description on a task works static void test_task_get_set_description(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -89,22 +89,22 @@ static void test_task_get_set_description(void) { tc_string_borrow("my task")); TEST_ASSERT_NOT_NULL(task); - TCString *desc; + TCString desc; tc_task_to_mut(task, rep); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_description(task, tc_string_borrow("updated"))); - TEST_ASSERT_TRUE(desc = tc_task_get_description(task)); - TEST_ASSERT_NOT_NULL(desc); - TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(desc)); - tc_string_free(desc); + desc = tc_task_get_description(task); + TEST_ASSERT_NOT_NULL(desc.ptr); + TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(&desc)); + tc_string_free(&desc); tc_task_to_immut(task); desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc); - TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(desc)); - tc_string_free(desc); + TEST_ASSERT_NOT_NULL(desc.ptr); + TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(&desc)); + tc_string_free(&desc); tc_task_free(task); @@ -114,7 +114,7 @@ static void test_task_get_set_description(void) { // updating entry on a task works static void test_task_get_set_entry(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -141,7 +141,7 @@ static void test_task_get_set_entry(void) { // updating wait on a task works static void test_task_get_set_wait_and_is_waiting(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -175,7 +175,7 @@ static void test_task_get_set_wait_and_is_waiting(void) { // updating modified on a task works static void test_task_get_set_modified(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -202,7 +202,7 @@ static void test_task_get_set_modified(void) { // starting and stopping a task works, as seen by tc_task_is_active static void test_task_start_stop_is_active(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -227,7 +227,7 @@ static void test_task_start_stop_is_active(void) { // tc_task_done and delete work and set the status static void test_task_done_and_delete(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -250,7 +250,7 @@ static void test_task_done_and_delete(void) { // adding and removing tags to a task works, and invalid tags are rejected static void test_task_add_remove_has_tag(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -263,33 +263,33 @@ static void test_task_add_remove_has_tag(void) { TEST_ASSERT_FALSE(tc_task_has_tag(task, tc_string_borrow("next"))); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); - TEST_ASSERT_NULL(tc_task_error(task)); + TEST_ASSERT_NULL(tc_task_error(task).ptr); TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next"))); // invalid - synthetic tag TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("PENDING"))); - TCString *err = tc_task_error(task); - TEST_ASSERT_NOT_NULL(err); - tc_string_free(err); + TCString err = tc_task_error(task); + TEST_ASSERT_NOT_NULL(err.ptr); + tc_string_free(&err); // invald - not a valid tag string TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("my tag"))); err = tc_task_error(task); - TEST_ASSERT_NOT_NULL(err); - tc_string_free(err); + TEST_ASSERT_NOT_NULL(err.ptr); + tc_string_free(&err); // invald - not utf-8 TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("\xf0\x28\x8c\x28"))); err = tc_task_error(task); - TEST_ASSERT_NOT_NULL(err); - tc_string_free(err); + TEST_ASSERT_NOT_NULL(err.ptr); + tc_string_free(&err); TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next"))); // remove the tag TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_remove_tag(task, tc_string_borrow("next"))); - TEST_ASSERT_NULL(tc_task_error(task)); + TEST_ASSERT_NULL(tc_task_error(task).ptr); TEST_ASSERT_FALSE(tc_task_has_tag(task, tc_string_borrow("next"))); @@ -300,7 +300,7 @@ static void test_task_add_remove_has_tag(void) { // get_tags returns the list of tags static void test_task_get_tags(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -316,10 +316,10 @@ static void test_task_get_tags(void) { int found_pending = false, found_next = false; for (size_t i = 0; i < tags.len; i++) { - if (strcmp("PENDING", tc_string_content(tags.items[i])) == 0) { + if (strcmp("PENDING", tc_string_content(&tags.items[i])) == 0) { found_pending = true; } - if (strcmp("next", tc_string_content(tags.items[i])) == 0) { + if (strcmp("next", tc_string_content(&tags.items[i])) == 0) { found_next = true; } } @@ -336,7 +336,7 @@ static void test_task_get_tags(void) { // annotation manipulation (add, remove, list, free) static void test_task_annotations(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -356,22 +356,22 @@ static void test_task_annotations(void) { ann.entry = 1644623411; ann.description = tc_string_borrow("ann1"); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann)); - TEST_ASSERT_NULL(ann.description); + TEST_ASSERT_NULL(ann.description.ptr); ann.entry = 1644623422; ann.description = tc_string_borrow("ann2"); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann)); - TEST_ASSERT_NULL(ann.description); + TEST_ASSERT_NULL(ann.description.ptr); anns = tc_task_get_annotations(task); int found1 = false, found2 = false; for (size_t i = 0; i < anns.len; i++) { - if (0 == strcmp("ann1", tc_string_content(anns.items[i].description))) { + if (0 == strcmp("ann1", tc_string_content(&anns.items[i].description))) { TEST_ASSERT_EQUAL(anns.items[i].entry, 1644623411); found1 = true; } - if (0 == strcmp("ann2", tc_string_content(anns.items[i].description))) { + if (0 == strcmp("ann2", tc_string_content(&anns.items[i].description))) { TEST_ASSERT_EQUAL(anns.items[i].entry, 1644623422); found2 = true; } @@ -389,7 +389,7 @@ static void test_task_annotations(void) { // UDA manipulation (add, remove, list, free) static void test_task_udas(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task( rep, @@ -399,11 +399,11 @@ static void test_task_udas(void) { tc_task_to_mut(task, rep); - TCString *value; + TCString value; TCUdaList udas; - TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1"))); - TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1"))); + TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")).ptr); + TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")).ptr); udas = tc_task_get_udas(task); TEST_ASSERT_NOT_NULL(udas.items); @@ -422,25 +422,25 @@ static void test_task_udas(void) { tc_string_borrow("vvv"))); value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")); - TEST_ASSERT_NOT_NULL(value); - TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(value)); - tc_string_free(value); - TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1"))); + TEST_ASSERT_NOT_NULL(value.ptr); + TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&value)); + tc_string_free(&value); + TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")).ptr); udas = tc_task_get_udas(task); TEST_ASSERT_NOT_NULL(udas.items); TEST_ASSERT_EQUAL(1, udas.len); - TEST_ASSERT_EQUAL_STRING("ns", tc_string_content(udas.items[0].ns)); - TEST_ASSERT_EQUAL_STRING("u1", tc_string_content(udas.items[0].key)); - TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(udas.items[0].value)); + TEST_ASSERT_EQUAL_STRING("ns", tc_string_content(&udas.items[0].ns)); + TEST_ASSERT_EQUAL_STRING("u1", tc_string_content(&udas.items[0].key)); + TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&udas.items[0].value)); tc_uda_list_free(&udas); udas = tc_task_get_legacy_udas(task); TEST_ASSERT_NOT_NULL(udas.items); TEST_ASSERT_EQUAL(1, udas.len); - TEST_ASSERT_NULL(udas.items[0].ns); - TEST_ASSERT_EQUAL_STRING("ns.u1", tc_string_content(udas.items[0].key)); - TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(udas.items[0].value)); + TEST_ASSERT_NULL(udas.items[0].ns.ptr); + TEST_ASSERT_EQUAL_STRING("ns.u1", tc_string_content(&udas.items[0].key)); + TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&udas.items[0].value)); tc_uda_list_free(&udas); TEST_ASSERT_EQUAL(TC_RESULT_OK, @@ -449,14 +449,14 @@ static void test_task_udas(void) { tc_string_borrow("legv"))); value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")); - TEST_ASSERT_NOT_NULL(value); - TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(value)); - tc_string_free(value); + TEST_ASSERT_NOT_NULL(value.ptr); + TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&value)); + tc_string_free(&value); value = tc_task_get_legacy_uda(task, tc_string_borrow("leg1")); - TEST_ASSERT_NOT_NULL(value); - TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(value)); - tc_string_free(value); + TEST_ASSERT_NOT_NULL(value.ptr); + TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(&value)); + tc_string_free(&value); udas = tc_task_get_udas(task); TEST_ASSERT_NOT_NULL(udas.items); @@ -473,7 +473,7 @@ static void test_task_udas(void) { tc_string_borrow("ns"), tc_string_borrow("u1"))); - TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1"))); + TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")).ptr); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_remove_uda(task, @@ -483,24 +483,24 @@ static void test_task_udas(void) { udas = tc_task_get_udas(task); TEST_ASSERT_NOT_NULL(udas.items); TEST_ASSERT_EQUAL(1, udas.len); - TEST_ASSERT_EQUAL_STRING("", tc_string_content(udas.items[0].ns)); - TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(udas.items[0].key)); - TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(udas.items[0].value)); + TEST_ASSERT_EQUAL_STRING("", tc_string_content(&udas.items[0].ns)); + TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(&udas.items[0].key)); + TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(&udas.items[0].value)); tc_uda_list_free(&udas); udas = tc_task_get_legacy_udas(task); TEST_ASSERT_NOT_NULL(udas.items); TEST_ASSERT_EQUAL(1, udas.len); - TEST_ASSERT_NULL(udas.items[0].ns); - TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(udas.items[0].key)); - TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(udas.items[0].value)); + TEST_ASSERT_NULL(udas.items[0].ns.ptr); + TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(&udas.items[0].key)); + TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(&udas.items[0].value)); tc_uda_list_free(&udas); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_remove_legacy_uda(task, tc_string_borrow("leg1"))); - TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1"))); + TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")).ptr); TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_remove_legacy_uda(task, @@ -523,8 +523,8 @@ static void test_task_udas(void) { static void tckvlist_assert_key(TCKVList *list, char *key, char *value) { TEST_ASSERT_NOT_NULL(list); for (size_t i = 0; i < list->len; i++) { - if (0 == strcmp(tc_string_content(list->items[i].key), key)) { - TEST_ASSERT_EQUAL_STRING(value, tc_string_content(list->items[i].value)); + if (0 == strcmp(tc_string_content(&list->items[i].key), key)) { + TEST_ASSERT_EQUAL_STRING(value, tc_string_content(&list->items[i].value)); return; } } @@ -534,7 +534,7 @@ static void tckvlist_assert_key(TCKVList *list, char *key, char *value) { // get_tags returns the list of tags static void test_task_taskmap(void) { TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep)); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); TCTask *task = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("my task")); TEST_ASSERT_NOT_NULL(task); diff --git a/integration-tests/src/bindings_tests/uuid.c b/integration-tests/src/bindings_tests/uuid.c index 0c9b72be1..83b02482d 100644 --- a/integration-tests/src/bindings_tests/uuid.c +++ b/integration-tests/src/bindings_tests/uuid.c @@ -22,11 +22,11 @@ static void test_uuid_to_buf(void) { // converting UUIDs to a buf works static void test_uuid_to_str(void) { TCUuid u = tc_uuid_nil(); - TCString *s = tc_uuid_to_str(u); + TCString s = tc_uuid_to_str(u); TEST_ASSERT_EQUAL_STRING( "00000000-0000-0000-0000-000000000000", - tc_string_content(s)); - tc_string_free(s); + tc_string_content(&s)); + tc_string_free(&s); } // converting valid UUIDs from string works diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index 7142eff62..774f94478 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -22,24 +22,24 @@ pub struct TCAnnotation { /// Time the annotation was made. Must be nonzero. pub entry: libc::time_t, /// Content of the annotation. Must not be NULL. - pub description: *mut TCString<'static>, + pub description: TCString, } impl PassByValue for TCAnnotation { // NOTE: we cannot use `RustType = Annotation` here because conversion of the - // TCString to a String can fail. - type RustType = (DateTime, TCString<'static>); + // Rust to a String can fail. + type RustType = (DateTime, RustString<'static>); - unsafe fn from_ctype(self) -> Self::RustType { + unsafe fn from_ctype(mut self) -> Self::RustType { // SAFETY: // - any time_t value is valid // - time_t is copy, so ownership is not important let entry = unsafe { libc::time_t::val_from_arg(self.entry) }.unwrap(); // SAFETY: - // - self.description is not NULL (field docstring) - // - self.description came from return_ptr in as_ctype + // - self.description is valid (came from return_val in as_ctype) // - self is owned, so we can take ownership of this TCString - let description = unsafe { TCString::take_from_ptr_arg(self.description) }; + let description = + unsafe { TCString::take_val_from_arg(&mut self.description, TCString::default()) }; (entry, description) } @@ -48,7 +48,7 @@ impl PassByValue for TCAnnotation { entry: libc::time_t::as_ctype(Some(entry)), // SAFETY: // - ownership of the TCString tied to ownership of Self - description: unsafe { description.return_ptr() }, + description: unsafe { TCString::return_val(description) }, } } } @@ -57,7 +57,7 @@ impl Default for TCAnnotation { fn default() -> Self { TCAnnotation { entry: 0 as libc::time_t, - description: std::ptr::null_mut(), + description: TCString::default(), } } } @@ -125,7 +125,7 @@ mod test { #[test] fn empty_list_has_non_null_pointer() { - let tcanns = TCAnnotationList::return_val(Vec::new()); + let tcanns = unsafe { TCAnnotationList::return_val(Vec::new()) }; assert!(!tcanns.items.is_null()); assert_eq!(tcanns.len, 0); assert_eq!(tcanns._capacity, 0); @@ -133,7 +133,7 @@ mod test { #[test] fn free_sets_null_pointer() { - let mut tcanns = TCAnnotationList::return_val(Vec::new()); + let mut tcanns = unsafe { TCAnnotationList::return_val(Vec::new()) }; // SAFETY: testing expected behavior unsafe { tc_annotation_list_free(&mut tcanns) }; assert!(tcanns.items.is_null()); diff --git a/lib/src/kv.rs b/lib/src/kv.rs index 1a6f61c00..716883c75 100644 --- a/lib/src/kv.rs +++ b/lib/src/kv.rs @@ -7,21 +7,21 @@ use crate::types::*; /// will be freed when it is freed with tc_kv_list_free. #[repr(C)] pub struct TCKV { - pub key: *mut TCString<'static>, - pub value: *mut TCString<'static>, + pub key: TCString, + pub value: TCString, } impl PassByValue for TCKV { - type RustType = (TCString<'static>, TCString<'static>); + type RustType = (RustString<'static>, RustString<'static>); unsafe fn from_ctype(self) -> Self::RustType { // SAFETY: // - self.key is not NULL (field docstring) // - self.key came from return_ptr in as_ctype // - self is owned, so we can take ownership of this TCString - let key = unsafe { TCString::take_from_ptr_arg(self.key) }; + let key = unsafe { TCString::val_from_arg(self.key) }; // SAFETY: (same) - let value = unsafe { TCString::take_from_ptr_arg(self.value) }; + let value = unsafe { TCString::val_from_arg(self.value) }; (key, value) } @@ -29,10 +29,10 @@ impl PassByValue for TCKV { TCKV { // SAFETY: // - ownership of the TCString tied to ownership of Self - key: unsafe { key.return_ptr() }, + key: unsafe { TCString::return_val(key) }, // SAFETY: // - ownership of the TCString tied to ownership of Self - value: unsafe { value.return_ptr() }, + value: unsafe { TCString::return_val(value) }, } } } @@ -88,7 +88,7 @@ mod test { #[test] fn empty_list_has_non_null_pointer() { - let tckvs = TCKVList::return_val(Vec::new()); + let tckvs = unsafe { TCKVList::return_val(Vec::new()) }; assert!(!tckvs.items.is_null()); assert_eq!(tckvs.len, 0); assert_eq!(tckvs._capacity, 0); @@ -96,7 +96,7 @@ mod test { #[test] fn free_sets_null_pointer() { - let mut tckvs = TCKVList::return_val(Vec::new()); + let mut tckvs = unsafe { TCKVList::return_val(Vec::new()) }; // SAFETY: testing expected behavior unsafe { tc_kv_list_free(&mut tckvs) }; assert!(tckvs.items.is_null()); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 614d57f7b..946342fb3 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -29,7 +29,7 @@ pub(crate) mod types { pub(crate) use crate::result::TCResult; pub(crate) use crate::server::TCServer; pub(crate) use crate::status::TCStatus; - pub(crate) use crate::string::{TCString, TCStringList}; + pub(crate) use crate::string::{RustString, TCString, TCStringList}; pub(crate) use crate::task::{TCTask, TCTaskList}; pub(crate) use crate::uda::{TCUda, TCUdaList, Uda}; pub(crate) use crate::uuid::{TCUuid, TCUuidList}; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index a35260b51..a1d488ede 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -1,6 +1,6 @@ use crate::traits::*; use crate::types::*; -use crate::util::err_to_tcstring; +use crate::util::err_to_ruststring; use std::ptr::NonNull; use taskchampion::{Replica, StorageConfig}; @@ -34,7 +34,7 @@ pub struct TCReplica { mut_borrowed: bool, /// The error from the most recent operation, if any - error: Option>, + error: Option>, } impl PassByPointer for TCReplica {} @@ -88,7 +88,7 @@ where match f(&mut rep.inner) { Ok(v) => v, Err(e) => { - rep.error = Some(err_to_tcstring(e)); + rep.error = Some(err_to_ruststring(e)); err_value } } @@ -111,14 +111,20 @@ pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { /// must free this string. #[no_mangle] pub unsafe extern "C" fn tc_replica_new_on_disk( - path: *mut TCString, - error_out: *mut *mut TCString, + path: TCString, + error_out: *mut TCString, ) -> *mut TCReplica { + if !error_out.is_null() { + // SAFETY: + // - error_out is not NULL (just checked) + // - properly aligned and valid (promised by caller) + unsafe { *error_out = TCString::default() }; + } + // SAFETY: - // - path is not NULL (promised by caller) - // - path is return from a tc_string_.. so is valid + // - path is valid (promised by caller) // - caller will not use path after this call (convention) - let path = unsafe { TCString::take_from_ptr_arg(path) }; + let path = unsafe { TCString::val_from_arg(path) }; let storage_res = StorageConfig::OnDisk { taskdb_dir: path.to_path_buf(), } @@ -130,11 +136,9 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( if !error_out.is_null() { unsafe { // SAFETY: - // - return_ptr: caller promises to free this string - // - *error_out: - // - error_out is not NULL (checked) - // - error_out points to a valid place for a pointer (caller promises) - *error_out = err_to_tcstring(e).return_ptr(); + // - error_out is not NULL (just checked) + // - properly aligned and valid (promised by caller) + TCString::val_to_arg_out(err_to_ruststring(e), error_out); } } return std::ptr::null_mut(); @@ -169,7 +173,9 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList .expect("TCTask::return_ptr returned NULL") }) .collect(); - Ok(TCTaskList::return_val(tasks)) + // SAFETY: + // - value is not allocated and need not be freed + Ok(unsafe { TCTaskList::return_val(tasks) }) }, TCTaskList::null_value(), ) @@ -178,6 +184,8 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList /// 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`. #[no_mangle] pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUuidList { wrap( @@ -186,9 +194,13 @@ pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUui let uuids: Vec<_> = rep .all_task_uuids()? .drain(..) - .map(TCUuid::return_val) + // SAFETY: + // - value is not allocated and need not be freed + .map(|uuid| unsafe { TCUuid::return_val(uuid) }) .collect(); - Ok(TCUuidList::return_val(uuids)) + // SAFETY: + // - value will be freed (promised by caller) + Ok(unsafe { TCUuidList::return_val(uuids) }) }, TCUuidList::null_value(), ) @@ -244,13 +256,12 @@ pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid pub unsafe extern "C" fn tc_replica_new_task( rep: *mut TCReplica, status: TCStatus, - description: *mut TCString, + description: TCString, ) -> *mut TCTask { // SAFETY: - // - description is not NULL (promised by caller) - // - description is return from a tc_string_.. so is valid + // - description is valid (promised by caller) // - caller will not use description after this call (convention) - let description = unsafe { TCString::take_from_ptr_arg(description) }; + let mut description = unsafe { TCString::val_from_arg(description) }; wrap( rep, |rep| { @@ -369,23 +380,23 @@ pub unsafe extern "C" fn tc_replica_rebuild_working_set( ) } -/// Get the latest error for a replica, or NULL if the last operation succeeded. Subsequent calls -/// to this function will return NULL. The rep pointer must not be NULL. The caller must free the -/// returned string. +/// 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. #[no_mangle] -pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> TCString { // SAFETY: // - rep is not NULL (promised by caller) // - *rep is a valid TCReplica (promised by caller) // - rep is valid for the duration of this function // - rep is not modified by anything else (not threadsafe) let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; - if let Some(tcstring) = rep.error.take() { + if let Some(rstring) = rep.error.take() { // SAFETY: // - caller promises to free this string - unsafe { tcstring.return_ptr() } + unsafe { TCString::return_val(rstring) } } else { - std::ptr::null_mut() + TCString::default() } } diff --git a/lib/src/server.rs b/lib/src/server.rs index 74feff34f..711fcacf5 100644 --- a/lib/src/server.rs +++ b/lib/src/server.rs @@ -1,6 +1,6 @@ use crate::traits::*; use crate::types::*; -use crate::util::err_to_tcstring; +use crate::util::err_to_ruststring; use taskchampion::{Server, ServerConfig}; /// TCServer represents an interface to a sync server. Aside from new and free, a server @@ -26,20 +26,26 @@ impl AsMut> for TCServer { } /// Utility function to allow using `?` notation to return an error value. -fn wrap(f: F, error_out: *mut *mut TCString, err_value: T) -> T +fn wrap(f: F, error_out: *mut TCString, err_value: T) -> T where F: FnOnce() -> anyhow::Result, { + if !error_out.is_null() { + // SAFETY: + // - error_out is not NULL (just checked) + // - properly aligned and valid (promised by caller) + unsafe { *error_out = TCString::default() }; + } + match f() { Ok(v) => v, Err(e) => { if !error_out.is_null() { // SAFETY: - // - error_out is not NULL (checked) - // - ..and points to a valid pointer (promised by caller) - // - caller will free this string (promised by caller) + // - error_out is not NULL (just checked) + // - properly aligned and valid (promised by caller) unsafe { - *error_out = err_to_tcstring(e).return_ptr(); + TCString::val_to_arg_out(err_to_ruststring(e), error_out); } } err_value @@ -56,16 +62,15 @@ where /// The server must be freed after it is used - tc_replica_sync does not automatically free it. #[no_mangle] pub unsafe extern "C" fn tc_server_new_local( - server_dir: *mut TCString, - error_out: *mut *mut TCString, + server_dir: TCString, + error_out: *mut TCString, ) -> *mut TCServer { wrap( || { // SAFETY: - // - server_dir is not NULL (promised by caller) - // - server_dir is return from a tc_string_.. so is valid + // - server_dir is valid (promised by caller) // - caller will not use server_dir after this call (convention) - let server_dir = unsafe { TCString::take_from_ptr_arg(server_dir) }; + let server_dir = unsafe { TCString::val_from_arg(server_dir) }; let server_config = ServerConfig::Local { server_dir: server_dir.to_path_buf(), }; @@ -87,30 +92,26 @@ pub unsafe extern "C" fn tc_server_new_local( /// The server must be freed after it is used - tc_replica_sync does not automatically free it. #[no_mangle] pub unsafe extern "C" fn tc_server_new_remote( - origin: *mut TCString, + origin: TCString, client_key: TCUuid, - encryption_secret: *mut TCString, - error_out: *mut *mut TCString, + encryption_secret: TCString, + error_out: *mut TCString, ) -> *mut TCServer { wrap( || { - debug_assert!(!origin.is_null()); - debug_assert!(!encryption_secret.is_null()); // SAFETY: - // - origin is not NULL // - origin is valid (promised by caller) // - origin ownership is transferred to this function - let origin = unsafe { TCString::take_from_ptr_arg(origin) }.into_string()?; + let origin = unsafe { TCString::val_from_arg(origin) }.into_string()?; // SAFETY: // - client_key is a valid Uuid (any 8-byte sequence counts) let client_key = unsafe { TCUuid::val_from_arg(client_key) }; // SAFETY: - // - encryption_secret is not NULL // - encryption_secret is valid (promised by caller) // - encryption_secret ownership is transferred to this function - let encryption_secret = unsafe { TCString::take_from_ptr_arg(encryption_secret) } + let encryption_secret = unsafe { TCString::val_from_arg(encryption_secret) } .as_bytes() .to_vec(); diff --git a/lib/src/string.rs b/lib/src/string.rs index 70e4795ee..3767f7bb2 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -1,9 +1,9 @@ use crate::traits::*; +use crate::util::{string_into_raw_parts, vec_into_raw_parts}; use std::ffi::{CStr, CString, OsStr}; +use std::os::raw::c_char; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; -use std::ptr::NonNull; -use std::str::Utf8Error; /// TCString supports passing strings into and out of the TaskChampion API. /// @@ -22,98 +22,273 @@ use std::str::Utf8Error; /// /// # Safety /// -/// When a `*TCString` appears as a return value or output argument, ownership is passed to the +/// 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: +/// 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 pointer is invalid when the function returns. Callers -/// must not use or free TCStrings after passing them to such API functions. +/// 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. +/// cbindgen:field-names=[ptr, _u1, _u2, _u3] +#[repr(C)] +pub struct TCString { + // defined based on the type + ptr: *mut libc::c_void, + len: usize, + cap: usize, + + // type of TCString this represents + ty: u8, +} + +// TODO: figure out how to ignore this but still use it in TCString +/// A discriminator for TCString +#[repr(u8)] +enum TCStringType { + /// Null. Nothing is contained in this string. + /// + /// * `ptr` is NULL. + /// * `len` and `cap` are zero. + Null = 0, + + /// A CString. + /// + /// * `ptr` is the result of CString::into_raw, containing a terminating NUL. It may not be + /// valid UTF-8. + /// * `len` and `cap` are zero. + CString, + + /// A CStr, referencing memory borrowed from C + /// + /// * `ptr` points to the string, containing a terminating NUL. It may not be valid UTF-8. + /// * `len` and `cap` are zero. + CStr, + + /// A String. + /// + /// * `ptr`, `len`, and `cap` are as would be returned from String::into_raw_parts. + String, + + /// A byte sequence. + /// + /// * `ptr`, `len`, and `cap` are as would be returned from Vec::into_raw_parts. + Bytes, +} + +impl Default for TCString { + fn default() -> Self { + TCString { + ptr: std::ptr::null_mut(), + len: 0, + cap: 0, + ty: TCStringType::Null as u8, + } + } +} + +impl TCString { + pub(crate) fn is_null(&self) -> bool { + self.ptr.is_null() + } +} + #[derive(PartialEq, Debug)] -pub enum TCString<'a> { +pub enum RustString<'a> { + Null, CString(CString), CStr(&'a CStr), String(String), - - /// This variant denotes an input string that was not valid UTF-8. This allows reporting this - /// error when the string is read, with the constructor remaining infallible. - InvalidUtf8(Utf8Error, Vec), - - /// None is the default value for TCString, but this variant is never seen by C code or by Rust - /// code outside of this module. - None, + Bytes(Vec), } -impl<'a> Default for TCString<'a> { +impl<'a> Default for RustString<'a> { fn default() -> Self { - TCString::None + RustString::Null } } -impl<'a> PassByPointer for TCString<'a> {} +impl PassByValue for TCString { + type RustType = RustString<'static>; -impl<'a> TCString<'a> { - /// Get a regular Rust &str for this value. - pub(crate) fn as_str(&self) -> Result<&str, std::str::Utf8Error> { - match self { - TCString::CString(cstring) => cstring.as_c_str().to_str(), - TCString::CStr(cstr) => cstr.to_str(), - TCString::String(string) => Ok(string.as_ref()), - TCString::InvalidUtf8(e, _) => Err(*e), - TCString::None => unreachable!(), + unsafe fn from_ctype(self) -> Self::RustType { + match self.ty { + ty if ty == TCStringType::CString as u8 => { + // SAFETY: + // - ptr was derived from CString::into_raw + // - data was not modified since that time (caller promises) + RustString::CString(unsafe { CString::from_raw(self.ptr as *mut c_char) }) + } + ty if ty == TCStringType::CStr as u8 => { + // SAFETY: + // - ptr was created by CStr::as_ptr + // - data was not modified since that time (caller promises) + RustString::CStr(unsafe { CStr::from_ptr(self.ptr as *mut c_char) }) + } + ty if ty == TCStringType::String as u8 => { + // SAFETY: + // - ptr was created by string_into_raw_parts + // - data was not modified since that time (caller promises) + RustString::String(unsafe { + String::from_raw_parts(self.ptr as *mut u8, self.len, self.cap) + }) + } + ty if ty == TCStringType::Bytes as u8 => { + // SAFETY: + // - ptr was created by vec_into_raw_parts + // - data was not modified since that time (caller promises) + RustString::Bytes(unsafe { + Vec::from_raw_parts(self.ptr as *mut u8, self.len, self.cap) + }) + } + _ => RustString::Null, } } - /// Consume this TCString and return an equivalent String, or an error if not - /// valid UTF-8. In the error condition, the original data is lost. - pub(crate) fn into_string(self) -> Result { + fn as_ctype(arg: Self::RustType) -> Self { + match arg { + RustString::Null => Self { + ty: TCStringType::Null as u8, + ..Default::default() + }, + RustString::CString(cstring) => Self { + ty: TCStringType::CString as u8, + ptr: cstring.into_raw() as *mut libc::c_void, + ..Default::default() + }, + RustString::CStr(cstr) => Self { + ty: TCStringType::CStr as u8, + ptr: cstr.as_ptr() as *mut libc::c_void, + ..Default::default() + }, + RustString::String(string) => { + let (ptr, len, cap) = string_into_raw_parts(string); + Self { + ty: TCStringType::String as u8, + ptr: ptr as *mut libc::c_void, + len, + cap, + } + } + RustString::Bytes(bytes) => { + let (ptr, len, cap) = vec_into_raw_parts(bytes); + Self { + ty: TCStringType::Bytes as u8, + ptr: ptr as *mut libc::c_void, + len, + cap, + } + } + } + } +} + +impl<'a> RustString<'a> { + /// Get a regular Rust &str for this value. + pub(crate) fn as_str(&mut self) -> Result<&str, std::str::Utf8Error> { match self { - TCString::CString(cstring) => cstring.into_string().map_err(|e| e.utf8_error()), - TCString::CStr(cstr) => cstr.to_str().map(|s| s.to_string()), - TCString::String(string) => Ok(string), - TCString::InvalidUtf8(e, _) => Err(e), - TCString::None => unreachable!(), + RustString::CString(cstring) => cstring.as_c_str().to_str(), + RustString::CStr(cstr) => cstr.to_str(), + RustString::String(ref string) => Ok(string.as_ref()), + RustString::Bytes(_) => { + self.bytes_to_string()?; + self.as_str() // now the String variant, so won't recurse + } + RustString::Null => unreachable!(), + } + } + + /// Consume this RustString and return an equivalent String, or an error if not + /// valid UTF-8. In the error condition, the original data is lost. + pub(crate) fn into_string(mut self) -> Result { + match self { + RustString::CString(cstring) => cstring.into_string().map_err(|e| e.utf8_error()), + RustString::CStr(cstr) => cstr.to_str().map(|s| s.to_string()), + RustString::String(string) => Ok(string), + RustString::Bytes(_) => { + self.bytes_to_string()?; + self.into_string() // now the String variant, so won't recurse + } + RustString::Null => unreachable!(), } } pub(crate) fn as_bytes(&self) -> &[u8] { match self { - TCString::CString(cstring) => cstring.as_bytes(), - TCString::CStr(cstr) => cstr.to_bytes(), - TCString::String(string) => string.as_bytes(), - TCString::InvalidUtf8(_, data) => data.as_ref(), - TCString::None => unreachable!(), + RustString::CString(cstring) => cstring.as_bytes(), + RustString::CStr(cstr) => cstr.to_bytes(), + RustString::String(string) => string.as_bytes(), + RustString::Bytes(bytes) => bytes.as_ref(), + RustString::Null => unreachable!(), } } - /// Convert the TCString, in place, into one of the C variants. If this is not + /// Convert the RustString, in place, from the Bytes to String variant. On successful return, + /// the RustString has variant RustString::String. + fn bytes_to_string(&mut self) -> Result<(), std::str::Utf8Error> { + let mut owned = RustString::Null; + // temporarily swap a Null value into self; we'll swap that back + // shortly. + std::mem::swap(self, &mut owned); + match owned { + RustString::Bytes(bytes) => match String::from_utf8(bytes) { + Ok(string) => { + *self = RustString::String(string); + Ok(()) + } + Err(e) => { + let (e, bytes) = (e.utf8_error(), e.into_bytes()); + // put self back as we found it + *self = RustString::Bytes(bytes); + Err(e) + } + }, + _ => { + // not bytes, so just swap back + std::mem::swap(self, &mut owned); + Ok(()) + } + } + } + + /// Convert the RustString, in place, into one of the C variants. If this is not /// possible, such as if the string contains an embedded NUL, then the string /// remains unchanged. - fn to_c_string_mut(&mut self) { - if matches!(self, TCString::String(_)) { - // we must take ownership of the String in order to try converting it, - // leaving the underlying TCString as its default (None) - if let TCString::String(string) = std::mem::take(self) { + fn string_to_cstring(&mut self) { + let mut owned = RustString::Null; + // temporarily swap a Null value into self; we'll swap that back shortly + std::mem::swap(self, &mut owned); + match owned { + RustString::String(string) => { match CString::new(string) { - Ok(cstring) => *self = TCString::CString(cstring), + Ok(cstring) => { + *self = RustString::CString(cstring); + } Err(nul_err) => { // recover the underlying String from the NulError and restore - // the TCString + // the RustString let original_bytes = nul_err.into_vec(); // SAFETY: original_bytes came from a String moments ago, so still valid utf8 let string = unsafe { String::from_utf8_unchecked(original_bytes) }; - *self = TCString::String(string); + *self = RustString::String(string); } } - } else { - // the `matches!` above verified self was a TCString::String - unreachable!() + } + _ => { + // not a CString, so just swap back + std::mem::swap(self, &mut owned); } } } @@ -125,18 +300,55 @@ impl<'a> TCString<'a> { } } -impl<'a> From for TCString<'a> { - fn from(string: String) -> TCString<'a> { - TCString::String(string) +impl<'a> From for RustString<'a> { + fn from(string: String) -> RustString<'a> { + RustString::String(string) } } -impl<'a> From<&str> for TCString<'static> { - fn from(string: &str) -> TCString<'static> { - TCString::String(string.to_string()) +impl<'a> From<&str> for RustString<'static> { + fn from(string: &str) -> RustString<'static> { + RustString::String(string.to_string()) } } +/// Utility function to borrow a TCString from a pointer arg, modify it, +/// and restore it. +/// +/// This implements a kind of "interior mutability", relying on the +/// single-threaded use of all TC* types. +/// +/// # SAFETY +/// +/// - tcstring must not be NULL +/// - *tcstring must be a valid TCString +/// - *tcstring must not be accessed by anything else, despite the *const +unsafe fn wrap(tcstring: *const TCString, f: F) -> T +where + F: FnOnce(&mut RustString) -> T, +{ + debug_assert!(!tcstring.is_null()); + + // SAFETY: + // - we have exclusive to *tcstring (promised by caller) + let tcstring = tcstring as *mut TCString; + + // SAFETY: + // - tcstring is not NULL + // - *tcstring is a valid string (promised by caller) + let mut rstring = unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) }; + + let rv = f(&mut rstring); + + // update the caller's TCString with the updated RustString + // SAFETY: + // - tcstring is not NULL (we just took from it) + // - tcstring points to valid memory (we just took from it) + unsafe { TCString::val_to_arg_out(rstring, tcstring) }; + + rv +} + /// TCStringList represents a list of strings. /// /// The content of this struct must be treated as read-only. @@ -151,11 +363,11 @@ pub struct TCStringList { /// TCStringList representing each string. these remain owned by the TCStringList instance and will /// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the /// *TCStringList at indexes 0..len-1 are not NULL. - items: *const NonNull>, + items: *const TCString, } impl CList for TCStringList { - type Element = NonNull>; + type Element = TCString; unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { TCStringList { @@ -185,7 +397,7 @@ impl CList for TCStringList { /// free(url); // string is no longer referenced and can be freed /// ``` #[no_mangle] -pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> TCString { debug_assert!(!cstr.is_null()); // SAFETY: // - cstr is not NULL (promised by caller, verified by assertion) @@ -195,13 +407,13 @@ pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCS let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; // SAFETY: // - caller promises to free this string - unsafe { TCString::CStr(cstr).return_ptr() } + unsafe { TCString::return_val(RustString::CStr(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. #[no_mangle] -pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> TCString { debug_assert!(!cstr.is_null()); // SAFETY: // - cstr is not NULL (promised by caller, verified by assertion) @@ -209,21 +421,24 @@ pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCSt // - cstr contains a valid NUL terminator (promised by caller) // - cstr's content will not change before it is destroyed (by C convention) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; + let cstring: CString = cstr.into(); // SAFETY: // - caller promises to free this string - unsafe { TCString::CString(cstr.into()).return_ptr() } + unsafe { TCString::return_val(RustString::CString(cstring)) } } /// 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. #[no_mangle] pub unsafe extern "C" fn tc_string_clone_with_len( buf: *const libc::c_char, len: usize, -) -> *mut TCString<'static> { +) -> TCString { debug_assert!(!buf.is_null()); debug_assert!(len < isize::MAX as usize); // SAFETY: @@ -237,47 +452,45 @@ pub unsafe extern "C" fn tc_string_clone_with_len( // allocate and copy into Rust-controlled memory let vec = slice.to_vec(); - // try converting to a string, which is the only variant that can contain embedded NULs. If - // the bytes are not valid utf-8, store that information for reporting later. - let tcstring = match String::from_utf8(vec) { - Ok(string) => TCString::String(string), - Err(e) => { - let (e, vec) = (e.utf8_error(), e.into_bytes()); - TCString::InvalidUtf8(e, vec) - } - }; - // SAFETY: // - caller promises to free this string - unsafe { tcstring.return_ptr() } + unsafe { TCString::return_val(RustString::Bytes(vec)) } } -/// Get the content of the string as a regular C string. The given string must not be NULL. The +/// 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. #[no_mangle] -pub unsafe extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { - // SAFETY: +pub unsafe extern "C" fn tc_string_content(tcstring: *const TCString) -> *const libc::c_char { + // SAFETY; // - tcstring is not NULL (promised by caller) - // - lifetime of tcstring outlives the lifetime of this function - // - lifetime of tcstring outlives the lifetime of the returned pointer (promised by caller) - let tcstring = unsafe { TCString::from_ptr_arg_ref_mut(tcstring) }; + // - *tcstring is valid (promised by caller) + // - *tcstring is not accessed concurrently (single-threaded) + unsafe { + wrap(tcstring, |rstring| { + // try to eliminate the Bytes variant. If this fails, we'll return NULL + // below, so the error is ignorable. + let _ = rstring.bytes_to_string(); - // if we have a String, we need to consume it and turn it into - // a CString. - tcstring.to_c_string_mut(); + // and eliminate the String variant + rstring.string_to_cstring(); - match tcstring { - TCString::CString(cstring) => cstring.as_ptr(), - TCString::String(_) => std::ptr::null(), // to_c_string_mut failed - TCString::CStr(cstr) => cstr.as_ptr(), - TCString::InvalidUtf8(_, _) => std::ptr::null(), - TCString::None => unreachable!(), + match &rstring { + RustString::CString(cstring) => cstring.as_ptr(), + RustString::String(_) => std::ptr::null(), // string_to_cstring failed + RustString::CStr(cstr) => cstr.as_ptr(), + RustString::Bytes(_) => std::ptr::null(), // already returned above + RustString::Null => unreachable!(), + } + }) } } @@ -286,26 +499,31 @@ pub unsafe extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const li /// 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. #[no_mangle] pub unsafe extern "C" fn tc_string_content_with_len( - tcstring: *mut TCString, + tcstring: *const TCString, len_out: *mut usize, ) -> *const libc::c_char { - // SAFETY: + // SAFETY; // - tcstring is not NULL (promised by caller) - // - lifetime of tcstring outlives the lifetime of this function - // - lifetime of tcstring outlives the lifetime of the returned pointer (promised by caller) - let tcstring = unsafe { TCString::from_ptr_arg_ref(tcstring) }; + // - *tcstring is valid (promised by caller) + // - *tcstring is not accessed concurrently (single-threaded) + unsafe { + wrap(tcstring, |rstring| { + let bytes = rstring.as_bytes(); - let bytes = tcstring.as_bytes(); - - // SAFETY: - // - len_out is not NULL (promised by caller) - // - len_out points to valid memory (promised by caller) - // - len_out is properly aligned (C convention) - unsafe { usize::val_to_arg_out(bytes.len(), len_out) }; - bytes.as_ptr() as *const libc::c_char + // SAFETY: + // - len_out is not NULL (promised by caller) + // - len_out points to valid memory (promised by caller) + // - len_out is properly aligned (C convention) + usize::val_to_arg_out(bytes.len(), len_out); + bytes.as_ptr() as *const libc::c_char + }) + } } /// Free a TCString. The given string must not be NULL. The string must not be used @@ -315,7 +533,7 @@ pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { // SAFETY: // - tcstring is not NULL (promised by caller) // - caller is exclusive owner of tcstring (promised by caller) - drop(unsafe { TCString::take_from_ptr_arg(tcstring) }); + drop(unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) }); } /// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after @@ -328,7 +546,7 @@ pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { // - tcstrings is not NULL and points to a valid TCStringList (caller is not allowed to // modify the list) // - caller promises not to use the value after return - unsafe { drop_pointer_list(tcstrings) }; + unsafe { drop_value_list(tcstrings) }; } #[cfg(test)] @@ -338,7 +556,7 @@ mod test { #[test] fn empty_list_has_non_null_pointer() { - let tcstrings = TCStringList::return_val(Vec::new()); + let tcstrings = unsafe { TCStringList::return_val(Vec::new()) }; assert!(!tcstrings.items.is_null()); assert_eq!(tcstrings.len, 0); assert_eq!(tcstrings._capacity, 0); @@ -346,7 +564,7 @@ mod test { #[test] fn free_sets_null_pointer() { - let mut tcstrings = TCStringList::return_val(Vec::new()); + let mut tcstrings = unsafe { TCStringList::return_val(Vec::new()) }; // SAFETY: testing expected behavior unsafe { tc_string_list_free(&mut tcstrings) }; assert!(tcstrings.items.is_null()); @@ -356,26 +574,29 @@ mod test { const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28"; - fn make_cstring() -> TCString<'static> { - TCString::CString(CString::new("a string").unwrap()) + fn make_cstring() -> RustString<'static> { + RustString::CString(CString::new("a string").unwrap()) } - fn make_cstr() -> TCString<'static> { + fn make_cstr() -> RustString<'static> { let cstr = CStr::from_bytes_with_nul(b"a string\0").unwrap(); - TCString::CStr(&cstr) + RustString::CStr(&cstr) } - fn make_string() -> TCString<'static> { - TCString::String("a string".into()) + fn make_string() -> RustString<'static> { + RustString::String("a string".into()) } - fn make_string_with_nul() -> TCString<'static> { - TCString::String("a \0 nul!".into()) + fn make_string_with_nul() -> RustString<'static> { + RustString::String("a \0 nul!".into()) } - fn make_invalid() -> TCString<'static> { - let e = String::from_utf8(INVALID_UTF8.to_vec()).unwrap_err(); - TCString::InvalidUtf8(e.utf8_error(), e.into_bytes()) + fn make_invalid_bytes() -> RustString<'static> { + RustString::Bytes(INVALID_UTF8.to_vec()) + } + + fn make_bytes() -> RustString<'static> { + RustString::Bytes(b"bytes".to_vec()) } #[test] @@ -399,11 +620,16 @@ mod test { } #[test] - fn invalid_as_str() { - let as_str_err = make_invalid().as_str().unwrap_err(); + fn invalid_bytes_as_str() { + let as_str_err = make_invalid_bytes().as_str().unwrap_err(); assert_eq!(as_str_err.valid_up_to(), 3); // "abc" is valid } + #[test] + fn valid_bytes_as_str() { + assert_eq!(make_bytes().as_str().unwrap(), "bytes"); + } + #[test] fn cstring_as_bytes() { assert_eq!(make_cstring().as_bytes(), b"a string"); @@ -425,42 +651,42 @@ mod test { } #[test] - fn invalid_as_bytes() { - assert_eq!(make_invalid().as_bytes(), INVALID_UTF8); + fn invalid_bytes_as_bytes() { + assert_eq!(make_invalid_bytes().as_bytes(), INVALID_UTF8); } #[test] - fn cstring_to_c_string_mut() { + fn cstring_string_to_cstring() { let mut tcstring = make_cstring(); - tcstring.to_c_string_mut(); + tcstring.string_to_cstring(); assert_eq!(tcstring, make_cstring()); // unchanged } #[test] - fn cstr_to_c_string_mut() { + fn cstr_string_to_cstring() { let mut tcstring = make_cstr(); - tcstring.to_c_string_mut(); + tcstring.string_to_cstring(); assert_eq!(tcstring, make_cstr()); // unchanged } #[test] - fn string_to_c_string_mut() { + fn string_string_to_cstring() { let mut tcstring = make_string(); - tcstring.to_c_string_mut(); + tcstring.string_to_cstring(); assert_eq!(tcstring, make_cstring()); // converted to CString, same content } #[test] - fn string_with_nul_to_c_string_mut() { + fn string_with_nul_string_to_cstring() { let mut tcstring = make_string_with_nul(); - tcstring.to_c_string_mut(); + tcstring.string_to_cstring(); assert_eq!(tcstring, make_string_with_nul()); // unchanged } #[test] - fn invalid_to_c_string_mut() { - let mut tcstring = make_invalid(); - tcstring.to_c_string_mut(); - assert_eq!(tcstring, make_invalid()); // unchanged + fn bytes_string_to_cstring() { + let mut tcstring = make_bytes(); + tcstring.string_to_cstring(); + assert_eq!(tcstring, make_bytes()); // unchanged } } diff --git a/lib/src/task.rs b/lib/src/task.rs index ccea9fa20..c3c0b896b 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,6 +1,6 @@ use crate::traits::*; use crate::types::*; -use crate::util::err_to_tcstring; +use crate::util::err_to_ruststring; use chrono::{TimeZone, Utc}; use std::convert::TryFrom; use std::ops::Deref; @@ -42,7 +42,7 @@ pub struct TCTask { inner: Inner, /// The error from the most recent operation, if any - error: Option>, + error: Option>, } enum Inner { @@ -154,17 +154,17 @@ where match f(task) { Ok(rv) => rv, Err(e) => { - tctask.error = Some(err_to_tcstring(e)); + tctask.error = Some(err_to_ruststring(e)); err_value } } } -impl TryFrom> for Tag { +impl TryFrom> for Tag { type Error = anyhow::Error; - fn try_from(tcstring: TCString) -> Result { - let tagstr = tcstring.as_str()?; + fn try_from(mut rstring: RustString) -> Result { + let tagstr = rstring.as_str()?; Tag::from_str(tagstr) } } @@ -248,7 +248,11 @@ pub unsafe extern "C" fn tc_task_to_immut(task: *mut TCTask) { /// Get a task's UUID. #[no_mangle] pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { - wrap(task, |task| TCUuid::return_val(task.get_uuid())) + wrap(task, |task| { + // SAFETY: + // - value is not allocated and need not be freed + unsafe { TCUuid::return_val(task.get_uuid()) } + }) } /// Get a task's status. @@ -259,7 +263,7 @@ pub unsafe extern "C" fn tc_task_get_status(task: *mut TCTask) -> TCStatus { /// 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. +/// modified. It is the caller's responsibility to free the TCKVList. #[no_mangle] pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { wrap(task, |task| { @@ -267,24 +271,26 @@ pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { .get_taskmap() .iter() .map(|(k, v)| { - let key = TCString::from(k.as_ref()); - let value = TCString::from(v.as_ref()); + let key = RustString::from(k.as_ref()); + let value = RustString::from(v.as_ref()); TCKV::as_ctype((key, value)) }) .collect(); - TCKVList::return_val(vec) + // SAFETY: + // - caller will free this list + unsafe { TCKVList::return_val(vec) } }) } /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). #[no_mangle] -pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> TCString { wrap(task, |task| { - let descr: TCString = task.get_description().into(); + let descr = task.get_description(); // SAFETY: // - caller promises to free this string - unsafe { descr.return_ptr() } + unsafe { TCString::return_val(descr.into()) } }) } @@ -321,12 +327,11 @@ pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { /// 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`. #[no_mangle] -pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: *mut TCString) -> bool { +pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: TCString) -> bool { // SAFETY: - // - tag is not NULL (promised by caller) - // - tag is return from a tc_string_.. so is valid + // - tag is valid (promised by caller) // - caller will not use tag after this call (convention) - let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; + let tcstring = unsafe { TCString::val_from_arg(tag) }; wrap(task, |task| { if let Ok(tag) = Tag::try_from(tcstring) { task.has_tag(&tag) @@ -343,18 +348,17 @@ pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: *mut TCString) #[no_mangle] pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList { wrap(task, |task| { - let vec: Vec>> = task + let vec: Vec = task .get_tags() .map(|t| { - NonNull::new( - // SAFETY: - // - this TCString will be freed via tc_string_list_free. - unsafe { TCString::from(t.as_ref()).return_ptr() }, - ) - .expect("TCString::return_ptr() returned NULL") + // SAFETY: + // - this TCString will be freed via tc_string_list_free. + unsafe { TCString::return_val(t.as_ref().into()) } }) .collect(); - TCStringList::return_val(vec) + // SAFETY: + // - caller will free the list + unsafe { TCStringList::return_val(vec) } }) } @@ -368,39 +372,40 @@ pub unsafe extern "C" fn tc_task_get_annotations(task: *mut TCTask) -> TCAnnotat let vec: Vec = task .get_annotations() .map(|a| { - let description = TCString::from(a.description); + let description = RustString::from(a.description); TCAnnotation::as_ctype((a.entry, description)) }) .collect(); - TCAnnotationList::return_val(vec) + // SAFETY: + // - caller will free the list + unsafe { TCAnnotationList::return_val(vec) } }) } /// Get the named UDA from the task. /// -/// Returns NULL if the UDA does not exist. +/// Returns a TCString with NULL ptr field if the UDA does not exist. #[no_mangle] pub unsafe extern "C" fn tc_task_get_uda<'a>( task: *mut TCTask, - ns: *mut TCString<'a>, - key: *mut TCString<'a>, -) -> *mut TCString<'static> { + ns: TCString, + key: TCString, +) -> TCString { wrap(task, |task| { // SAFETY: - // - ns is not NULL (promised by caller) - // - ns is return from a tc_string_.. so is valid + // - ns is valid (promised by caller) // - caller will not use ns after this call (convention) - if let Ok(ns) = unsafe { TCString::take_from_ptr_arg(ns) }.as_str() { + if let Ok(ns) = unsafe { TCString::val_from_arg(ns) }.as_str() { // SAFETY: same - if let Ok(key) = unsafe { TCString::take_from_ptr_arg(key) }.as_str() { + if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() { if let Some(value) = task.get_uda(ns, key) { // SAFETY: // - caller will free this string (caller promises) - return unsafe { TCString::return_ptr(value.into()) }; + return unsafe { TCString::return_val(value.into()) }; } } } - std::ptr::null_mut() + TCString::default() }) } @@ -408,23 +413,19 @@ pub unsafe extern "C" fn tc_task_get_uda<'a>( /// /// Returns NULL if the UDA does not exist. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>( - task: *mut TCTask, - key: *mut TCString<'a>, -) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>(task: *mut TCTask, key: TCString) -> TCString { wrap(task, |task| { // SAFETY: - // - key is not NULL (promised by caller) - // - key is return from a tc_string_.. so is valid + // - key is valid (promised by caller) // - caller will not use key after this call (convention) - if let Ok(key) = unsafe { TCString::take_from_ptr_arg(key) }.as_str() { + if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() { if let Some(value) = task.get_legacy_uda(key) { // SAFETY: // - caller will free this string (caller promises) - return unsafe { TCString::return_ptr(value.into()) }; + return unsafe { TCString::return_val(value.into()) }; } } - std::ptr::null_mut() + TCString::default() }) } @@ -437,35 +438,47 @@ pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUdaList { let vec: Vec = task .get_udas() .map(|((ns, key), value)| { - TCUda::return_val(Uda { - ns: Some(ns.into()), - key: key.into(), - value: value.into(), - }) + // SAFETY: + // - will be freed by tc_uda_list_free + unsafe { + TCUda::return_val(Uda { + ns: Some(ns.into()), + key: key.into(), + value: value.into(), + }) + } }) .collect(); - TCUdaList::return_val(vec) + // SAFETY: + // - caller will free this list + unsafe { TCUdaList::return_val(vec) } }) } /// 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. +/// included in the key field. The caller must free the returned list. #[no_mangle] pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUdaList { wrap(task, |task| { let vec: Vec = task .get_legacy_udas() .map(|(key, value)| { - TCUda::return_val(Uda { - ns: None, - key: key.into(), - value: value.into(), - }) + // SAFETY: + // - will be freed by tc_uda_list_free + unsafe { + TCUda::return_val(Uda { + ns: None, + key: key.into(), + value: value.into(), + }) + } }) .collect(); - TCUdaList::return_val(vec) + // SAFETY: + // - caller will free this list + unsafe { TCUdaList::return_val(vec) } }) } @@ -486,13 +499,12 @@ pub unsafe extern "C" fn tc_task_set_status(task: *mut TCTask, status: TCStatus) #[no_mangle] pub unsafe extern "C" fn tc_task_set_description( task: *mut TCTask, - description: *mut TCString, + description: TCString, ) -> TCResult { // SAFETY: - // - description is not NULL (promised by caller) - // - description is return from a tc_string_.. so is valid + // - description is valid (promised by caller) // - caller will not use description after this call (convention) - let description = unsafe { TCString::take_from_ptr_arg(description) }; + let mut description = unsafe { TCString::val_from_arg(description) }; wrap_mut( task, |task| { @@ -606,12 +618,11 @@ pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { /// Add a tag to a mutable task. #[no_mangle] -pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { +pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: TCString) -> TCResult { // SAFETY: - // - tag is not NULL (promised by caller) - // - tag is return from a tc_string_.. so is valid + // - tag is valid (promised by caller) // - caller will not use tag after this call (convention) - let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; + let tcstring = unsafe { TCString::val_from_arg(tag) }; wrap_mut( task, |task| { @@ -625,12 +636,11 @@ pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) /// Remove a tag from a mutable task. #[no_mangle] -pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { +pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: TCString) -> TCResult { // SAFETY: - // - tag is not NULL (promised by caller) - // - tag is return from a tc_string_.. so is valid + // - tag is valid (promised by caller) // - caller will not use tag after this call (convention) - let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; + let tcstring = unsafe { TCString::val_from_arg(tag) }; wrap_mut( task, |task| { @@ -683,19 +693,18 @@ pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64 #[no_mangle] pub unsafe extern "C" fn tc_task_set_uda( task: *mut TCTask, - ns: *mut TCString, - key: *mut TCString, - value: *mut TCString, + ns: TCString, + key: TCString, + value: TCString, ) -> TCResult { // safety: - // - ns is not null (promised by caller) - // - ns is return from a tc_string_.. so is valid + // - ns is valid (promised by caller) // - caller will not use ns after this call (convention) - let ns = unsafe { TCString::take_from_ptr_arg(ns) }; + let mut ns = unsafe { TCString::val_from_arg(ns) }; // SAFETY: same - let key = unsafe { TCString::take_from_ptr_arg(key) }; + let mut key = unsafe { TCString::val_from_arg(key) }; // SAFETY: same - let value = unsafe { TCString::take_from_ptr_arg(value) }; + let mut value = unsafe { TCString::val_from_arg(value) }; wrap_mut( task, |task| { @@ -714,16 +723,15 @@ pub unsafe extern "C" fn tc_task_set_uda( #[no_mangle] pub unsafe extern "C" fn tc_task_remove_uda( task: *mut TCTask, - ns: *mut TCString, - key: *mut TCString, + ns: TCString, + key: TCString, ) -> TCResult { // safety: - // - ns is not null (promised by caller) - // - ns is return from a tc_string_.. so is valid + // - ns is valid (promised by caller) // - caller will not use ns after this call (convention) - let ns = unsafe { TCString::take_from_ptr_arg(ns) }; + let mut ns = unsafe { TCString::val_from_arg(ns) }; // SAFETY: same - let key = unsafe { TCString::take_from_ptr_arg(key) }; + let mut key = unsafe { TCString::val_from_arg(key) }; wrap_mut( task, |task| { @@ -738,16 +746,15 @@ pub unsafe extern "C" fn tc_task_remove_uda( #[no_mangle] pub unsafe extern "C" fn tc_task_set_legacy_uda( task: *mut TCTask, - key: *mut TCString, - value: *mut TCString, + key: TCString, + value: TCString, ) -> TCResult { // safety: - // - key is not null (promised by caller) - // - key is return from a tc_string_.. so is valid + // - key is valid (promised by caller) // - caller will not use key after this call (convention) - let key = unsafe { TCString::take_from_ptr_arg(key) }; + let mut key = unsafe { TCString::val_from_arg(key) }; // SAFETY: same - let value = unsafe { TCString::take_from_ptr_arg(value) }; + let mut value = unsafe { TCString::val_from_arg(value) }; wrap_mut( task, |task| { @@ -760,15 +767,11 @@ pub unsafe extern "C" fn tc_task_set_legacy_uda( /// Remove a UDA fraom a mutable task. #[no_mangle] -pub unsafe extern "C" fn tc_task_remove_legacy_uda( - task: *mut TCTask, - key: *mut TCString, -) -> TCResult { +pub unsafe extern "C" fn tc_task_remove_legacy_uda(task: *mut TCTask, key: TCString) -> TCResult { // safety: - // - key is not null (promised by caller) - // - key is return from a tc_string_.. so is valid + // - key is valid (promised by caller) // - caller will not use key after this call (convention) - let key = unsafe { TCString::take_from_ptr_arg(key) }; + let mut key = unsafe { TCString::val_from_arg(key) }; wrap_mut( task, |task| { @@ -779,21 +782,21 @@ pub unsafe extern "C" fn tc_task_remove_legacy_uda( ) } -/// Get the latest error for a task, or NULL 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. +/// 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. #[no_mangle] -pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> TCString { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) let task: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - if let Some(tcstring) = task.error.take() { + if let Some(rstring) = task.error.take() { // SAFETY: // - caller promises to free this value - unsafe { tcstring.return_ptr() } + unsafe { TCString::return_val(rstring) } } else { - std::ptr::null_mut() + TCString::default() } } @@ -833,7 +836,7 @@ mod test { #[test] fn empty_list_has_non_null_pointer() { - let tctasks = TCTaskList::return_val(Vec::new()); + let tctasks = unsafe { TCTaskList::return_val(Vec::new()) }; assert!(!tctasks.items.is_null()); assert_eq!(tctasks.len, 0); assert_eq!(tctasks._capacity, 0); @@ -841,7 +844,7 @@ mod test { #[test] fn free_sets_null_pointer() { - let mut tctasks = TCTaskList::return_val(Vec::new()); + let mut tctasks = unsafe { TCTaskList::return_val(Vec::new()) }; // SAFETY: testing expected behavior unsafe { tc_task_list_free(&mut tctasks) }; assert!(tctasks.items.is_null()); diff --git a/lib/src/traits.rs b/lib/src/traits.rs index e71ab0b9f..ffbdf9a79 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -28,8 +28,6 @@ pub(crate) trait PassByValue: Sized { /// /// - `self` must be a valid instance of the C type. This is typically ensured either by /// requiring that C code not modify it, or by defining the valid values in C comments. - /// - if RustType is not Copy, then arg must not be used by the caller after calling this - /// function unsafe fn val_from_arg(arg: Self) -> Self::RustType { // SAFETY: // - arg is a valid CType (promised by caller) @@ -54,7 +52,11 @@ pub(crate) trait PassByValue: Sized { } /// Return a value to C - fn return_val(arg: Self::RustType) -> Self { + /// + /// # Safety + /// + /// - if the value is allocated, the caller must ensure that the value is eventually freed + unsafe fn return_val(arg: Self::RustType) -> Self { Self::as_ctype(arg) } diff --git a/lib/src/uda.rs b/lib/src/uda.rs index 3712932ad..607d77e27 100644 --- a/lib/src/uda.rs +++ b/lib/src/uda.rs @@ -4,18 +4,18 @@ use crate::types::*; /// TCUda contains the details of a UDA. #[repr(C)] pub struct TCUda { - /// Namespace of the UDA. For legacy UDAs, this is NULL. - pub ns: *mut TCString<'static>, + /// Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. + pub ns: TCString, /// UDA key. Must not be NULL. - pub key: *mut TCString<'static>, + pub key: TCString, /// Content of the UDA. Must not be NULL. - pub value: *mut TCString<'static>, + pub value: TCString, } pub(crate) struct Uda { - pub ns: Option>, - pub key: TCString<'static>, - pub value: TCString<'static>, + pub ns: Option>, + pub key: RustString<'static>, + pub value: RustString<'static>, } impl PassByValue for TCUda { @@ -29,16 +29,16 @@ impl PassByValue for TCUda { // SAFETY: // - self is owned, so we can take ownership of this TCString // - self.ns is a valid, non-null TCString (NULL just checked) - Some(unsafe { TCString::take_from_ptr_arg(self.ns) }) + Some(unsafe { TCString::val_from_arg(self.ns) }) }, // SAFETY: // - self is owned, so we can take ownership of this TCString // - self.key is a valid, non-null TCString (see type docstring) - key: unsafe { TCString::take_from_ptr_arg(self.key) }, + key: unsafe { TCString::val_from_arg(self.key) }, // SAFETY: // - self is owned, so we can take ownership of this TCString // - self.value is a valid, non-null TCString (see type docstring) - value: unsafe { TCString::take_from_ptr_arg(self.value) }, + value: unsafe { TCString::val_from_arg(self.value) }, } } @@ -46,14 +46,14 @@ impl PassByValue for TCUda { TCUda { // SAFETY: caller assumes ownership of this value ns: if let Some(ns) = uda.ns { - unsafe { ns.return_ptr() } + unsafe { TCString::return_val(ns) } } else { - std::ptr::null_mut() + TCString::default() }, // SAFETY: caller assumes ownership of this value - key: unsafe { uda.key.return_ptr() }, + key: unsafe { TCString::return_val(uda.key) }, // SAFETY: caller assumes ownership of this value - value: unsafe { uda.value.return_ptr() }, + value: unsafe { TCString::return_val(uda.value) }, } } } @@ -61,9 +61,9 @@ impl PassByValue for TCUda { impl Default for TCUda { fn default() -> Self { TCUda { - ns: std::ptr::null_mut(), - key: std::ptr::null_mut(), - value: std::ptr::null_mut(), + ns: TCString::default(), + key: TCString::default(), + value: TCString::default(), } } } @@ -130,7 +130,7 @@ mod test { #[test] fn empty_list_has_non_null_pointer() { - let tcudas = TCUdaList::return_val(Vec::new()); + let tcudas = unsafe { TCUdaList::return_val(Vec::new()) }; assert!(!tcudas.items.is_null()); assert_eq!(tcudas.len, 0); assert_eq!(tcudas._capacity, 0); @@ -138,7 +138,7 @@ mod test { #[test] fn free_sets_null_pointer() { - let mut tcudas = TCUdaList::return_val(Vec::new()); + let mut tcudas = unsafe { TCUdaList::return_val(Vec::new()) }; // SAFETY: testing expected behavior unsafe { tc_uda_list_free(&mut tcudas) }; assert!(tcudas.items.is_null()); diff --git a/lib/src/util.rs b/lib/src/util.rs index 405a8b292..bfd739282 100644 --- a/lib/src/util.rs +++ b/lib/src/util.rs @@ -1,7 +1,7 @@ -use crate::string::TCString; +use crate::string::RustString; -pub(crate) fn err_to_tcstring(e: impl std::string::ToString) -> TCString<'static> { - TCString::from(e.to_string()) +pub(crate) fn err_to_ruststring(e: impl std::string::ToString) -> RustString<'static> { + RustString::from(e.to_string()) } /// An implementation of Vec::into_raw_parts, which is still unstable. Returns ptr, len, cap. @@ -12,3 +12,12 @@ pub(crate) fn vec_into_raw_parts(vec: Vec) -> (*mut T, usize, usize) { let mut vec = std::mem::ManuallyDrop::new(vec); (vec.as_mut_ptr(), vec.len(), vec.capacity()) } + +/// An implementation of String::into_raw_parts, which is still unstable. Returns ptr, len, cap. +pub(crate) fn string_into_raw_parts(string: String) -> (*mut u8, usize, usize) { + // emulate String::into_raw_parts(): + // - disable dropping the String with ManuallyDrop + // - extract ptr, len, and capacity using those methods + let mut string = std::mem::ManuallyDrop::new(string); + (string.as_mut_ptr(), string.len(), string.capacity()) +} diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 1bc300cf1..c4ec8f474 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -31,13 +31,17 @@ impl PassByValue for TCUuid { /// Create a new, randomly-generated UUID. #[no_mangle] pub unsafe extern "C" fn tc_uuid_new_v4() -> TCUuid { - TCUuid::return_val(Uuid::new_v4()) + // 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 { - TCUuid::return_val(Uuid::nil()) + // SAFETY: + // - value is not allocated + unsafe { TCUuid::return_val(Uuid::nil()) } } /// TCUuidList represents a list of uuids. @@ -95,27 +99,26 @@ pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) /// Return the hyphenated string representation of a TCUuid. The returned string /// must be freed with tc_string_free. #[no_mangle] -pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> TCString { // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; let s = uuid.to_string(); // SAFETY: // - caller promises to free this value. - unsafe { TCString::from(s).return_ptr() } + unsafe { TCString::return_val(s.into()) } } /// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given /// string is not valid. #[no_mangle] -pub unsafe extern "C" fn tc_uuid_from_str(s: *mut TCString, uuid_out: *mut TCUuid) -> TCResult { +pub unsafe extern "C" fn tc_uuid_from_str(s: TCString, uuid_out: *mut TCUuid) -> TCResult { debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null()); // SAFETY: - // - s is not NULL (promised by caller) - // - s is return from a tc_string_.. so is valid + // - s is valid (promised by caller) // - caller will not use s after this call (convention) - let s = unsafe { TCString::take_from_ptr_arg(s) }; + let mut s = unsafe { TCString::val_from_arg(s) }; if let Ok(s) = s.as_str() { if let Ok(u) = Uuid::parse_str(s) { // SAFETY: @@ -147,7 +150,7 @@ mod test { #[test] fn empty_list_has_non_null_pointer() { - let tcuuids = TCUuidList::return_val(Vec::new()); + let tcuuids = unsafe { TCUuidList::return_val(Vec::new()) }; assert!(!tcuuids.items.is_null()); assert_eq!(tcuuids.len, 0); assert_eq!(tcuuids._capacity, 0); @@ -155,7 +158,7 @@ mod test { #[test] fn free_sets_null_pointer() { - let mut tcuuids = TCUuidList::return_val(Vec::new()); + let mut tcuuids = unsafe { TCUuidList::return_val(Vec::new()) }; // SAFETY: testing expected behavior unsafe { tc_uuid_list_free(&mut tcuuids) }; assert!(tcuuids.items.is_null()); diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 702e24446..f09bddfe1 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -151,40 +151,6 @@ typedef struct TCReplica TCReplica; */ typedef struct TCServer TCServer; -/** - * 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 - * - * 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 pointer is invalid when the function returns. Callers - * must not use or free TCStrings after passing them to such API functions. - * - * TCString is not threadsafe. - */ -typedef struct TCString TCString; - /** * A task, as publicly exposed by this library. * @@ -243,6 +209,52 @@ typedef struct TCTask TCTask; */ 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. + */ +typedef struct TCString { + void *ptr; + size_t _u1; + size_t _u2; + uint8_t _u3; +} TCString; + /** * TCAnnotation contains the details of an annotation. * @@ -268,7 +280,7 @@ typedef struct TCAnnotation { /** * Content of the annotation. Must not be NULL. */ - struct TCString *description; + struct TCString description; } TCAnnotation; /** @@ -299,8 +311,8 @@ typedef struct TCAnnotationList { * will be freed when it is freed with tc_kv_list_free. */ typedef struct TCKV { - struct TCString *key; - struct TCString *value; + struct TCString key; + struct TCString value; } TCKV; /** @@ -395,7 +407,7 @@ typedef struct TCStringList { * 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 *const *items; + const struct TCString *items; } TCStringList; /** @@ -403,17 +415,17 @@ typedef struct TCStringList { */ typedef struct TCUda { /** - * Namespace of the UDA. For legacy UDAs, this is NULL. + * Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. */ - struct TCString *ns; + struct TCString ns; /** * UDA key. Must not be NULL. */ - struct TCString *key; + struct TCString key; /** * Content of the UDA. Must not be NULL. */ - struct TCString *value; + struct TCString value; } TCUda; /** @@ -474,7 +486,7 @@ struct TCReplica *tc_replica_new_in_memory(void); * 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, struct TCString **error_out); +struct TCReplica *tc_replica_new_on_disk(struct TCString path, struct TCString *error_out); /** * Get a list of all tasks in the replica. @@ -487,6 +499,8 @@ 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); @@ -513,7 +527,7 @@ struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid); */ struct TCTask *tc_replica_new_task(struct TCReplica *rep, enum TCStatus status, - struct TCString *description); + struct TCString description); /** * Create a new task. The task must not already exist. @@ -553,11 +567,11 @@ TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); TCResult tc_replica_rebuild_working_set(struct TCReplica *rep, bool renumber); /** - * Get the latest error for a replica, or NULL if the last operation succeeded. Subsequent calls - * to this function will return NULL. The rep pointer must not be NULL. The caller must free the - * returned string. + * 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); +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 @@ -574,7 +588,7 @@ void tc_replica_free(struct TCReplica *rep); * * 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); +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 @@ -585,10 +599,10 @@ struct TCServer *tc_server_new_local(struct TCString *server_dir, struct TCStrin * * 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, +struct TCServer *tc_server_new_remote(struct TCString origin, struct TCUuid client_key, - struct TCString *encryption_secret, - struct TCString **error_out); + 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 @@ -612,34 +626,39 @@ void tc_server_free(struct TCServer *server); * free(url); // string is no longer referenced and can be freed * ``` */ -struct TCString *tc_string_borrow(const char *cstr); +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. */ -struct TCString *tc_string_clone(const char *cstr); +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. */ -struct TCString *tc_string_clone_with_len(const char *buf, size_t len); +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 not be NULL. The + * 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(struct TCString *tcstring); +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. @@ -647,9 +666,12 @@ const char *tc_string_content(struct TCString *tcstring); * 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(struct TCString *tcstring, size_t *len_out); +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 @@ -707,7 +729,7 @@ enum TCStatus tc_task_get_status(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. + * modified. It is the caller's responsibility to free the TCKVList. */ struct TCKVList tc_task_get_taskmap(struct TCTask *task); @@ -715,7 +737,7 @@ struct TCKVList tc_task_get_taskmap(struct TCTask *task); * Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it * contains embedded NUL characters). */ -struct TCString *tc_task_get_description(struct TCTask *task); +struct TCString tc_task_get_description(struct TCTask *task); /** * Get the entry timestamp for a task (when it was created), or 0 if not set. @@ -746,7 +768,7 @@ bool tc_task_is_active(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); +bool tc_task_has_tag(struct TCTask *task, struct TCString tag); /** * Get the tags for the task. @@ -767,16 +789,16 @@ struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); /** * Get the named UDA from the task. * - * Returns NULL if the UDA does not exist. + * 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); +struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key); /** * 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); +struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); /** * Get all UDAs for this task. @@ -789,7 +811,7 @@ struct TCUdaList tc_task_get_udas(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. + * included in the key field. The caller must free the returned list. */ struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); @@ -801,7 +823,7 @@ TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); /** * Set a mutable task's description. */ -TCResult tc_task_set_description(struct TCTask *task, struct TCString *description); +TCResult tc_task_set_description(struct TCTask *task, struct TCString description); /** * Set a mutable task's entry (creation time). Pass entry=0 to unset @@ -842,12 +864,12 @@ TCResult tc_task_delete(struct TCTask *task); /** * Add a tag to a mutable task. */ -TCResult tc_task_add_tag(struct TCTask *task, struct TCString *tag); +TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag); /** * Remove a tag from a mutable task. */ -TCResult tc_task_remove_tag(struct TCTask *task, struct TCString *tag); +TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag); /** * Add an annotation to a mutable task. This call takes ownership of the @@ -864,31 +886,31 @@ TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); * Set a UDA on a mutable task. */ TCResult tc_task_set_uda(struct TCTask *task, - struct TCString *ns, - struct TCString *key, - struct TCString *value); + 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); +TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key); /** * Set a legacy UDA on a mutable task. */ -TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString *key, struct TCString *value); +TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value); /** * Remove a UDA fraom a mutable task. */ -TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString *key); +TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); /** - * Get the latest error for a task, or NULL 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. + * 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); +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 @@ -940,13 +962,13 @@ 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. */ -struct TCString *tc_uuid_to_str(struct TCUuid tcuuid); +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); +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 From f0178d4fabdc6c4bfd5dd4ec809a41e9edf8daf2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 24 Feb 2022 03:33:08 +0000 Subject: [PATCH 515/548] don't use unsafe_op_in_unsafe_fn, as it's not in MSRV --- lib/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 946342fb3..014a70c5a 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,4 +1,7 @@ -#![warn(unsafe_op_in_unsafe_fn)] +// Not compatible with the MSRV +// #![warn(unsafe_op_in_unsafe_fn)] +#![allow(unused_unsafe)] + // Not working yet in stable - https://github.com/rust-lang/rust-clippy/issues/8020 // #![warn(clippy::undocumented_unsafe_blocks)] From 17ccaea0966363270f5e145cec61ffa25fc5b42c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 26 Feb 2022 22:45:41 +0000 Subject: [PATCH 516/548] try the latest 'cc' crate in hopes it fixes things --- Cargo.lock | 4 ++-- integration-tests/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fed2a869a..0f5d620c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -612,9 +612,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.68" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 2b4a3bd8a..1d57b1a7d 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -21,5 +21,5 @@ env_logger = "^0.8.3" lazy_static = "1" [build-dependencies] -cc = "1.0" +cc = "1.0.73" taskchampion-lib = { path = "../lib" } From 2c9d74515ebf19ef833a2f08d775be8e0e896376 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 27 Feb 2022 17:03:04 +0000 Subject: [PATCH 517/548] link to libtaskchampion separately from the unity tests --- integration-tests/build.rs | 43 ++++++++++++++++++++++---------------- lib/Cargo.toml | 2 +- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/integration-tests/build.rs b/integration-tests/build.rs index b5217f5a0..0537507db 100644 --- a/integration-tests/build.rs +++ b/integration-tests/build.rs @@ -2,25 +2,29 @@ use std::env; use std::fs; use std::path::Path; -fn build_libtaskchampion(suites: &[&'static str]) { - // This crate has taskchampion-lib in its build-dependencies, so - // libtaskchampion.so should be built already. Hopefully it's in target/$PROFILE, and hopefully - // it's named libtaskchampion.so and not something else - let mut libtaskchampion = env::current_dir().unwrap(); - libtaskchampion.pop(); - libtaskchampion.push("target"); - libtaskchampion.push(env::var("PROFILE").unwrap()); - libtaskchampion.push("deps"); - libtaskchampion.push(if cfg!(target_os = "macos") { - "libtaskchampion.dylib" - } else { - "libtaskchampion.so" - }); +/// Link to the libtaskchampion library produced by the `taskchampion-lib` crate. This is done as +/// a build dependency, rather than a cargo dependency, for unclear reasons. TODO +fn link_libtaskchampion() { + // This crate has taskchampion-lib in its build-dependencies, so libtaskchampion.so should be + // built already. + // + // Shared libraries (crate-type=cdylib) appear to be placed in target/$PROFILE/deps. + let mut libtc_dir = env::current_dir().unwrap(); + libtc_dir.pop(); + libtc_dir.push("target"); + libtc_dir.push(env::var("PROFILE").unwrap()); + libtc_dir.push("deps"); + let libtc_dir = libtc_dir.to_str().expect("path is valid utf-8"); + println!("cargo:rustc-link-search={}", libtc_dir); + println!("cargo:rustc-link-lib=dylib=taskchampion"); +} + +/// Build the Unity-based C test suite in `src/bindings_tests`, linking the result with this +/// package's library crate. +fn build_bindings_tests(suites: &[&'static str]) { let mut build = cc::Build::new(); - build.shared_flag(true); - build.object(libtaskchampion); - build.include("../lib"); + build.include("../lib"); // include path for taskchampion.h build.include("src/bindings_tests/unity"); build.define("UNITY_OUTPUT_CHAR", "test_output"); build.define( @@ -41,6 +45,8 @@ fn build_libtaskchampion(suites: &[&'static str]) { build.compile("bindings-tests"); } +/// Make `bindings_test_suites.rs` listing all of the test suites, for use in building the +/// bindings-test binary. fn make_suite_file(suites: &[&'static str]) { let out_dir = env::var_os("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("bindings_test_suites.rs"); @@ -55,6 +61,7 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); let suites = &["uuid", "string", "task", "replica"]; - build_libtaskchampion(suites); + link_libtaskchampion(); + build_bindings_tests(suites); make_suite_file(suites); } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 564f28590..6f85b62b7 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" [lib] name = "taskchampion" -crate-type = ["cdylib"] +crate-type = ["staticlib", "cdylib"] [dependencies] libc = "0.2.113" From 8a96ca72736de8ea87b354565638355c6c623e67 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 27 Feb 2022 17:07:09 +0000 Subject: [PATCH 518/548] fix formatting --- integration-tests/build.rs | 3 ++- lib/src/lib.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/build.rs b/integration-tests/build.rs index 0537507db..8369b57a7 100644 --- a/integration-tests/build.rs +++ b/integration-tests/build.rs @@ -3,7 +3,8 @@ use std::fs; use std::path::Path; /// Link to the libtaskchampion library produced by the `taskchampion-lib` crate. This is done as -/// a build dependency, rather than a cargo dependency, for unclear reasons. TODO +/// a build dependency, rather than a cargo dependency, so that the symbols are available to +/// bindings-tests. fn link_libtaskchampion() { // This crate has taskchampion-lib in its build-dependencies, so libtaskchampion.so should be // built already. diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 014a70c5a..4f83aaf0d 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,7 +1,6 @@ // Not compatible with the MSRV // #![warn(unsafe_op_in_unsafe_fn)] #![allow(unused_unsafe)] - // Not working yet in stable - https://github.com/rust-lang/rust-clippy/issues/8020 // #![warn(clippy::undocumented_unsafe_blocks)] From 5072ed74582fa25c63c1cf4b1c77a939a77fe8da Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 27 Feb 2022 17:13:55 +0000 Subject: [PATCH 519/548] a bit of docs --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 6bf44be76..f1c66f275 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,18 @@ There are five crates here: The _taskchampion_lib_ crate uses a bit of code generation to create the `lib/taskchampion.h` header file. To regenerate this file, run `cargo xtask codegen`. +## C libraries + +The `taskchampion-lib` crate generates libraries suitable for use from C (or any C-compatible language). + +The necessary bits are: + +* a shared object in `target/$PROFILE/deps` (e.g., `target/debug/deps/libtaskchampion.so`) +* a static library in `target/$PROFILE` (e.g., `target/debug/libtaskchampion.a`) +* a header file, `lib/taskchampion.h`. + +Downstream consumers may use either the static or dynamic library, as they prefer. + ## Documentation Generation The `mdbook` configuration contains a "preprocessor" implemented in the `taskchampion-cli` crate in order to reflect CLI usage information into the generated book. From aadd4a762febb69625eb504dc1c8499dd1339f1e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 27 Feb 2022 17:28:36 +0000 Subject: [PATCH 520/548] support generation of paths from TCString on Windows --- lib/src/replica.rs | 76 +++++++++++++++++++++++++++------------------- lib/src/server.rs | 4 +-- lib/src/string.rs | 22 ++++++++++---- 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/lib/src/replica.rs b/lib/src/replica.rs index a1d488ede..11fd26186 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -94,6 +94,34 @@ where } } +/// Utility function to allow using `?` notation to return an error value in the constructor. +fn wrap_constructor(f: F, error_out: *mut TCString, err_value: T) -> T +where + F: FnOnce() -> anyhow::Result, +{ + if !error_out.is_null() { + // SAFETY: + // - error_out is not NULL (just checked) + // - properly aligned and valid (promised by caller) + unsafe { *error_out = TCString::default() }; + } + + match f() { + Ok(v) => v, + Err(e) => { + if !error_out.is_null() { + // SAFETY: + // - error_out is not NULL (just checked) + // - properly aligned and valid (promised by caller) + unsafe { + TCString::val_to_arg_out(err_to_ruststring(e), error_out); + } + } + err_value + } + } +} + /// 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. #[no_mangle] @@ -114,40 +142,24 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( path: TCString, error_out: *mut TCString, ) -> *mut TCReplica { - if !error_out.is_null() { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - unsafe { *error_out = TCString::default() }; - } - - // SAFETY: - // - path is valid (promised by caller) - // - caller will not use path after this call (convention) - let path = unsafe { TCString::val_from_arg(path) }; - let storage_res = StorageConfig::OnDisk { - taskdb_dir: path.to_path_buf(), - } - .into_storage(); - - let storage = match storage_res { - Ok(storage) => storage, - Err(e) => { - if !error_out.is_null() { - unsafe { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - TCString::val_to_arg_out(err_to_ruststring(e), error_out); - } + wrap_constructor( + || { + // SAFETY: + // - path is valid (promised by caller) + // - caller will not use path after this call (convention) + let mut path = unsafe { TCString::val_from_arg(path) }; + let storage = StorageConfig::OnDisk { + taskdb_dir: path.to_path_buf()?, } - return std::ptr::null_mut(); - } - }; + .into_storage()?; - // SAFETY: - // - caller promises to free this value - unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } + // SAFETY: + // - caller promises to free this value + Ok(unsafe { TCReplica::from(Replica::new(storage)).return_ptr() }) + }, + error_out, + std::ptr::null_mut(), + ) } /// Get a list of all tasks in the replica. diff --git a/lib/src/server.rs b/lib/src/server.rs index 711fcacf5..b7247bdb6 100644 --- a/lib/src/server.rs +++ b/lib/src/server.rs @@ -70,9 +70,9 @@ pub unsafe extern "C" fn tc_server_new_local( // SAFETY: // - server_dir is valid (promised by caller) // - caller will not use server_dir after this call (convention) - let server_dir = unsafe { TCString::val_from_arg(server_dir) }; + let mut server_dir = unsafe { TCString::val_from_arg(server_dir) }; let server_config = ServerConfig::Local { - server_dir: server_dir.to_path_buf(), + server_dir: server_dir.to_path_buf()?, }; let server = server_config.into_server()?; // SAFETY: caller promises to free this server. diff --git a/lib/src/string.rs b/lib/src/string.rs index 3767f7bb2..2791aadcc 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -1,8 +1,7 @@ use crate::traits::*; use crate::util::{string_into_raw_parts, vec_into_raw_parts}; -use std::ffi::{CStr, CString, OsStr}; +use std::ffi::{CStr, CString, OsStr, OsString}; use std::os::raw::c_char; -use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; /// TCString supports passing strings into and out of the TaskChampion API. @@ -293,10 +292,21 @@ impl<'a> RustString<'a> { } } - pub(crate) fn to_path_buf(&self) -> PathBuf { - // TODO: this is UNIX-specific. - let path: &OsStr = OsStr::from_bytes(self.as_bytes()); - path.to_os_string().into() + pub(crate) fn to_path_buf(&mut self) -> Result { + #[cfg(unix)] + let path: OsString = { + // on UNIX, we can use the bytes directly, without requiring that they + // be valid UTF-8. + use std::os::unix::ffi::OsStrExt; + OsStr::from_bytes(self.as_bytes()).to_os_string() + }; + #[cfg(windows)] + let path: OsString = { + // on Windows, we assume the filename is valid Unicode, so it can be + // represented as UTF-8. + OsString::from(self.as_str()?.to_string()) + }; + Ok(path.into()) } } From 1c5b01975c55da2df58136bee2c3abfde0085127 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 27 Feb 2022 17:57:34 +0000 Subject: [PATCH 521/548] fix unused symbol on windows --- lib/src/string.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/string.rs b/lib/src/string.rs index 2791aadcc..c6f1c3ce4 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -1,6 +1,6 @@ use crate::traits::*; use crate::util::{string_into_raw_parts, vec_into_raw_parts}; -use std::ffi::{CStr, CString, OsStr, OsString}; +use std::ffi::{CStr, CString, OsString}; use std::os::raw::c_char; use std::path::PathBuf; @@ -298,6 +298,7 @@ impl<'a> RustString<'a> { // on UNIX, we can use the bytes directly, without requiring that they // be valid UTF-8. use std::os::unix::ffi::OsStrExt; + use std::ffi::OsStr; OsStr::from_bytes(self.as_bytes()).to_os_string() }; #[cfg(windows)] From 85153423beb1523717751c9301ee88e7a40b1ff7 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 27 Feb 2022 17:59:58 +0000 Subject: [PATCH 522/548] include BCrypt on Windows --- README.md | 2 ++ integration-tests/build.rs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index f1c66f275..3622cc50e 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ The necessary bits are: Downstream consumers may use either the static or dynamic library, as they prefer. +NOTE: on Windows, the "BCrypt" library must be included when linking to taskchampion. + ## Documentation Generation The `mdbook` configuration contains a "preprocessor" implemented in the `taskchampion-cli` crate in order to reflect CLI usage information into the generated book. diff --git a/integration-tests/build.rs b/integration-tests/build.rs index 8369b57a7..90165f6d0 100644 --- a/integration-tests/build.rs +++ b/integration-tests/build.rs @@ -19,6 +19,11 @@ fn link_libtaskchampion() { let libtc_dir = libtc_dir.to_str().expect("path is valid utf-8"); println!("cargo:rustc-link-search={}", libtc_dir); println!("cargo:rustc-link-lib=dylib=taskchampion"); + + // on windows, it appears that rust std requires BCrypt + if cfg!(target_os = "windows") { + println!("cargo:rustc-link-lib=dylib=bcrypt"); + } } /// Build the Unity-based C test suite in `src/bindings_tests`, linking the result with this From a5259350087e0997894451f868ad3fe29f44e21d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 27 Feb 2022 18:14:21 +0000 Subject: [PATCH 523/548] import order fix --- lib/src/string.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/string.rs b/lib/src/string.rs index c6f1c3ce4..86b1ac3bd 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -297,8 +297,8 @@ impl<'a> RustString<'a> { let path: OsString = { // on UNIX, we can use the bytes directly, without requiring that they // be valid UTF-8. - use std::os::unix::ffi::OsStrExt; use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; OsStr::from_bytes(self.as_bytes()).to_os_string() }; #[cfg(windows)] From 3a4c417ceea83d3026967b77dab042694cefc05e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 2 Mar 2022 20:58:36 -0500 Subject: [PATCH 524/548] free replica in test --- integration-tests/src/bindings_tests/replica.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index cd02d3a30..88651fc45 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -305,6 +305,8 @@ static void test_replica_get_task_not_found(void) { TCTask *task = tc_replica_get_task(rep, uuid); TEST_ASSERT_NULL(task); TEST_ASSERT_NULL(tc_replica_error(rep).ptr); + + tc_replica_free(rep); } int replica_tests(void) { From 43ab50db424274ee6aa99a2a45dc2956a2f2e434 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Mar 2022 01:22:59 +0000 Subject: [PATCH 525/548] use TC_UUID_STRING_BYTES constant directly --- Cargo.lock | 1 - lib/Cargo.toml | 1 - lib/src/uuid.rs | 5 ++--- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f5d620c1..b4ff54c2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3039,7 +3039,6 @@ dependencies = [ "libc", "pretty_assertions", "taskchampion", - "uuid", ] [[package]] diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 6f85b62b7..6d6158026 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,7 +11,6 @@ crate-type = ["staticlib", "cdylib"] libc = "0.2.113" chrono = "^0.4.10" taskchampion = { path = "../taskchampion" } -uuid = { version = "^0.8.2", features = ["v4"] } anyhow = "1.0" [dev-dependencies] diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index c4ec8f474..28421a81c 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -87,9 +87,8 @@ pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) // - content of buf will not be mutated during the lifetime of this slice (lifetime // does not outlive this function call) // - the length of the buffer is less than isize::MAX (promised by caller) - let buf: &mut [u8] = unsafe { - std::slice::from_raw_parts_mut(buf as *mut u8, ::uuid::adapter::Hyphenated::LENGTH) - }; + let buf: &mut [u8] = + unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, TC_UUID_STRING_BYTES) }; // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; From 2a6c91b3f9439f80f258e6e40975f388b9ad7bdc Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Mar 2022 01:36:20 +0000 Subject: [PATCH 526/548] Support expiring old, deleted tasks --- taskchampion/src/replica.rs | 57 +++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 79938423f..686b34842 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -4,7 +4,7 @@ use crate::task::{Status, Task}; use crate::taskdb::TaskDb; use crate::workingset::WorkingSet; use anyhow::Context; -use chrono::Utc; +use chrono::{Duration, Utc}; use log::trace; use std::collections::HashMap; use uuid::Uuid; @@ -127,7 +127,6 @@ impl Replica { /// Delete a task. The task must exist. Note that this is different from setting status to /// Deleted; this is the final purge of the task. This is not a public method as deletion /// should only occur through expiration. - #[allow(dead_code)] fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result<()> { self.add_undo_point(false)?; self.taskdb.apply(SyncOp::Delete { uuid })?; @@ -175,6 +174,29 @@ impl Replica { Ok(()) } + /// Expire old, deleted tasks. + /// + /// Expiration entails removal of tasks from the replica. Any modifications that occur after + /// the deletion (such as operations synchronized from other replicas) will do nothing. + /// + /// Tasks are eligible for expiration when they have status Deleted and have not been modified + /// for 180 days (about six months). Note that completed tasks are not eligible. + pub fn expire_tasks(&mut self) -> anyhow::Result<()> { + let six_mos_ago = Utc::now() - Duration::days(180); + self.all_tasks()? + .iter() + .filter(|(_, t)| t.get_status() == Status::Deleted) + .filter(|(_, t)| { + if let Some(m) = t.get_modified() { + m < six_mos_ago + } else { + false + } + }) + .try_for_each(|(u, _)| self.delete_task(*u))?; + Ok(()) + } + /// 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 @@ -193,6 +215,7 @@ mod tests { use super::*; use crate::storage::ReplicaOp; use crate::task::Status; + use chrono::TimeZone; use pretty_assertions::assert_eq; use uuid::Uuid; @@ -392,4 +415,34 @@ mod tests { let uuid = Uuid::new_v4(); assert_eq!(rep.get_task(uuid).unwrap(), None); } + + #[test] + fn expire() { + let mut rep = Replica::new_inmemory(); + let mut t; + + rep.new_task(Status::Pending, "keeper 1".into()).unwrap(); + rep.new_task(Status::Completed, "keeper 2".into()).unwrap(); + + t = rep.new_task(Status::Deleted, "keeper 3".into()).unwrap(); + { + let mut t = t.into_mut(&mut rep); + // set entry, with modification set as a side-effect + t.set_entry(Some(Utc::now())).unwrap(); + } + + t = rep.new_task(Status::Deleted, "goner".into()).unwrap(); + { + let mut t = t.into_mut(&mut rep); + t.set_modified(Utc.ymd(1980, 1, 1).and_hms(0, 0, 0)) + .unwrap(); + } + + rep.expire_tasks().unwrap(); + + for (_, t) in rep.all_tasks().unwrap() { + println!("got task {}", t.get_description()); + assert!(t.get_description().starts_with("keeper")); + } + } } From 919e91fd13333f284c798b909c68ef0dd6891064 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Mar 2022 22:17:59 +0000 Subject: [PATCH 527/548] don't automatically update modified when updating it explicitly --- taskchampion/src/task/task.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index d5517f5b4..cd8a12f85 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -425,7 +425,7 @@ impl<'r> TaskMut<'r> { // -- utility functions - fn lastmod(&mut self) -> anyhow::Result<()> { + fn update_modified(&mut self) -> anyhow::Result<()> { if !self.updated_modified { let now = format!("{}", Utc::now().timestamp()); trace!("task {}: set property modified={:?}", self.task.uuid, now); @@ -443,7 +443,10 @@ impl<'r> TaskMut<'r> { value: Option, ) -> anyhow::Result<()> { let property = property.into(); - self.lastmod()?; + // updated the modified timestamp unless we are setting it explicitly + if &property != "modified" { + self.update_modified()?; + } if let Some(ref v) = value { trace!("task {}: set property {}={:?}", self.task.uuid, property, v); From 3cdc13aa37bdb28ea1eeddd5928c970c587346cd Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Mar 2022 22:23:27 +0000 Subject: [PATCH 528/548] expire tasks in 'ta gc' --- cli/src/invocation/cmd/gc.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/src/invocation/cmd/gc.rs b/cli/src/invocation/cmd/gc.rs index f35eaed76..e9b8a2828 100644 --- a/cli/src/invocation/cmd/gc.rs +++ b/cli/src/invocation/cmd/gc.rs @@ -4,6 +4,8 @@ use termcolor::WriteColor; pub(crate) fn execute(w: &mut W, replica: &mut Replica) -> Result<(), crate::Error> { log::debug!("rebuilding working set"); replica.rebuild_working_set(true)?; + log::debug!("expiring old tasks"); + replica.expire_tasks()?; writeln!(w, "garbage collected.")?; Ok(()) } From 97bd2addc9ed2aa945da0484f6f316b75ea91e21 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Mar 2022 22:28:06 +0000 Subject: [PATCH 529/548] add a 'ta delete' subcommand --- cli/src/argparse/subcommand.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 22c270ba3..553350740 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -218,6 +218,7 @@ impl Modify { "start" => modification.active = Some(true), "stop" => modification.active = Some(false), "done" => modification.status = Some(Status::Completed), + "delete" => modification.status = Some(Status::Deleted), "annotate" => { // what would be parsed as a description is, here, used as the annotation if let DescriptionMod::Set(s) = modification.description { @@ -243,6 +244,7 @@ impl Modify { arg_matching(literal("start")), arg_matching(literal("stop")), arg_matching(literal("done")), + arg_matching(literal("delete")), arg_matching(literal("annotate")), )), Modification::parse, @@ -297,10 +299,19 @@ impl Modify { Mark all tasks matching the required filter as completed, additionally applying any given modifications.", }); + u.subcommands.push(usage::Subcommand { + name: "delete", + syntax: " delete [modification]", + summary: "Mark tasks as deleted", + description: " + Mark all tasks matching the required filter as deleted, additionally applying any given + modifications. Deleted tasks remain until they are expired in a 'ta gc' operation at + least six months after their last modification.", + }); u.subcommands.push(usage::Subcommand { name: "annotate", syntax: " annotate [modification]", - summary: "Mark tasks as completed", + summary: "Annotate a task", description: " Add an annotation to all tasks matching the required filter.", }); @@ -761,6 +772,23 @@ mod test { ); } + #[test] + fn test_delete() { + let subcommand = Subcommand::Modify { + filter: Filter { + conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])], + }, + modification: Modification { + status: Some(Status::Deleted), + ..Default::default() + }, + }; + assert_eq!( + Subcommand::parse(argv!["123", "delete"]).unwrap(), + (&EMPTY[..], subcommand) + ); + } + #[test] fn test_annotate() { let subcommand = Subcommand::Modify { From 411bc197629b14c4e694fe4f0508771a3be8e935 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 6 Mar 2022 22:32:39 +0000 Subject: [PATCH 530/548] doc expiration --- docs/src/taskdb.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/taskdb.md b/docs/src/taskdb.md index 40ee74b9c..b70e9bf3c 100644 --- a/docs/src/taskdb.md +++ b/docs/src/taskdb.md @@ -25,4 +25,8 @@ Operations are described in [Replica Storage](./storage.md). Each operation is added to the list of operations in the storage, and simultaneously applied to the tasks in that storage. Operations are checked for validity as they are applied. +## Deletion and Expiration +Deletion of a task merely changes the task's status to "deleted", leaving it in the Task database. +Actual removal of tasks from the task database takes place as part of _expiration_, triggered by the user as part of a garbage-collection process. +Expiration removes tasks with a `modified` property more than 180 days in the past, by creating a `Delete(uuid)` operation. From 42238b5306865ab09806ccecb3a4a6cd85195402 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 7 Mar 2022 23:22:57 +0000 Subject: [PATCH 531/548] add an integration test for syncing task expirations --- Cargo.lock | 1 + integration-tests/Cargo.toml | 1 + .../tests/update-and-delete-sync.rs | 72 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 integration-tests/tests/update-and-delete-sync.rs diff --git a/Cargo.lock b/Cargo.lock index b4ff54c2a..1d3dfbfc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1528,6 +1528,7 @@ dependencies = [ "actix-web", "anyhow", "cc", + "chrono", "env_logger 0.8.4", "lazy_static", "log", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 1d57b1a7d..89e3f0fbc 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -7,6 +7,7 @@ publish = false build = "build.rs" [dependencies] +chrono = { version = "^0.4.10", features = ["serde"] } taskchampion = { path = "../taskchampion" } taskchampion-sync-server = { path = "../sync-server" } diff --git a/integration-tests/tests/update-and-delete-sync.rs b/integration-tests/tests/update-and-delete-sync.rs new file mode 100644 index 000000000..e75dceae0 --- /dev/null +++ b/integration-tests/tests/update-and-delete-sync.rs @@ -0,0 +1,72 @@ +use chrono::{TimeZone, Utc}; +use taskchampion::{Replica, ServerConfig, Status, StorageConfig}; +use tempfile::TempDir; + +#[test] +fn update_and_delete_sync_delete_first() -> anyhow::Result<()> { + update_and_delete_sync(true) +} + +#[test] +fn update_and_delete_sync_update_first() -> anyhow::Result<()> { + update_and_delete_sync(false) +} + +/// Test what happens when an update is sync'd into a repo after a task is deleted. +/// If delete_first, then the deletion is sync'd to the server first; otherwise +/// the update is sync'd first. Either way, the task is gone. +fn update_and_delete_sync(delete_first: bool) -> anyhow::Result<()> { + // set up two replicas, and demonstrate replication between them + let mut rep1 = Replica::new(StorageConfig::InMemory.into_storage()?); + let mut rep2 = Replica::new(StorageConfig::InMemory.into_storage()?); + + let tmp_dir = TempDir::new().expect("TempDir failed"); + let mut server = ServerConfig::Local { + server_dir: tmp_dir.path().to_path_buf(), + } + .into_server()?; + + // add a task on rep1, and sync it to rep2 + let t = rep1.new_task(Status::Pending, "test task".into())?; + let u = t.get_uuid(); + + rep1.sync(&mut server, false)?; + rep2.sync(&mut server, false)?; + + // mark the task as deleted, long in the past, on rep2 + { + let mut t = rep2.get_task(u)?.unwrap().into_mut(&mut rep2); + t.delete()?; + t.set_modified(Utc.ymd(1980, 1, 1).and_hms(0, 0, 0))?; + } + + // sync it back to rep1 + rep2.sync(&mut server, false)?; + rep1.sync(&mut server, false)?; + + // expire the task on rep1 and check that it is gone locally + rep1.expire_tasks()?; + assert!(rep1.get_task(u)?.is_none()); + + // modify the task on rep2 + { + let mut t = rep2.get_task(u)?.unwrap().into_mut(&mut rep2); + t.set_description("modified".to_string())?; + } + + // sync back and forth + if delete_first { + rep1.sync(&mut server, false)?; + } + rep2.sync(&mut server, false)?; + rep1.sync(&mut server, false)?; + if !delete_first { + rep2.sync(&mut server, false)?; + } + + // check that the task is gone on both replicas + assert!(rep1.get_task(u)?.is_none()); + assert!(rep2.get_task(u)?.is_none()); + + Ok(()) +} From a5a8e3b7c887fa90bee8983ff12dcf7c909abcb2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 7 Mar 2022 23:49:37 +0000 Subject: [PATCH 532/548] fix some clippy warnings, and make them errors for taskchampion-lib --- lib/src/lib.rs | 5 +++++ lib/src/replica.rs | 2 +- lib/src/server.rs | 2 +- lib/src/string.rs | 2 +- lib/src/task.rs | 12 ++++-------- lib/src/uda.rs | 11 +---------- xtask/src/main.rs | 2 +- 7 files changed, 14 insertions(+), 22 deletions(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 4f83aaf0d..58ab5e6b1 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -7,6 +7,11 @@ // docstrings for extern "C" functions are reflected into C, and do not benefit // from safety docs. #![allow(clippy::missing_safety_doc)] +// deny some things that are typically warnings +#![deny(clippy::derivable_impls)] +#![deny(clippy::wrong_self_convention)] +#![deny(clippy::extra_unused_lifetimes)] +#![deny(clippy::unnecessary_to_owned)] mod traits; mod util; diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 11fd26186..cec26ad42 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -149,7 +149,7 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( // - caller will not use path after this call (convention) let mut path = unsafe { TCString::val_from_arg(path) }; let storage = StorageConfig::OnDisk { - taskdb_dir: path.to_path_buf()?, + taskdb_dir: path.to_path_buf_mut()?, } .into_storage()?; diff --git a/lib/src/server.rs b/lib/src/server.rs index b7247bdb6..dc1160d0c 100644 --- a/lib/src/server.rs +++ b/lib/src/server.rs @@ -72,7 +72,7 @@ pub unsafe extern "C" fn tc_server_new_local( // - caller will not use server_dir after this call (convention) let mut server_dir = unsafe { TCString::val_from_arg(server_dir) }; let server_config = ServerConfig::Local { - server_dir: server_dir.to_path_buf()?, + server_dir: server_dir.to_path_buf_mut()?, }; let server = server_config.into_server()?; // SAFETY: caller promises to free this server. diff --git a/lib/src/string.rs b/lib/src/string.rs index 86b1ac3bd..719a30563 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -292,7 +292,7 @@ impl<'a> RustString<'a> { } } - pub(crate) fn to_path_buf(&mut self) -> Result { + pub(crate) fn to_path_buf_mut(&mut self) -> Result { #[cfg(unix)] let path: OsString = { // on UNIX, we can use the bytes directly, without requiring that they diff --git a/lib/src/task.rs b/lib/src/task.rs index c3c0b896b..159553760 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -386,7 +386,7 @@ pub unsafe extern "C" fn tc_task_get_annotations(task: *mut TCTask) -> TCAnnotat /// /// Returns a TCString with NULL ptr field if the UDA does not exist. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_uda<'a>( +pub unsafe extern "C" fn tc_task_get_uda( task: *mut TCTask, ns: TCString, key: TCString, @@ -413,7 +413,7 @@ pub unsafe extern "C" fn tc_task_get_uda<'a>( /// /// Returns NULL if the UDA does not exist. #[no_mangle] -pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>(task: *mut TCTask, key: TCString) -> TCString { +pub unsafe extern "C" fn tc_task_get_legacy_uda(task: *mut TCTask, key: TCString) -> TCString { wrap(task, |task| { // SAFETY: // - key is valid (promised by caller) @@ -708,11 +708,7 @@ pub unsafe extern "C" fn tc_task_set_uda( wrap_mut( task, |task| { - task.set_uda( - ns.as_str()?.to_string(), - key.as_str()?.to_string(), - value.as_str()?.to_string(), - )?; + task.set_uda(ns.as_str()?, key.as_str()?, value.as_str()?.to_string())?; Ok(TCResult::Ok) }, TCResult::Error, @@ -735,7 +731,7 @@ pub unsafe extern "C" fn tc_task_remove_uda( wrap_mut( task, |task| { - task.remove_uda(ns.as_str()?.to_string(), key.as_str()?.to_string())?; + task.remove_uda(ns.as_str()?, key.as_str()?)?; Ok(TCResult::Ok) }, TCResult::Error, diff --git a/lib/src/uda.rs b/lib/src/uda.rs index 607d77e27..30607090c 100644 --- a/lib/src/uda.rs +++ b/lib/src/uda.rs @@ -3,6 +3,7 @@ use crate::types::*; /// TCUda contains the details of a UDA. #[repr(C)] +#[derive(Default)] pub struct TCUda { /// Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. pub ns: TCString, @@ -58,16 +59,6 @@ impl PassByValue for TCUda { } } -impl Default for TCUda { - fn default() -> Self { - TCUda { - ns: TCString::default(), - key: TCString::default(), - value: TCString::default(), - } - } -} - /// TCUdaList represents a list of UDAs. /// /// The content of this struct must be treated as read-only. diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 3990a4f39..6fd41b2d7 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; pub fn main() -> anyhow::Result<()> { let arg = env::args().nth(1); - match arg.as_ref().map(|arg| arg.as_str()) { + match arg.as_deref() { Some("codegen") => codegen(), Some(arg) => anyhow::bail!("unknown xtask {}", arg), _ => anyhow::bail!("unknown xtask"), From bd9a5ad5ff3ac9ca1bf6ee37fd8ec1fdd4488a87 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 9 Mar 2022 17:52:57 -0500 Subject: [PATCH 533/548] Address RUSTSEC-2022-0013. Note that the risk from this vulnerability was minimal for this project. --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4ff54c2a..690adfc8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2467,9 +2467,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", From 3c6da4138f1c967b2f95a3ee2ab4b16dd3ddd4a9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 12 Mar 2022 21:38:59 +0000 Subject: [PATCH 534/548] add an include guard to taskchampion.h --- lib/taskchampion.h | 5 +++++ xtask/src/main.rs | 1 + 2 files changed, 6 insertions(+) diff --git a/lib/taskchampion.h b/lib/taskchampion.h index f09bddfe1..fbdb42ab3 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -76,6 +76,9 @@ */ +#ifndef TASKCHAMPION_H +#define TASKCHAMPION_H + #include #include #include @@ -1009,3 +1012,5 @@ void tc_working_set_free(struct TCWorkingSet *ws); #ifdef __cplusplus } // extern "C" #endif // __cplusplus + +#endif /* TASKCHAMPION_H */ diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 6fd41b2d7..73ea71119 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -29,6 +29,7 @@ fn codegen() -> anyhow::Result<()> { .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, From 33a3b980d0e8beab0804b0ec28b75898b2a04341 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 13 Mar 2022 00:08:55 +0000 Subject: [PATCH 535/548] Allow taking from pointer lists This introduces `tc_task_list_take`, supporting taking ownership of an item in a task list. TCTaskList is the only pointer list, but this is a generic and could be used for other types. --- integration-tests/src/bindings_tests/task.c | 48 +++++++++++ lib/src/annotation.rs | 15 +++- lib/src/kv.rs | 15 +++- lib/src/replica.rs | 12 +-- lib/src/string.rs | 15 +++- lib/src/task.rs | 70 ++++++++++++---- lib/src/traits.rs | 93 +++++++++++++++++++-- lib/src/uda.rs | 15 +++- lib/src/uuid.rs | 15 +++- lib/taskchampion.h | 32 +++++-- 10 files changed, 278 insertions(+), 52 deletions(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index f3ff29ae0..4e20e52b3 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -561,6 +561,53 @@ static void test_task_taskmap(void) { tc_replica_free(rep); } +// taking from a task list behaves correctly +static void test_task_list_take(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); + + TCTask *task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("t")); + TEST_ASSERT_NOT_NULL(task1); + + TCTask *task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("t")); + TEST_ASSERT_NOT_NULL(task2); + tc_task_free(task2); + + TCString desc; + TCTaskList tasks = tc_replica_all_tasks(rep); + TEST_ASSERT_NOT_NULL(tasks.items); + TEST_ASSERT_EQUAL(2, tasks.len); + + task1 = tc_task_list_take(&tasks, 5); // out of bounds + TEST_ASSERT_NULL(task1); + + task1 = tc_task_list_take(&tasks, 0); + TEST_ASSERT_NOT_NULL(task1); + desc = tc_task_get_description(task1); + TEST_ASSERT_EQUAL_STRING("t", tc_string_content(&desc)); + tc_string_free(&desc); + + task2 = tc_task_list_take(&tasks, 1); + TEST_ASSERT_NOT_NULL(task2); + desc = tc_task_get_description(task2); + TEST_ASSERT_EQUAL_STRING("t", tc_string_content(&desc)); + tc_string_free(&desc); + + tc_task_free(task1); + tc_task_free(task2); + + task1 = tc_task_list_take(&tasks, 0); // already taken + TEST_ASSERT_NULL(task1); + + task1 = tc_task_list_take(&tasks, 5); // out of bounds + TEST_ASSERT_NULL(task1); + + tc_task_list_free(&tasks); + TEST_ASSERT_NULL(tasks.items); + + tc_replica_free(rep); +} + int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. @@ -578,5 +625,6 @@ int task_tests(void) { RUN_TEST(test_task_annotations); RUN_TEST(test_task_udas); RUN_TEST(test_task_taskmap); + RUN_TEST(test_task_list_take); return UNITY_END(); } diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index 774f94478..06c3069b2 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -75,13 +75,13 @@ pub struct TCAnnotationList { /// 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: *const TCAnnotation, + items: *mut TCAnnotation, } impl CList for TCAnnotationList { type Element = TCAnnotation; - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCAnnotationList { len, _capacity: cap, @@ -89,7 +89,16 @@ impl CList for TCAnnotationList { } } - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + fn slice(&mut self) -> &mut [Self::Element] { + // SAFETY: + // - because we have &mut self, we have read/write access to items[0..len] + // - all items are properly initialized Element's + // - return value lifetime is equal to &mmut self's, so access is exclusive + // - items and len came from Vec, so total size is < isize::MAX + unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } + } + + fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { (self.items, self.len, self._capacity) } } diff --git a/lib/src/kv.rs b/lib/src/kv.rs index 716883c75..8fcbaabbe 100644 --- a/lib/src/kv.rs +++ b/lib/src/kv.rs @@ -50,13 +50,13 @@ pub struct TCKVList { /// 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: *const TCKV, + items: *mut TCKV, } impl CList for TCKVList { type Element = TCKV; - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCKVList { len, _capacity: cap, @@ -64,7 +64,16 @@ impl CList for TCKVList { } } - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + fn slice(&mut self) -> &mut [Self::Element] { + // SAFETY: + // - because we have &mut self, we have read/write access to items[0..len] + // - all items are properly initialized Element's + // - return value lifetime is equal to &mmut self's, so access is exclusive + // - items and len came from Vec, so total size is < isize::MAX + unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } + } + + fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { (self.items, self.len, self._capacity) } } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index cec26ad42..6d9683959 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -177,12 +177,14 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList .all_tasks()? .drain() .map(|(_uuid, t)| { - NonNull::new( - // SAFETY: - // - caller promises to free this value (via freeing the list) - unsafe { TCTask::from(t).return_ptr() }, + Some( + NonNull::new( + // SAFETY: + // - caller promises to free this value (via freeing the list) + unsafe { TCTask::from(t).return_ptr() }, + ) + .expect("TCTask::return_ptr returned NULL"), ) - .expect("TCTask::return_ptr returned NULL") }) .collect(); // SAFETY: diff --git a/lib/src/string.rs b/lib/src/string.rs index 719a30563..65c9dfd9e 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -374,13 +374,13 @@ pub struct TCStringList { /// TCStringList representing each string. these remain owned by the TCStringList instance and will /// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the /// *TCStringList at indexes 0..len-1 are not NULL. - items: *const TCString, + items: *mut TCString, } impl CList for TCStringList { type Element = TCString; - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCStringList { len, _capacity: cap, @@ -388,7 +388,16 @@ impl CList for TCStringList { } } - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + fn slice(&mut self) -> &mut [Self::Element] { + // SAFETY: + // - because we have &mut self, we have read/write access to items[0..len] + // - all items are properly initialized Element's + // - return value lifetime is equal to &mmut self's, so access is exclusive + // - items and len came from Vec, so total size is < isize::MAX + unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } + } + + fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { (self.items, self.len, self._capacity) } } diff --git a/lib/src/task.rs b/lib/src/task.rs index 159553760..8bcdde446 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -171,7 +171,10 @@ impl TryFrom> for Tag { /// TCTaskList represents a list of tasks. /// -/// The content of this struct must be treated as read-only. +/// 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. #[repr(C)] pub struct TCTaskList { /// number of tasks in items @@ -183,13 +186,13 @@ pub struct TCTaskList { /// array of pointers representing each task. these remain owned by the TCTaskList instance and /// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList, /// and the *TCTaskList at indexes 0..len-1 are not NULL. - items: *const NonNull, + items: *mut Option>, } impl CList for TCTaskList { - type Element = NonNull; + type Element = Option>; - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCTaskList { len, _capacity: cap, @@ -197,7 +200,16 @@ impl CList for TCTaskList { } } - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + fn slice(&mut self) -> &mut [Self::Element] { + // SAFETY: + // - because we have &mut self, we have read/write access to items[0..len] + // - all items are properly initialized Element's + // - return value lifetime is equal to &mmut self's, so access is exclusive + // - items and len came from Vec, so total size is < isize::MAX + unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } + } + + fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { (self.items, self.len, self._capacity) } } @@ -813,17 +825,39 @@ pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) { drop(tctask); } +/// 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. +#[no_mangle] +pub unsafe extern "C" fn tc_task_list_take(tasks: *mut TCTaskList, index: usize) -> *mut TCTask { + // SAFETY: + // - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to + // modify the list directly, and tc_task_list_take leaves the list valid) + let p = unsafe { take_optional_pointer_list_item(tasks, index) }; + if let Some(p) = p { + p.as_ptr() + } else { + std::ptr::null_mut() + } +} + /// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after /// this call. /// /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. #[no_mangle] -pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { +pub unsafe extern "C" fn tc_task_list_free(tasks: *mut TCTaskList) { // SAFETY: - // - tctasks is not NULL and points to a valid TCTaskList (caller is not allowed to - // modify the list) + // - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to + // modify the list directly, and tc_task_list_take leaves the list valid) // - caller promises not to use the value after return - unsafe { drop_pointer_list(tctasks) }; + unsafe { drop_optional_pointer_list(tasks) }; } #[cfg(test)] @@ -832,19 +866,19 @@ mod test { #[test] fn empty_list_has_non_null_pointer() { - let tctasks = unsafe { TCTaskList::return_val(Vec::new()) }; - assert!(!tctasks.items.is_null()); - assert_eq!(tctasks.len, 0); - assert_eq!(tctasks._capacity, 0); + let tasks = unsafe { TCTaskList::return_val(Vec::new()) }; + assert!(!tasks.items.is_null()); + assert_eq!(tasks.len, 0); + assert_eq!(tasks._capacity, 0); } #[test] fn free_sets_null_pointer() { - let mut tctasks = unsafe { TCTaskList::return_val(Vec::new()) }; + let mut tasks = unsafe { TCTaskList::return_val(Vec::new()) }; // SAFETY: testing expected behavior - unsafe { tc_task_list_free(&mut tctasks) }; - assert!(tctasks.items.is_null()); - assert_eq!(tctasks.len, 0); - assert_eq!(tctasks._capacity, 0); + unsafe { tc_task_list_free(&mut tasks) }; + assert!(tasks.items.is_null()); + assert_eq!(tasks.len, 0); + assert_eq!(tasks._capacity, 0); } } diff --git a/lib/src/traits.rs b/lib/src/traits.rs index ffbdf9a79..5322eccb0 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -150,13 +150,17 @@ pub(crate) trait PassByPointer: Sized { /// The PassByValue trait will be implemented automatically, converting between the C type and /// `Vec`. /// -/// For most cases, it is only necessary to implement `tc_.._free` that calls either -/// drop_value_list (if Element is PassByValue) or drop_pointer_list (if element is PassByPointer). +/// The element type can be PassByValue or PassByPointer. If the latter, it should use either +/// `NonNull` or `Option>` to represent the element. The latter is an "optional +/// pointer list", where elements can be omitted. +/// +/// For most cases, it is only necessary to implement `tc_.._free` that calls one of the +/// drop_..._list functions. /// /// # Safety /// /// The C type must be documented as read-only. None of the fields may be modified, nor anything -/// accessible via the `items` array. +/// accessible via the `items` array. The exception is modification via "taking" elements. /// /// This class guarantees that the items pointer is non-NULL for any valid list (even when len=0). pub(crate) trait CList: Sized { @@ -169,18 +173,21 @@ pub(crate) trait CList: Sized { /// The arguments must either: /// - be NULL, 0, and 0, respectively; or /// - be valid for Vec::from_raw_parts - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self; + unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self; + + /// Return a mutable slice representing the elements in this list. + fn slice(&mut self) -> &mut [Self::Element]; /// Get the items, len, and capacity (in that order) for this instance. These must be /// precisely the same values passed tearlier to `from_raw_parts`. - fn into_raw_parts(self) -> (*const Self::Element, usize, usize); + fn into_raw_parts(self) -> (*mut Self::Element, usize, usize); /// Generate a NULL value. By default this is a NULL items pointer with zero length and /// capacity. fn null_value() -> Self { // SAFETY: // - satisfies the first case in from_raw_parts' safety documentation - unsafe { Self::from_raw_parts(std::ptr::null(), 0, 0) } + unsafe { Self::from_raw_parts(std::ptr::null_mut(), 0, 0) } } } @@ -225,6 +232,7 @@ where /// - List must be non-NULL and point to a valid CL instance /// - The caller must not use the value array points to after this function, as /// it has been freed. It will be replaced with the null value. +#[allow(dead_code)] // this was useful once, and might be again? pub(crate) unsafe fn drop_pointer_list(list: *mut CL) where CL: CList>, @@ -246,6 +254,79 @@ where drop(vec); } +/// Given a CList containing optional pointers, drop all of the non-null pointed-to values and the +/// list. +/// +/// This is a convenience function for `tc_.._list_free` functions, for lists from which items +/// can be taken. +/// +/// # Safety +/// +/// - List must be non-NULL and point to a valid CL instance +/// - The caller must not use the value array points to after this function, as +/// it has been freed. It will be replaced with the null value. +pub(crate) unsafe fn drop_optional_pointer_list(list: *mut CL) +where + CL: CList>>, + T: PassByPointer, +{ + debug_assert!(!list.is_null()); + // SAFETY: + // - *list is a valid CL (promised by caller) + let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; + + // first, drop each of the elements in turn + for e in vec.drain(..) { + if let Some(e) = e { + // SAFETY: + // - e is a valid Element (promised by caller) + // - e is owned + drop(unsafe { PassByPointer::take_from_ptr_arg(e.as_ptr()) }); + } + } + // then drop the vector + drop(vec); +} + +/// Take a value from an optional pointer list, returning the value and replacing its array +/// element with NULL. +/// +/// This is a convenience function for `tc_.._list_take` functions, for lists from which items +/// can be taken. +/// +/// The returned value will be None if the element has already been taken, or if the index is +/// out of bounds. +/// +/// # Safety +/// +/// - List must be non-NULL and point to a valid CL instance +pub(crate) unsafe fn take_optional_pointer_list_item( + list: *mut CL, + index: usize, +) -> Option> +where + CL: CList>>, + T: PassByPointer, +{ + debug_assert!(!list.is_null()); + + // SAFETy: + // - list is properly aligned, dereferencable, and points to an initialized CL, since it is valid + // - the lifetime of the resulting reference is limited to this function, during which time + // nothing else refers to this memory. + let slice = list.as_mut().unwrap().slice(); + if let Some(elt_ref) = slice.get_mut(index) { + let mut rv = None; + if let Some(elt) = elt_ref.as_mut() { + rv = Some(*elt); + *elt_ref = None; // clear out the array element + } + rv + } else { + None // index out of bounds + } +} + impl PassByValue for A where A: CList, diff --git a/lib/src/uda.rs b/lib/src/uda.rs index 30607090c..679557504 100644 --- a/lib/src/uda.rs +++ b/lib/src/uda.rs @@ -72,13 +72,13 @@ pub struct TCUdaList { /// 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: *const TCUda, + items: *mut TCUda, } impl CList for TCUdaList { type Element = TCUda; - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCUdaList { len, _capacity: cap, @@ -86,7 +86,16 @@ impl CList for TCUdaList { } } - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + fn slice(&mut self) -> &mut [Self::Element] { + // SAFETY: + // - because we have &mut self, we have read/write access to items[0..len] + // - all items are properly initialized Element's + // - return value lifetime is equal to &mmut self's, so access is exclusive + // - items and len came from Vec, so total size is < isize::MAX + unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } + } + + fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { (self.items, self.len, self._capacity) } } diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 28421a81c..8284caac2 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -57,13 +57,13 @@ pub struct TCUuidList { /// array of uuids. these remain owned by the TCUuidList instance and will be freed by /// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList. - items: *const TCUuid, + items: *mut TCUuid, } impl CList for TCUuidList { type Element = TCUuid; - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCUuidList { len, _capacity: cap, @@ -71,7 +71,16 @@ impl CList for TCUuidList { } } - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + fn slice(&mut self) -> &mut [Self::Element] { + // SAFETY: + // - because we have &mut self, we have read/write access to items[0..len] + // - all items are properly initialized Element's + // - return value lifetime is equal to &mmut self's, so access is exclusive + // - items and len came from Vec, so total size is < isize::MAX + unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } + } + + fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { (self.items, self.len, self._capacity) } } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index f09bddfe1..5e9c728bb 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -301,7 +301,7 @@ typedef struct TCAnnotationList { * 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. */ - const struct TCAnnotation *items; + struct TCAnnotation *items; } TCAnnotationList; /** @@ -333,13 +333,16 @@ typedef struct TCKVList { * 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. */ - const struct TCKV *items; + struct TCKV *items; } TCKVList; /** * TCTaskList represents a list of tasks. * - * The content of this struct must be treated as read-only. + * 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 { /** @@ -355,7 +358,7 @@ typedef struct TCTaskList { * 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 *const *items; + struct TCTask **items; } TCTaskList; /** @@ -385,7 +388,7 @@ typedef struct TCUuidList { * 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. */ - const struct TCUuid *items; + struct TCUuid *items; } TCUuidList; /** @@ -407,7 +410,7 @@ typedef struct TCStringList { * 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. */ - const struct TCString *items; + struct TCString *items; } TCStringList; /** @@ -446,7 +449,7 @@ typedef struct TCUdaList { * 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. */ - const struct TCUda *items; + struct TCUda *items; } TCUdaList; #ifdef __cplusplus @@ -920,13 +923,26 @@ struct TCString tc_task_error(struct TCTask *task); */ void tc_task_free(struct TCTask *task); +/** + * 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. */ -void tc_task_list_free(struct TCTaskList *tctasks); +void tc_task_list_free(struct TCTaskList *tasks); /** * Free a TCUda instance. The instance, and the TCStrings it contains, must not be used From f8c4ece238ae79901e0f7c37b3b9322babde517c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 12 Mar 2022 20:12:39 +0000 Subject: [PATCH 536/548] Re-export the chrono crate from taskchampion. The chrono types are central to use of TC, so this will help consumers of the TC crate to avoid dependency conflicts. --- Cargo.lock | 3 --- cli/Cargo.toml | 1 - cli/src/argparse/args/colon.rs | 7 ++++--- cli/src/argparse/args/time.rs | 2 +- cli/src/argparse/mod.rs | 2 +- cli/src/argparse/modification.rs | 5 +++-- cli/src/invocation/cmd/import_tdb2.rs | 2 +- cli/src/invocation/cmd/import_tw.rs | 4 ++-- cli/src/invocation/modify.rs | 2 +- cli/src/invocation/report.rs | 6 +++--- integration-tests/Cargo.toml | 1 - integration-tests/tests/update-and-delete-sync.rs | 2 +- lib/Cargo.toml | 1 - lib/src/annotation.rs | 2 +- lib/src/atomic.rs | 2 +- lib/src/task.rs | 2 +- taskchampion/src/lib.rs | 3 +++ 17 files changed, 23 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86be4733f..1418d3bbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1528,7 +1528,6 @@ dependencies = [ "actix-web", "anyhow", "cc", - "chrono", "env_logger 0.8.4", "lazy_static", "log", @@ -3007,7 +3006,6 @@ dependencies = [ "assert_cmd", "atty", "built", - "chrono", "dialoguer", "dirs-next", "env_logger 0.8.4", @@ -3036,7 +3034,6 @@ name = "taskchampion-lib" version = "0.1.0" dependencies = [ "anyhow", - "chrono", "libc", "pretty_assertions", "taskchampion", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9c3e63974..621b004b5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -24,7 +24,6 @@ toml = "^0.5.8" toml_edit = "^0.2.0" serde = { version = "^1.0.125", features = ["derive"] } serde_json = "^1.0" -chrono = { version = "^0.4.10", features = ["serde"] } lazy_static = "1" iso8601-duration = "0.1" dialoguer = "0.8" diff --git a/cli/src/argparse/args/colon.rs b/cli/src/argparse/args/colon.rs index 3fc94c734..a0eec90f8 100644 --- a/cli/src/argparse/args/colon.rs +++ b/cli/src/argparse/args/colon.rs @@ -1,8 +1,8 @@ use super::{any, timestamp}; use crate::argparse::NOW; -use chrono::prelude::*; use nom::bytes::complete::tag as nomtag; use nom::{branch::*, character::complete::*, combinator::*, sequence::*, IResult}; +use taskchampion::chrono::prelude::*; use taskchampion::Status; /// Recognizes up to the colon of the common `:...` syntax @@ -52,6 +52,7 @@ pub(crate) fn wait_colon(input: &str) -> IResult<&str, Option>> { mod test { use super::*; use pretty_assertions::assert_eq; + use taskchampion::chrono::Duration; #[test] fn test_colon_prefix() { @@ -77,10 +78,10 @@ mod test { fn test_wait() { assert_eq!(wait_colon("wait:").unwrap(), ("", None)); - let one_day = *NOW + chrono::Duration::days(1); + let one_day = *NOW + Duration::days(1); assert_eq!(wait_colon("wait:1d").unwrap(), ("", Some(one_day))); - let one_day = *NOW + chrono::Duration::days(1); + let one_day = *NOW + Duration::days(1); assert_eq!(wait_colon("wait:1d2").unwrap(), ("2", Some(one_day))); } } diff --git a/cli/src/argparse/args/time.rs b/cli/src/argparse/args/time.rs index 66c385f85..4848e3888 100644 --- a/cli/src/argparse/args/time.rs +++ b/cli/src/argparse/args/time.rs @@ -1,4 +1,3 @@ -use chrono::{prelude::*, Duration}; use iso8601_duration::Duration as IsoDuration; use lazy_static::lazy_static; use nom::{ @@ -13,6 +12,7 @@ use nom::{ Err, IResult, }; use std::str::FromStr; +use taskchampion::chrono::{self, prelude::*, Duration}; // https://taskwarrior.org/docs/dates.html // https://taskwarrior.org/docs/named_dates.html diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index 7f2607631..678c9424e 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -31,8 +31,8 @@ pub(crate) use modification::{DescriptionMod, Modification}; pub(crate) use subcommand::Subcommand; use crate::usage::Usage; -use chrono::prelude::*; use lazy_static::lazy_static; +use taskchampion::chrono::prelude::*; lazy_static! { // A static value of NOW to make tests easier diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 08b04ffd2..ed597b2e0 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -1,9 +1,9 @@ use super::args::{any, arg_matching, minus_tag, plus_tag, wait_colon}; use super::ArgList; use crate::usage; -use chrono::prelude::*; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use std::collections::HashSet; +use taskchampion::chrono::prelude::*; use taskchampion::{Status, Tag}; #[derive(Debug, PartialEq, Clone)] @@ -169,6 +169,7 @@ mod test { use super::*; use crate::argparse::NOW; use pretty_assertions::assert_eq; + use taskchampion::chrono::Duration; #[test] fn test_empty() { @@ -215,7 +216,7 @@ mod test { assert_eq!( modification, Modification { - wait: Some(Some(*NOW + chrono::Duration::days(2))), + wait: Some(Some(*NOW + Duration::days(2))), ..Default::default() } ); diff --git a/cli/src/invocation/cmd/import_tdb2.rs b/cli/src/invocation/cmd/import_tdb2.rs index 8db967699..a01ae70fc 100644 --- a/cli/src/invocation/cmd/import_tdb2.rs +++ b/cli/src/invocation/cmd/import_tdb2.rs @@ -84,9 +84,9 @@ fn import_task( mod test { use super::*; use crate::invocation::test::*; - use chrono::{TimeZone, Utc}; use pretty_assertions::assert_eq; use std::convert::TryInto; + use taskchampion::chrono::{TimeZone, Utc}; use taskchampion::{Priority, Status}; use tempfile::TempDir; diff --git a/cli/src/invocation/cmd/import_tw.rs b/cli/src/invocation/cmd/import_tw.rs index 249c1ad46..fc5158b66 100644 --- a/cli/src/invocation/cmd/import_tw.rs +++ b/cli/src/invocation/cmd/import_tw.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, bail}; -use chrono::{DateTime, TimeZone, Utc}; use serde::{self, Deserialize, Deserializer}; use serde_json::Value; use std::collections::HashMap; +use taskchampion::chrono::{DateTime, TimeZone, Utc}; use taskchampion::{Replica, Uuid}; use termcolor::{Color, ColorSpec, WriteColor}; @@ -152,10 +152,10 @@ fn import_task( mod test { use super::*; use crate::invocation::test::*; - use chrono::{TimeZone, Utc}; use pretty_assertions::assert_eq; use serde_json::json; use std::convert::TryInto; + use taskchampion::chrono::{TimeZone, Utc}; use taskchampion::{Priority, Status}; #[test] diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index e3731e5e1..7a6896565 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,5 +1,5 @@ use crate::argparse::{DescriptionMod, Modification}; -use chrono::Utc; +use taskchampion::chrono::Utc; use taskchampion::{Annotation, TaskMut}; /// Apply the given modification diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index db20b56ce..5c75f191e 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -132,9 +132,9 @@ mod test { use super::*; use crate::invocation::test::*; use crate::settings::Sort; - use chrono::prelude::*; use pretty_assertions::assert_eq; use std::convert::TryInto; + use taskchampion::chrono::{prelude::*, Duration}; use taskchampion::{Status, Uuid}; fn create_tasks(replica: &mut Replica) -> [Uuid; 3] { @@ -237,7 +237,7 @@ mod test { .unwrap() .unwrap() .into_mut(&mut replica) - .set_wait(Some(Utc::now() + chrono::Duration::days(2))) + .set_wait(Some(Utc::now() + Duration::days(2))) .unwrap(); replica @@ -245,7 +245,7 @@ mod test { .unwrap() .unwrap() .into_mut(&mut replica) - .set_wait(Some(Utc::now() + chrono::Duration::days(3))) + .set_wait(Some(Utc::now() + Duration::days(3))) .unwrap(); let working_set = replica.working_set().unwrap(); diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 89e3f0fbc..1d57b1a7d 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -7,7 +7,6 @@ publish = false build = "build.rs" [dependencies] -chrono = { version = "^0.4.10", features = ["serde"] } taskchampion = { path = "../taskchampion" } taskchampion-sync-server = { path = "../sync-server" } diff --git a/integration-tests/tests/update-and-delete-sync.rs b/integration-tests/tests/update-and-delete-sync.rs index e75dceae0..91727c7e1 100644 --- a/integration-tests/tests/update-and-delete-sync.rs +++ b/integration-tests/tests/update-and-delete-sync.rs @@ -1,4 +1,4 @@ -use chrono::{TimeZone, Utc}; +use taskchampion::chrono::{TimeZone, Utc}; use taskchampion::{Replica, ServerConfig, Status, StorageConfig}; use tempfile::TempDir; diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 6d6158026..af920e560 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -9,7 +9,6 @@ crate-type = ["staticlib", "cdylib"] [dependencies] libc = "0.2.113" -chrono = "^0.4.10" taskchampion = { path = "../taskchampion" } anyhow = "1.0" diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index 774f94478..acae80129 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -1,6 +1,6 @@ use crate::traits::*; use crate::types::*; -use chrono::prelude::*; +use taskchampion::chrono::prelude::*; /// TCAnnotation contains the details of an annotation. /// diff --git a/lib/src/atomic.rs b/lib/src/atomic.rs index 8a0d9e3d1..21f7bc282 100644 --- a/lib/src/atomic.rs +++ b/lib/src/atomic.rs @@ -1,7 +1,7 @@ //! Trait implementations for a few atomic types use crate::traits::*; -use chrono::prelude::*; +use taskchampion::chrono::prelude::*; impl PassByValue for usize { type RustType = usize; diff --git a/lib/src/task.rs b/lib/src/task.rs index 159553760..a997ed02a 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,11 +1,11 @@ use crate::traits::*; use crate::types::*; use crate::util::err_to_ruststring; -use chrono::{TimeZone, Utc}; use std::convert::TryFrom; use std::ops::Deref; use std::ptr::NonNull; use std::str::FromStr; +use taskchampion::chrono::{TimeZone, Utc}; use taskchampion::{Annotation, Tag, Task, TaskMut}; /// A task, as publicly exposed by this library. diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index dea259d4c..24dcd852d 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -63,3 +63,6 @@ pub use workingset::WorkingSet; /// Re-exported type from the `uuid` crate, for ease of compatibility for consumers of this crate. pub use uuid::Uuid; + +/// Re-exported chrono module. +pub use chrono; From bf73cc4cc749543272bee15619a6957224d1c724 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 21 Feb 2022 21:22:18 +0000 Subject: [PATCH 537/548] add dependency support to taskchampion --- integration-tests/src/bindings_tests/task.c | 38 +++++++++++++++ lib/src/task.rs | 52 ++++++++++++++++++++- lib/taskchampion.h | 15 ++++++ taskchampion/src/task/task.rs | 50 ++++++++++++++++++++ 4 files changed, 154 insertions(+), 1 deletion(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 4e20e52b3..4a30f8e1c 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -520,6 +520,43 @@ static void test_task_udas(void) { tc_replica_free(rep); } +// dependency manipulation +static void test_task_dependencies(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep).ptr); + + TCTask *task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task 1")); + TEST_ASSERT_NOT_NULL(task1); + TCTask *task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task 2")); + TEST_ASSERT_NOT_NULL(task2); + + TCUuidList deps; + + deps = tc_task_get_dependencies(task1); + TEST_ASSERT_EQUAL(0, deps.len); + tc_uuid_list_free(&deps); + + tc_task_to_mut(task1, rep); + TEST_ASSERT_EQUAL(TC_RESULT_OK, + tc_task_add_dependency(task1, tc_task_get_uuid(task2))); + + deps = tc_task_get_dependencies(task1); + TEST_ASSERT_EQUAL(1, deps.len); + TEST_ASSERT_EQUAL_MEMORY(tc_task_get_uuid(task2).bytes, deps.items[0].bytes, 16); + tc_uuid_list_free(&deps); + + TEST_ASSERT_EQUAL(TC_RESULT_OK, + tc_task_remove_dependency(task1, tc_task_get_uuid(task2))); + + deps = tc_task_get_dependencies(task1); + TEST_ASSERT_EQUAL(0, deps.len); + tc_uuid_list_free(&deps); + + tc_task_free(task1); + tc_task_free(task2); + tc_replica_free(rep); +} + static void tckvlist_assert_key(TCKVList *list, char *key, char *value) { TEST_ASSERT_NOT_NULL(list); for (size_t i = 0; i < list->len; i++) { @@ -624,6 +661,7 @@ int task_tests(void) { RUN_TEST(test_task_get_tags); RUN_TEST(test_task_annotations); RUN_TEST(test_task_udas); + RUN_TEST(test_task_dependencies); RUN_TEST(test_task_taskmap); RUN_TEST(test_task_list_take); return UNITY_END(); diff --git a/lib/src/task.rs b/lib/src/task.rs index ee4709a38..dce84d7aa 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -6,7 +6,7 @@ use std::ops::Deref; use std::ptr::NonNull; use std::str::FromStr; use taskchampion::chrono::{TimeZone, Utc}; -use taskchampion::{Annotation, Tag, Task, TaskMut}; +use taskchampion::{Annotation, Tag, Task, TaskMut, Uuid}; /// A task, as publicly exposed by this library. /// @@ -790,6 +790,56 @@ pub unsafe extern "C" fn tc_task_remove_legacy_uda(task: *mut TCTask, key: TCStr ) } +/// Get all dependencies for a task. +#[no_mangle] +pub unsafe extern "C" fn tc_task_get_dependencies(task: *mut TCTask) -> TCUuidList { + wrap(task, |task| { + let vec: Vec = task + .get_dependencies() + .map(|u| { + // SAFETY: + // - value is not allocated + unsafe { TCUuid::return_val(u) } + }) + .collect(); + // SAFETY: + // - caller will free this list + unsafe { TCUuidList::return_val(vec) } + }) +} + +/// Add a dependency. +#[no_mangle] +pub unsafe extern "C" fn tc_task_add_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult { + // SAFETY: + // - tcuuid is a valid TCUuid (all byte patterns are valid) + let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) }; + wrap_mut( + task, + |task| { + task.add_dependency(dep)?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + +/// Remove a dependency. +#[no_mangle] +pub unsafe extern "C" fn tc_task_remove_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult { + // SAFETY: + // - tcuuid is a valid TCUuid (all byte patterns are valid) + let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) }; + wrap_mut( + task, + |task| { + task.remove_dependency(dep)?; + Ok(TCResult::Ok) + }, + TCResult::Error, + ) +} + /// Get the latest error for a task, or a string NULL ptr field if the last operation succeeded. /// Subsequent calls to this function will return NULL. The task pointer must not be NULL. The /// caller must free the returned string. diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 8c5f02575..a4ec0061e 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -911,6 +911,21 @@ TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct */ TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); +/** + * Get all dependencies for a task. + */ +struct TCUuidList tc_task_get_dependencies(struct TCTask *task); + +/** + * Add a dependency. + */ +TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep); + +/** + * Remove a dependency. + */ +TCResult tc_task_remove_dependency(struct TCTask *task, struct TCUuid dep); + /** * Get the latest error for a task, or a string NULL ptr field if the last operation succeeded. * Subsequent calls to this function will return NULL. The task pointer must not be NULL. The diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index cd8a12f85..fa8700a62 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -243,10 +243,27 @@ impl Task { .map(|(p, v)| (p.as_ref(), v.as_ref())) } + /// Get the modification time for this task. pub fn get_modified(&self) -> Option> { self.get_timestamp(Prop::Modified.as_ref()) } + /// Get the UUIDs of tasks on which this task depends. + /// + /// This includes all dependencies, regardless of their status. In fact, it may include + /// dependencies that do not exist. + pub fn get_dependencies(&self) -> impl Iterator + '_ { + self.taskmap.iter().filter_map(|(p, _)| { + if let Some(dep_str) = p.strip_prefix("dep_") { + if let Ok(u) = Uuid::parse_str(dep_str) { + return Some(u); + } + // (un-parseable dep_.. properties are ignored) + } + None + }) + } + // -- utility functions fn is_known_key(key: &str) -> bool { @@ -423,6 +440,18 @@ impl<'r> TaskMut<'r> { self.set_string(key, None) } + /// Add a dependency. + pub fn add_dependency(&mut self, dep: Uuid) -> anyhow::Result<()> { + let key = format!("dep_{}", dep); + self.set_string(key, Some("".to_string())) + } + + /// Remove a dependency. + pub fn remove_dependency(&mut self, dep: Uuid) -> anyhow::Result<()> { + let key = format!("dep_{}", dep); + self.set_string(key, None) + } + // -- utility functions fn update_modified(&mut self) -> anyhow::Result<()> { @@ -1011,4 +1040,25 @@ mod test { assert!(task.remove_legacy_uda("tag_abc").is_err()); }) } + + #[test] + fn test_dependencies() { + with_mut_task(|mut task| { + assert_eq!(task.get_dependencies().collect::>(), vec![]); + let dep1 = Uuid::new_v4(); + let dep2 = Uuid::new_v4(); + + task.add_dependency(dep1).unwrap(); + assert_eq!(task.get_dependencies().collect::>(), vec![dep1]); + + task.add_dependency(dep1).unwrap(); // add twice is ok + task.add_dependency(dep2).unwrap(); + let deps = task.get_dependencies().collect::>(); + assert!(deps.contains(&dep1)); + assert!(deps.contains(&dep2)); + + task.remove_dependency(dep1).unwrap(); + assert_eq!(task.get_dependencies().collect::>(), vec![dep2]); + }) + } } From db1e1c9c96a593461baf53745c1f0af7271d83fd Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 21 Feb 2022 22:15:31 +0000 Subject: [PATCH 538/548] Support parsing depends:.. in CLI --- cli/src/argparse/args/colon.rs | 13 ++++- cli/src/argparse/args/idlist.rs | 2 +- cli/src/argparse/args/mod.rs | 2 +- cli/src/argparse/modification.rs | 94 +++++++++++++++++++++++++++++--- 4 files changed, 99 insertions(+), 12 deletions(-) diff --git a/cli/src/argparse/args/colon.rs b/cli/src/argparse/args/colon.rs index a0eec90f8..bca014081 100644 --- a/cli/src/argparse/args/colon.rs +++ b/cli/src/argparse/args/colon.rs @@ -1,4 +1,4 @@ -use super::{any, timestamp}; +use super::{any, id_list, timestamp, TaskId}; use crate::argparse::NOW; use nom::bytes::complete::tag as nomtag; use nom::{branch::*, character::complete::*, combinator::*, sequence::*, IResult}; @@ -48,6 +48,17 @@ pub(crate) fn wait_colon(input: &str) -> IResult<&str, Option>> { )(input) } +/// Recognizes `depends:` to `(true, )` and `depends:-` to `(false, )`. +pub(crate) fn depends_colon(input: &str) -> IResult<&str, (bool, Vec)> { + fn to_bool(maybe_minus: Option) -> Result { + Ok(maybe_minus.is_none()) // None -> true, Some -> false + } + preceded( + nomtag("depends:"), + pair(map_res(opt(char('-')), to_bool), id_list), + )(input) +} + #[cfg(test)] mod test { use super::*; diff --git a/cli/src/argparse/args/idlist.rs b/cli/src/argparse/args/idlist.rs index 095dd1cee..a7ea71e0e 100644 --- a/cli/src/argparse/args/idlist.rs +++ b/cli/src/argparse/args/idlist.rs @@ -2,7 +2,7 @@ use nom::{branch::*, character::complete::*, combinator::*, multi::*, sequence:: use taskchampion::Uuid; /// A task identifier, as given in a filter command-line expression -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub(crate) enum TaskId { /// A small integer identifying a working-set task WorkingSetId(usize), diff --git a/cli/src/argparse/args/mod.rs b/cli/src/argparse/args/mod.rs index f7124fa29..b9c20e9cd 100644 --- a/cli/src/argparse/args/mod.rs +++ b/cli/src/argparse/args/mod.rs @@ -8,7 +8,7 @@ mod tags; mod time; pub(crate) use arg_matching::arg_matching; -pub(crate) use colon::{status_colon, wait_colon}; +pub(crate) use colon::{depends_colon, status_colon, wait_colon}; pub(crate) use idlist::{id_list, TaskId}; pub(crate) use misc::{any, literal, report_name}; pub(crate) use tags::{minus_tag, plus_tag}; diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index ed597b2e0..21ddeef8e 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -1,4 +1,4 @@ -use super::args::{any, arg_matching, minus_tag, plus_tag, wait_colon}; +use super::args::{any, arg_matching, depends_colon, minus_tag, plus_tag, wait_colon, TaskId}; use super::ArgList; use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; @@ -30,27 +30,33 @@ impl Default for DescriptionMod { /// A modification represents a change to a task: adding or removing tags, setting the /// description, and so on. #[derive(Debug, PartialEq, Clone, Default)] -pub struct Modification { +pub(crate) struct Modification { /// Change the description - pub description: DescriptionMod, + pub(crate) description: DescriptionMod, /// Set the status - pub status: Option, + pub(crate) status: Option, /// Set (or, with `Some(None)`, clear) the wait timestamp - pub wait: Option>>, + pub(crate) wait: Option>>, /// Set the "active" state, that is, start (true) or stop (false) the task. - pub active: Option, + pub(crate) active: Option, /// Add tags - pub add_tags: HashSet, + pub(crate) add_tags: HashSet, /// Remove tags - pub remove_tags: HashSet, + pub(crate) remove_tags: HashSet, + + /// Add dependencies + pub(crate) add_dependencies: HashSet, + + /// Remove dependencies + pub(crate) remove_dependencies: HashSet, /// Add annotation - pub annotate: Option, + pub(crate) annotate: Option, } /// A single argument that is part of a modification, used internally to this module @@ -59,6 +65,8 @@ enum ModArg<'a> { PlusTag(Tag), MinusTag(Tag), Wait(Option>), + AddDependencies(Vec), + RemoveDependencies(Vec), } impl Modification { @@ -82,6 +90,16 @@ impl Modification { ModArg::Wait(wait) => { acc.wait = Some(wait); } + ModArg::AddDependencies(task_ids) => { + for tid in task_ids { + acc.add_dependencies.insert(tid); + } + } + ModArg::RemoveDependencies(task_ids) => { + for tid in task_ids { + acc.remove_dependencies.insert(tid); + } + } } acc } @@ -90,6 +108,7 @@ impl Modification { Self::plus_tag, Self::minus_tag, Self::wait, + Self::dependencies, // this must come last Self::description, )), @@ -128,6 +147,17 @@ impl Modification { map_res(arg_matching(wait_colon), to_modarg)(input) } + fn dependencies(input: ArgList) -> IResult { + fn to_modarg(input: (bool, Vec)) -> Result, ()> { + Ok(if input.0 { + ModArg::AddDependencies(input.1) + } else { + ModArg::RemoveDependencies(input.1) + }) + } + map_res(arg_matching(depends_colon), to_modarg)(input) + } + pub(super) fn get_usage(u: &mut usage::Usage) { u.modifications.push(usage::Modification { syntax: "DESCRIPTION", @@ -161,6 +191,19 @@ impl Modification { reports, e.g., `wait:3day` to wait for three days. With `wait:`, the time is un-set. See the documentation for the timestamp syntax.", }); + u.modifications.push(usage::Modification { + syntax: "depends:", + summary: "Add task dependencies", + description: " + Add a dependency of this task on the given tasks. The tasks can be specified + in the same syntax as for filters, e.g., `depends:13,94500c95`.", + }); + u.modifications.push(usage::Modification { + syntax: "depends:-", + summary: "Remove task dependencies", + description: " + Remove the dependency of this task on the given tasks.", + }); } } @@ -222,6 +265,39 @@ mod test { ); } + #[test] + fn test_add_deps() { + let (input, modification) = Modification::parse(argv!["depends:13,e72b73d1-9e88"]).unwrap(); + assert_eq!(input.len(), 0); + let mut deps = HashSet::new(); + deps.insert(TaskId::WorkingSetId(13)); + deps.insert(TaskId::PartialUuid("e72b73d1-9e88".into())); + assert_eq!( + modification, + Modification { + add_dependencies: deps, + ..Default::default() + } + ); + } + + #[test] + fn test_remove_deps() { + let (input, modification) = + Modification::parse(argv!["depends:-13,e72b73d1-9e88"]).unwrap(); + assert_eq!(input.len(), 0); + let mut deps = HashSet::new(); + deps.insert(TaskId::WorkingSetId(13)); + deps.insert(TaskId::PartialUuid("e72b73d1-9e88".into())); + assert_eq!( + modification, + Modification { + remove_dependencies: deps, + ..Default::default() + } + ); + } + #[test] fn test_unset_wait() { let (input, modification) = Modification::parse(argv!["wait:"]).unwrap(); From 47b1fed42a5738b2339a4bf37db5f324ab49e4a0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 22 Feb 2022 02:01:57 +0000 Subject: [PATCH 539/548] Add support for modifying dependencies This requires "resolving" dependencies after the command-line parsing phase is complete. --- cli/src/invocation/cmd/add.rs | 21 ++-- cli/src/invocation/cmd/modify.rs | 14 +-- cli/src/invocation/mod.rs | 12 +- cli/src/invocation/modify.rs | 200 ++++++++++++++++++++++++++++++- cli/src/macros.rs | 1 + 5 files changed, 225 insertions(+), 23 deletions(-) diff --git a/cli/src/invocation/cmd/add.rs b/cli/src/invocation/cmd/add.rs index 1957e6dfc..5dfd5c215 100644 --- a/cli/src/invocation/cmd/add.rs +++ b/cli/src/invocation/cmd/add.rs @@ -1,19 +1,19 @@ -use crate::argparse::{DescriptionMod, Modification}; -use crate::invocation::apply_modification; +use crate::argparse::DescriptionMod; +use crate::invocation::{apply_modification, ResolvedModification}; use taskchampion::{Replica, Status}; use termcolor::WriteColor; -pub(crate) fn execute( +pub(in crate::invocation) fn execute( w: &mut W, replica: &mut Replica, - mut modification: Modification, + mut modification: ResolvedModification, ) -> Result<(), crate::Error> { // extract the description from the modification to handle it specially - let description = match modification.description { + let description = match modification.0.description { DescriptionMod::Set(ref s) => s.clone(), _ => "(no description)".to_owned(), }; - modification.description = DescriptionMod::None; + modification.0.description = DescriptionMod::None; let task = replica.new_task(Status::Pending, description).unwrap(); let mut task = task.into_mut(replica); @@ -25,6 +25,7 @@ pub(crate) fn execute( #[cfg(test)] mod test { use super::*; + use crate::argparse::Modification; use crate::invocation::test::*; use pretty_assertions::assert_eq; @@ -32,10 +33,10 @@ mod test { fn test_add() { let mut w = test_writer(); let mut replica = test_replica(); - let modification = Modification { + let modification = ResolvedModification(Modification { description: DescriptionMod::Set(s!("my description")), ..Default::default() - }; + }); execute(&mut w, &mut replica, modification).unwrap(); // check that the task appeared.. @@ -54,11 +55,11 @@ mod test { fn test_add_with_tags() { let mut w = test_writer(); let mut replica = test_replica(); - let modification = Modification { + let modification = ResolvedModification(Modification { description: DescriptionMod::Set(s!("my description")), add_tags: vec![tag!("tag1")].drain(..).collect(), ..Default::default() - }; + }); execute(&mut w, &mut replica, modification).unwrap(); // check that the task appeared.. diff --git a/cli/src/invocation/cmd/modify.rs b/cli/src/invocation/cmd/modify.rs index 3bf7e5b41..cb8eaf5b1 100644 --- a/cli/src/invocation/cmd/modify.rs +++ b/cli/src/invocation/cmd/modify.rs @@ -1,6 +1,6 @@ -use crate::argparse::{Filter, Modification}; +use crate::argparse::Filter; use crate::invocation::util::{confirm, summarize_task}; -use crate::invocation::{apply_modification, filtered_tasks}; +use crate::invocation::{apply_modification, filtered_tasks, ResolvedModification}; use crate::settings::Settings; use taskchampion::Replica; use termcolor::WriteColor; @@ -39,12 +39,12 @@ fn check_modification( Ok(false) } -pub(crate) fn execute( +pub(in crate::invocation) fn execute( w: &mut W, replica: &mut Replica, settings: &Settings, filter: Filter, - modification: Modification, + modification: ResolvedModification, ) -> Result<(), crate::Error> { let tasks = filtered_tasks(replica, &filter)?; @@ -68,7 +68,7 @@ pub(crate) fn execute( #[cfg(test)] mod test { use super::*; - use crate::argparse::DescriptionMod; + use crate::argparse::{DescriptionMod, Modification}; use crate::invocation::test::test_replica; use crate::invocation::test::*; use pretty_assertions::assert_eq; @@ -87,10 +87,10 @@ mod test { let filter = Filter { ..Default::default() }; - let modification = Modification { + let modification = ResolvedModification(Modification { description: DescriptionMod::Set(s!("new description")), ..Default::default() - }; + }); execute(&mut w, &mut replica, &settings, filter, modification).unwrap(); // check that the task appeared.. diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index 0ae3f44e0..0b75799f4 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -15,7 +15,7 @@ mod util; mod test; use filter::filtered_tasks; -use modify::apply_modification; +use modify::{apply_modification, resolve_modification, ResolvedModification}; use report::display_report; /// Invoke the given Command in the context of the given settings @@ -52,7 +52,10 @@ pub(crate) fn invoke(command: Command, settings: Settings) -> Result<(), crate:: Command { subcommand: Subcommand::Add { modification }, .. - } => return cmd::add::execute(&mut w, &mut replica, modification), + } => { + let modification = resolve_modification(modification, &mut replica)?; + return cmd::add::execute(&mut w, &mut replica, modification); + } Command { subcommand: @@ -61,7 +64,10 @@ pub(crate) fn invoke(command: Command, settings: Settings) -> Result<(), crate:: modification, }, .. - } => return cmd::modify::execute(&mut w, &mut replica, &settings, filter, modification), + } => { + let modification = resolve_modification(modification, &mut replica)?; + return cmd::modify::execute(&mut w, &mut replica, &settings, filter, modification); + } Command { subcommand: diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index 7a6896565..934ab1bc2 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,12 +1,97 @@ -use crate::argparse::{DescriptionMod, Modification}; +use crate::argparse::{DescriptionMod, Modification, TaskId}; +use std::collections::HashSet; use taskchampion::chrono::Utc; -use taskchampion::{Annotation, TaskMut}; +use taskchampion::{Annotation, Replica, TaskMut}; + +/// A wrapper for Modification, promising that all TaskId instances are of variant TaskId::Uuid. +pub(super) struct ResolvedModification(pub(super) Modification); + +/// Resolve a Modification to a ResolvedModification, based on access to a Replica. +/// +/// This is not automatically done in `apply_modification` because, by that time, the TaskMut being +/// modified has an exclusive reference to the Replica, so it is impossible to search for matching +/// tasks. +pub(super) fn resolve_modification( + unres: Modification, + replica: &mut Replica, +) -> anyhow::Result { + Ok(ResolvedModification(Modification { + description: unres.description, + status: unres.status, + wait: unres.wait, + active: unres.active, + add_tags: unres.add_tags, + remove_tags: unres.remove_tags, + add_dependencies: resolve_task_ids(replica, unres.add_dependencies)?, + remove_dependencies: resolve_task_ids(replica, unres.remove_dependencies)?, + annotate: unres.annotate, + })) +} + +/// Convert a set of arbitrary TaskId's into TaskIds containing only TaskId::Uuid. +fn resolve_task_ids( + replica: &mut Replica, + task_ids: HashSet, +) -> anyhow::Result> { + // already all UUIDs (or empty)? + if task_ids.iter().all(|tid| matches!(tid, TaskId::Uuid(_))) { + return Ok(task_ids); + } + + let mut result = HashSet::new(); + let mut working_set = None; + let mut all_tasks = None; + for tid in task_ids { + match tid { + TaskId::WorkingSetId(i) => { + let ws = match working_set { + Some(ref ws) => ws, + None => { + working_set = Some(replica.working_set()?); + working_set.as_ref().unwrap() + } + }; + if let Some(u) = ws.by_index(i) { + result.insert(TaskId::Uuid(u)); + } + } + TaskId::PartialUuid(partial) => { + let ts = match all_tasks { + Some(ref ts) => ts, + None => { + all_tasks = Some( + replica + .all_task_uuids()? + .drain(..) + .map(|u| (u, u.to_string())) + .collect::>(), + ); + all_tasks.as_ref().unwrap() + } + }; + for (u, ustr) in ts { + if ustr.starts_with(&partial) { + result.insert(TaskId::Uuid(*u)); + } + } + } + TaskId::Uuid(u) => { + result.insert(TaskId::Uuid(u)); + } + } + } + + Ok(result) +} /// Apply the given modification pub(super) fn apply_modification( task: &mut TaskMut, - modification: &Modification, + modification: &ResolvedModification, ) -> anyhow::Result<()> { + // unwrap the "Resolved" promise + let modification = &modification.0; + match modification.description { DescriptionMod::Set(ref description) => task.set_description(description.clone())?, DescriptionMod::Prepend(ref description) => { @@ -49,5 +134,114 @@ pub(super) fn apply_modification( })?; } + for tid in &modification.add_dependencies { + if let TaskId::Uuid(u) = tid { + task.add_dependency(*u)?; + } else { + // this Modification is resolved, so all TaskIds should + // be the Uuid variant. + unreachable!(); + } + } + + for tid in &modification.remove_dependencies { + if let TaskId::Uuid(u) = tid { + task.remove_dependency(*u)?; + } else { + // this Modification is resolved, so all TaskIds should + // be the Uuid variant. + unreachable!(); + } + } + Ok(()) } + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::test::*; + use pretty_assertions::assert_eq; + use taskchampion::{Status, Uuid}; + + #[test] + fn test_resolve_modifications() { + let mut replica = test_replica(); + let u1 = Uuid::new_v4(); + let t1 = replica.new_task(Status::Pending, "a task".into()).unwrap(); + replica.rebuild_working_set(true).unwrap(); + + let modi = Modification { + add_dependencies: set![TaskId::Uuid(u1), TaskId::WorkingSetId(1)], + ..Default::default() + }; + + let res = resolve_modification(modi, &mut replica).unwrap(); + + assert_eq!( + res.0.add_dependencies, + set![TaskId::Uuid(u1), TaskId::Uuid(t1.get_uuid())], + ); + } + + #[test] + fn test_resolve_task_ids_empty() { + let mut replica = test_replica(); + + assert_eq!( + resolve_task_ids(&mut replica, HashSet::new()).unwrap(), + HashSet::new() + ); + } + + #[test] + fn test_resolve_task_ids_all_uuids() { + let mut replica = test_replica(); + let uuid = Uuid::new_v4(); + let tids = set![TaskId::Uuid(uuid)]; + assert_eq!(resolve_task_ids(&mut replica, tids.clone()).unwrap(), tids); + } + + #[test] + fn test_resolve_task_ids_working_set_not_found() { + let mut replica = test_replica(); + let tids = set![TaskId::WorkingSetId(13)]; + assert_eq!( + resolve_task_ids(&mut replica, tids.clone()).unwrap(), + HashSet::new() + ); + } + + #[test] + fn test_resolve_task_ids_working_set() { + let mut replica = test_replica(); + let t1 = replica.new_task(Status::Pending, "a task".into()).unwrap(); + let t2 = replica + .new_task(Status::Pending, "another task".into()) + .unwrap(); + replica.rebuild_working_set(true).unwrap(); + let tids = set![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2)]; + let resolved = set![TaskId::Uuid(t1.get_uuid()), TaskId::Uuid(t2.get_uuid())]; + assert_eq!(resolve_task_ids(&mut replica, tids).unwrap(), resolved); + } + + #[test] + fn test_resolve_task_ids_partial_not_found() { + let mut replica = test_replica(); + let tids = set![TaskId::PartialUuid("abcd".into())]; + assert_eq!( + resolve_task_ids(&mut replica, tids.clone()).unwrap(), + HashSet::new() + ); + } + + #[test] + fn test_resolve_task_ids_partial() { + let mut replica = test_replica(); + let t1 = replica.new_task(Status::Pending, "a task".into()).unwrap(); + let uuid_str = t1.get_uuid().to_string(); + let tids = set![TaskId::PartialUuid(uuid_str[..8].into())]; + let resolved = set![TaskId::Uuid(t1.get_uuid())]; + assert_eq!(resolve_task_ids(&mut replica, tids).unwrap(), resolved); + } +} diff --git a/cli/src/macros.rs b/cli/src/macros.rs index 1a3024c13..0f0eab6ad 100644 --- a/cli/src/macros.rs +++ b/cli/src/macros.rs @@ -12,6 +12,7 @@ macro_rules! argv { } /// Create a hashset, similar to vec! +// NOTE: in Rust 1.56.0, this can be changed to HashSet::from([..]) #[cfg(test)] macro_rules! set( { $($key:expr),+ } => { From 47e213d6ec649a036fb4b6f1172c676d0142cee9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 22 Feb 2022 02:42:16 +0000 Subject: [PATCH 540/548] add support for dependencies to 'ta info' --- cli/src/invocation/cmd/info.rs | 45 +++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/cli/src/invocation/cmd/info.rs b/cli/src/invocation/cmd/info.rs index bd7e6e269..2766e6267 100644 --- a/cli/src/invocation/cmd/info.rs +++ b/cli/src/invocation/cmd/info.rs @@ -2,7 +2,7 @@ use crate::argparse::Filter; use crate::invocation::filtered_tasks; use crate::table; use prettytable::{cell, row, Table}; -use taskchampion::Replica; +use taskchampion::{Replica, Status}; use termcolor::WriteColor; pub(crate) fn execute( @@ -44,6 +44,25 @@ pub(crate) fn execute( for ann in annotations { t.add_row(row![b->"Annotation", format!("{}: {}", ann.entry, ann.description)]); } + + let mut deps: Vec<_> = task.get_dependencies().collect(); + deps.sort(); + for dep in deps { + let mut descr = None; + if let Some(task) = replica.get_task(dep)? { + if task.get_status() == Status::Pending { + if let Some(i) = working_set.by_uuid(dep) { + descr = Some(format!("{} - {}", i, task.get_description())) + } else { + descr = Some(format!("{} - {}", dep, task.get_description())) + } + } + } + + if let Some(descr) = descr { + t.add_row(row![b->"Depends On", descr]); + } + } } t.print(w)?; } @@ -54,6 +73,7 @@ pub(crate) fn execute( #[cfg(test)] mod test { use super::*; + use crate::argparse::{Condition, TaskId}; use crate::invocation::test::*; use taskchampion::Status; @@ -71,4 +91,27 @@ mod test { execute(&mut w, &mut replica, filter, debug).unwrap(); assert!(w.into_string().contains("my task")); } + + #[test] + fn test_deps() { + let mut w = test_writer(); + let mut replica = test_replica(); + let t1 = replica.new_task(Status::Pending, s!("my task")).unwrap(); + let t2 = replica + .new_task(Status::Pending, s!("dunno, depends")) + .unwrap(); + let mut t2 = t2.into_mut(&mut replica); + t2.add_dependency(t1.get_uuid()).unwrap(); + let t2 = t2.into_immut(); + + let filter = Filter { + conditions: vec![Condition::IdList(vec![TaskId::Uuid(t2.get_uuid())])], + }; + let debug = false; + execute(&mut w, &mut replica, filter, debug).unwrap(); + let s = w.into_string(); + // length of whitespace between these two strings is not important + assert!(s.contains("Depends On")); + assert!(s.contains("1 - my task")); + } } From 6f48f715ac4226d2b36188a84df91f8ddacd75cd Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 22 Feb 2022 02:33:22 +0000 Subject: [PATCH 541/548] +BLOCKED, +UNBLOCKED, and +BLOCKING tags These are somewhat expensive tags, as they require reference to values outside of the task itself. To accomplish this, the replica supplies a pre-computed DependencyMap that is only calculated once per replica, and only from the working set. --- cli/src/invocation/report.rs | 7 +- cli/src/macros.rs | 5 +- taskchampion/src/depmap.rs | 81 +++++++++++++++++++++++ taskchampion/src/lib.rs | 5 ++ taskchampion/src/macros.rs | 17 +++++ taskchampion/src/replica.rs | 118 ++++++++++++++++++++++++++++++++-- taskchampion/src/task/tag.rs | 3 + taskchampion/src/task/task.rs | 103 +++++++++++++++++++++++++---- 8 files changed, 318 insertions(+), 21 deletions(-) create mode 100644 taskchampion/src/depmap.rs create mode 100644 taskchampion/src/macros.rs diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 5c75f191e..81b4c8622 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -406,9 +406,12 @@ mod test { let task = replica.get_task(uuids[0]).unwrap().unwrap(); assert_eq!( task_column(&task, &column, &working_set), - s!("+PENDING +bar +foo") + s!("+PENDING +UNBLOCKED +bar +foo") ); let task = replica.get_task(uuids[2]).unwrap().unwrap(); - assert_eq!(task_column(&task, &column, &working_set), s!("+PENDING")); + assert_eq!( + task_column(&task, &column, &working_set), + s!("+PENDING +UNBLOCKED") + ); } } diff --git a/cli/src/macros.rs b/cli/src/macros.rs index 0f0eab6ad..f2cbe803b 100644 --- a/cli/src/macros.rs +++ b/cli/src/macros.rs @@ -15,12 +15,13 @@ macro_rules! argv { // NOTE: in Rust 1.56.0, this can be changed to HashSet::from([..]) #[cfg(test)] macro_rules! set( - { $($key:expr),+ } => { + { $($key:expr),* $(,)? } => { { + #[allow(unused_mut)] let mut s = ::std::collections::HashSet::new(); $( s.insert($key); - )+ + )* s } }; diff --git a/taskchampion/src/depmap.rs b/taskchampion/src/depmap.rs new file mode 100644 index 000000000..d2f6225bf --- /dev/null +++ b/taskchampion/src/depmap.rs @@ -0,0 +1,81 @@ +use uuid::Uuid; + +/// DependencyMap stores information on task dependencies between pending tasks. +/// +/// This information requires a scan of the working set to generate, so it is +/// typically calculated once and re-used. +#[derive(Debug, PartialEq)] +pub struct DependencyMap { + /// Edges of the dependency graph. If (a, b) is in this array, then task a depends on tsak b. + edges: Vec<(Uuid, Uuid)>, +} + +impl DependencyMap { + /// Create a new, empty DependencyMap. + pub(super) fn new() -> Self { + Self { edges: Vec::new() } + } + + /// Add a dependency of a on b. + pub(super) fn add_dependency(&mut self, a: Uuid, b: Uuid) { + self.edges.push((a, b)); + } + + /// Return an iterator of Uuids on which task `deps_of` depends. This is equivalent to + /// `task.get_dependencies()`. + pub fn dependencies(&self, dep_of: Uuid) -> impl Iterator + '_ { + self.edges + .iter() + .filter_map(move |(a, b)| if a == &dep_of { Some(*b) } else { None }) + } + + /// Return an iterator of Uuids of tasks that depend on `dep_on` + /// `task.get_dependencies()`. + pub fn dependents(&self, dep_on: Uuid) -> impl Iterator + '_ { + self.edges + .iter() + .filter_map(move |(a, b)| if b == &dep_on { Some(*a) } else { None }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + use std::collections::HashSet; + + #[test] + fn dependencies() { + let t = Uuid::new_v4(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + let mut dm = DependencyMap::new(); + + dm.add_dependency(t, uuid1); + dm.add_dependency(t, uuid2); + dm.add_dependency(Uuid::new_v4(), t); + dm.add_dependency(Uuid::new_v4(), uuid1); + dm.add_dependency(uuid2, Uuid::new_v4()); + + assert_eq!( + dm.dependencies(t).collect::>(), + set![uuid1, uuid2] + ); + } + + #[test] + fn dependents() { + let t = Uuid::new_v4(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + let mut dm = DependencyMap::new(); + + dm.add_dependency(uuid1, t); + dm.add_dependency(uuid2, t); + dm.add_dependency(t, Uuid::new_v4()); + dm.add_dependency(Uuid::new_v4(), uuid1); + dm.add_dependency(uuid2, Uuid::new_v4()); + + assert_eq!(dm.dependents(t).collect::>(), set![uuid1, uuid2]); + } +} diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 24dcd852d..2e22b992e 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -45,6 +45,10 @@ This crate supports Rust version 1.47 and higher. */ +// NOTE: it's important that this 'mod' comes first so that the macros can be used in other modules +mod macros; + +mod depmap; mod errors; mod replica; pub mod server; @@ -54,6 +58,7 @@ mod taskdb; mod utils; mod workingset; +pub use depmap::DependencyMap; pub use errors::Error; pub use replica::Replica; pub use server::{Server, ServerConfig}; diff --git a/taskchampion/src/macros.rs b/taskchampion/src/macros.rs new file mode 100644 index 000000000..eb34b4640 --- /dev/null +++ b/taskchampion/src/macros.rs @@ -0,0 +1,17 @@ +#![macro_use] + +/// Create a hashset, similar to vec! +// NOTE: in Rust 1.56.0, this can be changed to HashSet::from([..]) +#[cfg(test)] +macro_rules! set( + { $($key:expr),* $(,)? } => { + { + #[allow(unused_mut)] + let mut s = ::std::collections::HashSet::new(); + $( + s.insert($key); + )* + s + } + }; +); diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 686b34842..3787f3b25 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -1,3 +1,4 @@ +use crate::depmap::DependencyMap; use crate::server::{Server, SyncOp}; use crate::storage::{Storage, TaskMap}; use crate::task::{Status, Task}; @@ -7,6 +8,7 @@ use anyhow::Context; use chrono::{Duration, Utc}; use log::trace; use std::collections::HashMap; +use std::rc::Rc; use uuid::Uuid; /// A replica represents an instance of a user's task data, providing an easy interface @@ -28,7 +30,12 @@ use uuid::Uuid; /// during the garbage-collection process. pub struct Replica { taskdb: TaskDb, + + /// If true, this replica has already added an undo point. added_undo_point: bool, + + /// The dependency map for this replica, if it has been calculated. + depmap: Option>, } impl Replica { @@ -36,6 +43,7 @@ impl Replica { Replica { taskdb: TaskDb::new(storage), added_undo_point: false, + depmap: None, } } @@ -76,9 +84,10 @@ impl Replica { /// Get all tasks represented as a map keyed by UUID pub fn all_tasks(&mut self) -> anyhow::Result> { + let depmap = self.dependency_map(false)?; let mut res = HashMap::new(); for (uuid, tm) in self.taskdb.all_tasks()?.drain(..) { - res.insert(uuid, Task::new(uuid, tm)); + res.insert(uuid, Task::new(uuid, tm, depmap.clone())); } Ok(res) } @@ -94,12 +103,47 @@ impl Replica { Ok(WorkingSet::new(self.taskdb.working_set()?)) } + /// Get the dependency map for all pending tasks. + /// + /// The data in this map is cached when it is first requested and may not contain modifications + /// made locally in this Replica instance. The result is reference-counted and may + /// outlive the Replica. + /// + /// If `force` is true, then the result is re-calculated from the current state of the replica, + /// although previously-returned dependency maps are not updated. + pub fn dependency_map(&mut self, force: bool) -> anyhow::Result> { + if force || self.depmap.is_none() { + let mut dm = DependencyMap::new(); + let ws = self.working_set()?; + for i in 1..=ws.largest_index() { + if let Some(u) = ws.by_index(i) { + // note: we can't use self.get_task here, as that depends on a + // DependencyMap + if let Some(taskmap) = self.taskdb.get_task(u)? { + for p in taskmap.keys() { + if let Some(dep_str) = p.strip_prefix("dep_") { + if let Ok(dep) = Uuid::parse_str(dep_str) { + dm.add_dependency(u, dep); + } + } + } + } + } + } + self.depmap = Some(Rc::new(dm)); + } + + // at this point self.depmap is guaranteed to be Some(_) + Ok(self.depmap.as_ref().unwrap().clone()) + } + /// Get an existing task by its UUID pub fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { + let depmap = self.dependency_map(false)?; Ok(self .taskdb .get_task(uuid)? - .map(move |tm| Task::new(uuid, tm))) + .map(move |tm| Task::new(uuid, tm, depmap))) } /// Create a new task. @@ -107,7 +151,8 @@ impl Replica { let uuid = Uuid::new_v4(); self.add_undo_point(false)?; let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; - let mut task = Task::new(uuid, taskmap).into_mut(self); + let depmap = self.dependency_map(false)?; + let mut task = Task::new(uuid, taskmap, depmap).into_mut(self); task.set_description(description)?; task.set_status(status)?; task.set_entry(Some(Utc::now()))?; @@ -121,7 +166,8 @@ impl Replica { pub fn import_task_with_uuid(&mut self, uuid: Uuid) -> anyhow::Result { self.add_undo_point(false)?; let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; - Ok(Task::new(uuid, taskmap)) + let depmap = self.dependency_map(false)?; + Ok(Task::new(uuid, taskmap, depmap)) } /// Delete a task. The task must exist. Note that this is different from setting status to @@ -217,6 +263,7 @@ mod tests { use crate::task::Status; use chrono::TimeZone; use pretty_assertions::assert_eq; + use std::collections::HashSet; use uuid::Uuid; #[test] @@ -445,4 +492,67 @@ mod tests { assert!(t.get_description().starts_with("keeper")); } } + + #[test] + fn dependency_map() { + let mut rep = Replica::new_inmemory(); + + let mut tasks = vec![]; + for _ in 0..4 { + tasks.push(rep.new_task(Status::Pending, "t".into()).unwrap()); + } + + let uuids: Vec<_> = tasks.iter().map(|t| t.get_uuid()).collect(); + + // t[3] depends on t[2], and t[1] + { + let mut t = tasks.pop().unwrap().into_mut(&mut rep); + t.add_dependency(uuids[2]).unwrap(); + t.add_dependency(uuids[1]).unwrap(); + } + + // t[2] depends on t[0] + { + let mut t = tasks.pop().unwrap().into_mut(&mut rep); + t.add_dependency(uuids[0]).unwrap(); + } + + // t[1] depends on t[0] + { + let mut t = tasks.pop().unwrap().into_mut(&mut rep); + t.add_dependency(uuids[0]).unwrap(); + } + + // generate the dependency map, forcing an update based on the newly-added + // dependencies + let dm = rep.dependency_map(true).unwrap(); + + assert_eq!( + dm.dependencies(uuids[3]).collect::>(), + set![uuids[1], uuids[2]] + ); + assert_eq!( + dm.dependencies(uuids[2]).collect::>(), + set![uuids[0]] + ); + assert_eq!( + dm.dependencies(uuids[1]).collect::>(), + set![uuids[0]] + ); + assert_eq!(dm.dependencies(uuids[0]).collect::>(), set![]); + + assert_eq!(dm.dependents(uuids[3]).collect::>(), set![]); + assert_eq!( + dm.dependents(uuids[2]).collect::>(), + set![uuids[3]] + ); + assert_eq!( + dm.dependents(uuids[1]).collect::>(), + set![uuids[3]] + ); + assert_eq!( + dm.dependents(uuids[0]).collect::>(), + set![uuids[1], uuids[2]] + ); + } } diff --git a/taskchampion/src/task/tag.rs b/taskchampion/src/task/tag.rs index bb8361f80..a4f1e677c 100644 --- a/taskchampion/src/task/tag.rs +++ b/taskchampion/src/task/tag.rs @@ -134,6 +134,9 @@ pub(super) enum SyntheticTag { Pending, Completed, Deleted, + Blocked, + Unblocked, + Blocking, } #[cfg(test)] diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index fa8700a62..2a8464837 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -1,11 +1,13 @@ use super::tag::{SyntheticTag, TagInner}; use super::{Annotation, Priority, Status, Tag, Timestamp}; +use crate::depmap::DependencyMap; use crate::replica::Replica; use crate::storage::TaskMap; use chrono::prelude::*; use log::trace; use std::convert::AsRef; use std::convert::TryInto; +use std::rc::Rc; use std::str::FromStr; use uuid::Uuid; @@ -29,10 +31,18 @@ use uuid::Uuid; /// This struct contains only getters for various values on the task. The /// [`into_mut`](Task::into_mut) method /// returns a TaskMut which can be used to modify the task. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Task { uuid: Uuid, taskmap: TaskMap, + depmap: Rc, +} + +impl PartialEq for Task { + fn eq(&self, other: &Task) -> bool { + // compare only the taskmap and uuid; depmap is just present for reference + self.uuid == other.uuid && self.taskmap == other.taskmap + } } /// A mutable task, with setter methods. @@ -84,8 +94,12 @@ fn uda_tuple_to_string(namespace: impl AsRef, key: impl AsRef) -> Stri } impl Task { - pub(crate) fn new(uuid: Uuid, taskmap: TaskMap) -> Task { - Task { uuid, taskmap } + pub(crate) fn new(uuid: Uuid, taskmap: TaskMap, depmap: Rc) -> Task { + Task { + uuid, + taskmap, + depmap, + } } pub fn get_uuid(&self) -> Uuid { @@ -151,6 +165,16 @@ impl Task { self.taskmap.contains_key(Prop::Start.as_ref()) } + /// Determine whether this task is blocked -- that is, has at least one unresolved dependency. + pub fn is_blocked(&self) -> bool { + self.depmap.dependencies(self.uuid).next().is_some() + } + + /// Determine whether this task is blocking -- that is, has at least one unresolved dependent. + pub fn is_blocking(&self) -> bool { + self.depmap.dependents(self.uuid).next().is_some() + } + /// Determine whether a given synthetic tag is present on this task. All other /// synthetic tag calculations are based on this one. fn has_synthetic_tag(&self, synth: &SyntheticTag) -> bool { @@ -160,6 +184,9 @@ impl Task { SyntheticTag::Pending => self.get_status() == Status::Pending, SyntheticTag::Completed => self.get_status() == Status::Completed, SyntheticTag::Deleted => self.get_status() == Status::Deleted, + SyntheticTag::Blocked => self.is_blocked(), + SyntheticTag::Unblocked => !self.is_blocked(), + SyntheticTag::Blocking => self.is_blocking(), } } @@ -520,6 +547,11 @@ impl<'r> std::ops::Deref for TaskMut<'r> { mod test { use super::*; use pretty_assertions::assert_eq; + use std::collections::HashSet; + + fn dm() -> Rc { + Rc::new(DependencyMap::new()) + } fn with_mut_task(f: F) { let mut replica = Replica::new_inmemory(); @@ -540,7 +572,7 @@ mod test { #[test] fn test_is_active_never_started() { - let task = Task::new(Uuid::new_v4(), TaskMap::new()); + let task = Task::new(Uuid::new_v4(), TaskMap::new(), dm()); assert!(!task.is_active()); } @@ -551,6 +583,7 @@ mod test { vec![(String::from("start"), String::from("1234"))] .drain(..) .collect(), + dm(), ); assert!(task.is_active()); @@ -558,13 +591,13 @@ mod test { #[test] fn test_is_active_inactive() { - let task = Task::new(Uuid::new_v4(), Default::default()); + let task = Task::new(Uuid::new_v4(), Default::default(), dm()); assert!(!task.is_active()); } #[test] fn test_entry_not_set() { - let task = Task::new(Uuid::new_v4(), TaskMap::new()); + let task = Task::new(Uuid::new_v4(), TaskMap::new(), dm()); assert_eq!(task.get_entry(), None); } @@ -576,13 +609,14 @@ mod test { vec![(String::from("entry"), format!("{}", ts.timestamp()))] .drain(..) .collect(), + dm(), ); assert_eq!(task.get_entry(), Some(ts)); } #[test] fn test_wait_not_set() { - let task = Task::new(Uuid::new_v4(), TaskMap::new()); + let task = Task::new(Uuid::new_v4(), TaskMap::new(), dm()); assert!(!task.is_waiting()); assert_eq!(task.get_wait(), None); @@ -596,6 +630,7 @@ mod test { vec![(String::from("wait"), format!("{}", ts.timestamp()))] .drain(..) .collect(), + dm(), ); assert!(!task.is_waiting()); @@ -610,6 +645,7 @@ mod test { vec![(String::from("wait"), format!("{}", ts.timestamp()))] .drain(..) .collect(), + dm(), ); assert!(task.is_waiting()); @@ -626,6 +662,7 @@ mod test { ] .drain(..) .collect(), + dm(), ); assert!(task.has_tag(&utag("abc"))); @@ -647,17 +684,17 @@ mod test { ] .drain(..) .collect(), + dm(), ); - let mut tags: Vec<_> = task.get_tags().collect(); - tags.sort(); - let mut exp = vec![ + let tags: HashSet<_> = task.get_tags().collect(); + let exp = set![ utag("abc"), utag("def"), stag(SyntheticTag::Pending), stag(SyntheticTag::Waiting), + stag(SyntheticTag::Unblocked), ]; - exp.sort(); assert_eq!(tags, exp); } @@ -673,11 +710,19 @@ mod test { ] .drain(..) .collect(), + dm(), ); // only "ok" is OK - let tags: Vec<_> = task.get_tags().collect(); - assert_eq!(tags, vec![utag("ok"), stag(SyntheticTag::Pending)]); + let tags: HashSet<_> = task.get_tags().collect(); + assert_eq!( + tags, + set![ + utag("ok"), + stag(SyntheticTag::Pending), + stag(SyntheticTag::Unblocked) + ] + ); } #[test] @@ -698,6 +743,7 @@ mod test { ] .drain(..) .collect(), + dm(), ); let mut anns: Vec<_> = task.get_annotations().collect(); @@ -913,6 +959,7 @@ mod test { ] .drain(..) .collect(), + dm(), ); let mut udas: Vec<_> = task.get_udas().collect(); @@ -934,6 +981,7 @@ mod test { ] .drain(..) .collect(), + dm(), ); assert_eq!(task.get_uda("", "description"), None); // invalid UDA @@ -954,6 +1002,7 @@ mod test { ] .drain(..) .collect(), + dm(), ); assert_eq!(task.get_legacy_uda("description"), None); // invalid UDA @@ -1061,4 +1110,32 @@ mod test { assert_eq!(task.get_dependencies().collect::>(), vec![dep2]); }) } + + #[test] + fn dependencies_tags() { + let mut rep = Replica::new_inmemory(); + let uuid1; + let uuid2; + { + let t1 = rep.new_task(Status::Pending, "1".into()).unwrap(); + uuid1 = t1.get_uuid(); + let t2 = rep.new_task(Status::Pending, "2".into()).unwrap(); + uuid2 = t2.get_uuid(); + + let mut t1 = t1.into_mut(&mut rep); + t1.add_dependency(t2.get_uuid()).unwrap(); + } + + // force-refresh depmap + rep.dependency_map(true).unwrap(); + + let t1 = rep.get_task(uuid1).unwrap().unwrap(); + let t2 = rep.get_task(uuid2).unwrap().unwrap(); + assert!(t1.has_tag(&stag(SyntheticTag::Blocked))); + assert!(!t1.has_tag(&stag(SyntheticTag::Unblocked))); + assert!(!t1.has_tag(&stag(SyntheticTag::Blocking))); + assert!(!t2.has_tag(&stag(SyntheticTag::Blocked))); + assert!(t2.has_tag(&stag(SyntheticTag::Unblocked))); + assert!(t2.has_tag(&stag(SyntheticTag::Blocking))); + } } From 1b586a425f405a8e413f5a6522061476af98b1e1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 27 Mar 2022 17:48:47 -0400 Subject: [PATCH 542/548] Treat priority as an arbitrary string This matches what TaskWarrior does: priority is a UDA, and can be redefined by the user's local config. --- cli/src/invocation/cmd/import_tdb2.rs | 6 ++-- cli/src/invocation/cmd/import_tw.rs | 4 +-- taskchampion/src/lib.rs | 2 +- taskchampion/src/task/mod.rs | 2 -- taskchampion/src/task/priority.rs | 49 --------------------------- taskchampion/src/task/task.rs | 30 +++++++++++++--- 6 files changed, 31 insertions(+), 62 deletions(-) delete mode 100644 taskchampion/src/task/priority.rs diff --git a/cli/src/invocation/cmd/import_tdb2.rs b/cli/src/invocation/cmd/import_tdb2.rs index a01ae70fc..c3eb9ba5f 100644 --- a/cli/src/invocation/cmd/import_tdb2.rs +++ b/cli/src/invocation/cmd/import_tdb2.rs @@ -87,7 +87,7 @@ mod test { use pretty_assertions::assert_eq; use std::convert::TryInto; use taskchampion::chrono::{TimeZone, Utc}; - use taskchampion::{Priority, Status}; + use taskchampion::Status; use tempfile::TempDir; #[test] @@ -113,7 +113,7 @@ mod test { .unwrap(); assert_eq!(task.get_description(), "snake ðŸ"); assert_eq!(task.get_status(), Status::Pending); - assert_eq!(task.get_priority(), Priority::M); + assert_eq!(task.get_priority(), "M"); assert_eq!(task.get_wait(), None); assert_eq!( task.get_modified(), @@ -128,7 +128,7 @@ mod test { .unwrap(); assert_eq!(task.get_description(), "[TEST] foo"); assert_eq!(task.get_status(), Status::Completed); - assert_eq!(task.get_priority(), Priority::M); + assert_eq!(task.get_priority(), "M".to_string()); assert_eq!(task.get_wait(), None); assert_eq!( task.get_modified(), diff --git a/cli/src/invocation/cmd/import_tw.rs b/cli/src/invocation/cmd/import_tw.rs index fc5158b66..aaee30d90 100644 --- a/cli/src/invocation/cmd/import_tw.rs +++ b/cli/src/invocation/cmd/import_tw.rs @@ -156,7 +156,7 @@ mod test { use serde_json::json; use std::convert::TryInto; use taskchampion::chrono::{TimeZone, Utc}; - use taskchampion::{Priority, Status}; + use taskchampion::Status; #[test] fn stringify_string() { @@ -235,7 +235,7 @@ mod test { .unwrap(); assert_eq!(task.get_description(), "repair window"); assert_eq!(task.get_status(), Status::Completed); - assert_eq!(task.get_priority(), Priority::M); + assert_eq!(task.get_priority(), "M".to_string()); assert_eq!( task.get_wait(), Some(Utc.ymd(2021, 12, 25).and_hms(00, 15, 23)) diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 2e22b992e..63e5c20ea 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -63,7 +63,7 @@ pub use errors::Error; pub use replica::Replica; pub use server::{Server, ServerConfig}; pub use storage::StorageConfig; -pub use task::{Annotation, Priority, Status, Tag, Task, TaskMut}; +pub use task::{Annotation, Status, Tag, Task, TaskMut}; pub use workingset::WorkingSet; /// Re-exported type from the `uuid` crate, for ease of compatibility for consumers of this crate. diff --git a/taskchampion/src/task/mod.rs b/taskchampion/src/task/mod.rs index bd0808015..2e95b2a09 100644 --- a/taskchampion/src/task/mod.rs +++ b/taskchampion/src/task/mod.rs @@ -2,13 +2,11 @@ use chrono::prelude::*; mod annotation; -mod priority; mod status; mod tag; mod task; pub use annotation::Annotation; -pub use priority::Priority; pub use status::Status; pub use tag::Tag; pub use task::{Task, TaskMut}; diff --git a/taskchampion/src/task/priority.rs b/taskchampion/src/task/priority.rs deleted file mode 100644 index cbe786524..000000000 --- a/taskchampion/src/task/priority.rs +++ /dev/null @@ -1,49 +0,0 @@ -/// The priority of a task -#[derive(Debug, PartialEq)] -pub enum Priority { - /// Low - L, - /// Medium - M, - /// High - H, -} - -#[allow(dead_code)] -impl Priority { - /// Get a Priority from the 1-character value in a TaskMap, - /// defaulting to M - pub(crate) fn from_taskmap(s: &str) -> Priority { - match s { - "L" => Priority::L, - "M" => Priority::M, - "H" => Priority::H, - _ => Priority::M, - } - } - - /// Get the 1-character value for this priority to use in the TaskMap. - pub(crate) fn to_taskmap(&self) -> &str { - match self { - Priority::L => "L", - Priority::M => "M", - Priority::H => "H", - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn test_priority() { - assert_eq!(Priority::L.to_taskmap(), "L"); - assert_eq!(Priority::M.to_taskmap(), "M"); - assert_eq!(Priority::H.to_taskmap(), "H"); - assert_eq!(Priority::from_taskmap("L"), Priority::L); - assert_eq!(Priority::from_taskmap("M"), Priority::M); - assert_eq!(Priority::from_taskmap("H"), Priority::H); - } -} diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 2a8464837..a3f4384ac 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -1,5 +1,5 @@ use super::tag::{SyntheticTag, TagInner}; -use super::{Annotation, Priority, Status, Tag, Timestamp}; +use super::{Annotation, Status, Tag, Timestamp}; use crate::depmap::DependencyMap; use crate::replica::Replica; use crate::storage::TaskMap; @@ -66,6 +66,7 @@ enum Prop { Modified, Start, Status, + Priority, Wait, End, Entry, @@ -138,11 +139,11 @@ impl Task { self.get_timestamp(Prop::Entry.as_ref()) } - pub fn get_priority(&self) -> Priority { + pub fn get_priority(&self) -> &str { self.taskmap - .get(Prop::Status.as_ref()) - .map(|s| Priority::from_taskmap(s)) - .unwrap_or(Priority::M) + .get(Prop::Priority.as_ref()) + .map(|s| s.as_ref()) + .unwrap_or("") } /// Get the wait time. If this value is set, it will be returned, even @@ -347,6 +348,10 @@ impl<'r> TaskMut<'r> { self.set_string(Prop::Description.as_ref(), Some(description)) } + pub fn set_priority(&mut self, priority: String) -> anyhow::Result<()> { + self.set_string(Prop::Priority.as_ref(), Some(priority)) + } + pub fn set_entry(&mut self, entry: Option>) -> anyhow::Result<()> { self.set_timestamp(Prop::Entry.as_ref(), entry) } @@ -725,6 +730,12 @@ mod test { ); } + #[test] + fn test_get_priority_default() { + let task = Task::new(Uuid::new_v4(), TaskMap::new(), dm()); + assert_eq!(task.get_priority(), ""); + } + #[test] fn test_get_annotations() { let task = Task::new( @@ -810,6 +821,15 @@ mod test { }); } + #[test] + fn test_set_get_priority() { + with_mut_task(|mut task| { + assert_eq!(task.get_priority(), ""); + task.set_priority("H".into()).unwrap(); + assert_eq!(task.get_priority(), "H"); + }); + } + #[test] fn test_set_status_pending() { with_mut_task(|mut task| { From fd504b7d66e2684bd5d9e441c4cad4a1e4ffe775 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 23 Apr 2022 18:51:40 +0000 Subject: [PATCH 543/548] add num_local_operations to Replica --- taskchampion/src/replica.rs | 7 +++++++ taskchampion/src/storage/inmemory.rs | 4 ++++ taskchampion/src/storage/mod.rs | 4 ++++ taskchampion/src/storage/sqlite.rs | 9 +++++++++ taskchampion/src/taskdb/mod.rs | 6 ++++++ 5 files changed, 30 insertions(+) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 3787f3b25..4bbbc9c32 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -254,6 +254,11 @@ impl Replica { } Ok(()) } + + /// Get the number of operations local to this replica and not yet synchronized to the server. + pub fn num_local_operations(&mut self) -> anyhow::Result { + self.taskdb.num_operations() + } } #[cfg(test)] @@ -399,6 +404,8 @@ mod tests { }, ] ); + + assert_eq!(rep.num_local_operations().unwrap(), 10); } #[test] diff --git a/taskchampion/src/storage/inmemory.rs b/taskchampion/src/storage/inmemory.rs index 4a4463c19..bde13637b 100644 --- a/taskchampion/src/storage/inmemory.rs +++ b/taskchampion/src/storage/inmemory.rs @@ -91,6 +91,10 @@ impl<'t> StorageTxn for Txn<'t> { Ok(self.data_ref().operations.clone()) } + fn num_operations(&mut self) -> anyhow::Result { + Ok(self.data_ref().operations.len()) + } + fn add_operation(&mut self, op: ReplicaOp) -> anyhow::Result<()> { self.mut_data_ref().operations.push(op); Ok(()) diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index dd3c9786a..d577103e5 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -82,6 +82,10 @@ pub trait StorageTxn { /// server yet) fn operations(&mut self) -> Result>; + /// Get the current set of outstanding operations (operations that have not been sync'd to the + /// server yet) + fn num_operations(&mut self) -> Result; + /// Add an operation to the end of the list of operations in the storage. Note that this /// merely *stores* the operation; it is up to the TaskDb to apply it. fn add_operation(&mut self, op: ReplicaOp) -> Result<()>; diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 1f1fe239a..7e30931a4 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -257,6 +257,12 @@ impl<'t> StorageTxn for Txn<'t> { Ok(ret) } + fn num_operations(&mut self) -> anyhow::Result { + let t = self.get_txn()?; + let count: usize = t.query_row("SELECT count(*) FROM operations", [], |x| x.get(0))?; + Ok(count) + } + fn add_operation(&mut self, op: ReplicaOp) -> anyhow::Result<()> { let t = self.get_txn()?; @@ -627,6 +633,8 @@ mod test { ReplicaOp::Create { uuid: uuid2 }, ] ); + + assert_eq!(txn.num_operations()?, 2); } // set them to a different bunch @@ -678,6 +686,7 @@ mod test { }, ] ); + assert_eq!(txn.num_operations()?, 4); } Ok(()) } diff --git a/taskchampion/src/taskdb/mod.rs b/taskchampion/src/taskdb/mod.rs index 7e802313f..71404d968 100644 --- a/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/src/taskdb/mod.rs @@ -128,6 +128,12 @@ impl TaskDb { undo::undo(txn.as_mut()) } + /// Get the number of un-synchronized operations in storage. + pub fn num_operations(&mut self) -> anyhow::Result { + let mut txn = self.storage.txn().unwrap(); + txn.num_operations() + } + // functions for supporting tests #[cfg(test)] From 20823b7a738939b4bbfd24f6488d394b24b4d906 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 23 Apr 2022 19:19:14 +0000 Subject: [PATCH 544/548] expose Replica::num_local_operations via FFI --- integration-tests/src/bindings_tests/replica.c | 2 ++ lib/src/replica.rs | 13 +++++++++++++ lib/taskchampion.h | 5 +++++ 3 files changed, 20 insertions(+) diff --git a/integration-tests/src/bindings_tests/replica.c b/integration-tests/src/bindings_tests/replica.c index 88651fc45..639cf5f48 100644 --- a/integration-tests/src/bindings_tests/replica.c +++ b/integration-tests/src/bindings_tests/replica.c @@ -109,6 +109,8 @@ static void test_replica_working_set(void) { tc_working_set_free(ws); + TEST_ASSERT_EQUAL(19, tc_replica_num_local_operations(rep)); + tc_replica_free(rep); } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 6d9683959..9ad5f29db 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -360,6 +360,19 @@ pub unsafe extern "C" fn tc_replica_undo(rep: *mut TCReplica, undone_out: *mut i ) } +/// Get the number of local, un-synchronized operations, or -1 on error +#[no_mangle] +pub unsafe extern "C" fn tc_replica_num_local_operations(rep: *mut TCReplica) -> i64 { + wrap( + rep, + |rep| { + let count = rep.num_local_operations()? as i64; + Ok(count) + }, + -1, + ) +} + /// 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 diff --git a/lib/taskchampion.h b/lib/taskchampion.h index a4ec0061e..c5a1eddf2 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -557,6 +557,11 @@ TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool av */ TCResult tc_replica_undo(struct TCReplica *rep, int32_t *undone_out); +/** + * Get the number of local, un-synchronized operations, or -1 on error + */ +int64_t tc_replica_num_local_operations(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 From ae3851f5a6293ab5889cc56f67505c57408a6e90 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 9 Apr 2022 20:15:00 +0000 Subject: [PATCH 545/548] export taskchampion-lib as an rlib, too --- README.md | 9 ++++++++- lib/Cargo.toml | 2 +- lib/src/lib.rs | 12 ++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3622cc50e..bc076c792 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,9 @@ To regenerate this file, run `cargo xtask codegen`. ## C libraries +NOTE: support for linking against taskchampion is a work in progress. +Contributions and pointers to best practices are appreciated! + The `taskchampion-lib` crate generates libraries suitable for use from C (or any C-compatible language). The necessary bits are: @@ -44,8 +47,12 @@ Downstream consumers may use either the static or dynamic library, as they prefe NOTE: on Windows, the "BCrypt" library must be included when linking to taskchampion. +### As a Rust dependency + +If you would prefer to build Taskchampion directly into your project, and have a build system capable of building Rust libraries (such as CMake), the `taskchampion-lib` crate can be referenced as an `rlib` dependency. + ## Documentation Generation The `mdbook` configuration contains a "preprocessor" implemented in the `taskchampion-cli` crate in order to reflect CLI usage information into the generated book. -Tihs preprocessor is not built by default. +This preprocessor is not built by default. To (re)build it, run `cargo build -p taskchampion-cli --features usage-docs --bin usage-docs`. diff --git a/lib/Cargo.toml b/lib/Cargo.toml index af920e560..6f023e859 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" [lib] name = "taskchampion" -crate-type = ["staticlib", "cdylib"] +crate-type = ["staticlib", "cdylib", "rlib"] [dependencies] libc = "0.2.113" diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 58ab5e6b1..69b83d64b 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -17,17 +17,29 @@ mod traits; mod util; pub mod annotation; +pub use annotation::*; pub mod atomic; +pub use atomic::*; pub mod kv; +pub use kv::*; pub mod replica; +pub use replica::*; pub mod result; +pub use result::*; pub mod server; +pub use server::*; pub mod status; +pub use status::*; pub mod string; +pub use string::*; pub mod task; +pub use task::*; pub mod uda; +pub use uda::*; pub mod uuid; +pub use uuid::*; pub mod workingset; +pub use workingset::*; pub(crate) mod types { pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList}; From 716a558ba2dfe19c22e122476b67bdd544d2cd06 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 24 Apr 2022 22:49:23 +0000 Subject: [PATCH 546/548] ignore a C code example --- lib/src/string.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/string.rs b/lib/src/string.rs index 65c9dfd9e..de090116b 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -411,7 +411,7 @@ impl CList for TCStringList { /// /// 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 From ac172b100830a2eabb93762100fd04b925132421 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 24 Apr 2022 22:48:48 +0000 Subject: [PATCH 547/548] Name the C library differently from the crate --- integration-tests/build.rs | 2 +- lib/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/build.rs b/integration-tests/build.rs index 90165f6d0..face5701d 100644 --- a/integration-tests/build.rs +++ b/integration-tests/build.rs @@ -18,7 +18,7 @@ fn link_libtaskchampion() { let libtc_dir = libtc_dir.to_str().expect("path is valid utf-8"); println!("cargo:rustc-link-search={}", libtc_dir); - println!("cargo:rustc-link-lib=dylib=taskchampion"); + println!("cargo:rustc-link-lib=dylib=taskchampionlib"); // on windows, it appears that rust std requires BCrypt if cfg!(target_os = "windows") { diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 6f023e859..bfc8cc829 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2018" [lib] -name = "taskchampion" +name = "taskchampionlib" crate-type = ["staticlib", "cdylib", "rlib"] [dependencies] From 2a92b2a4b93714dffe838b732c85da4b353c27d4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 8 May 2022 19:01:20 +0000 Subject: [PATCH 548/548] move contents of taskchampion repo to tc/ --- {.cargo => rust/.cargo}/audit.toml | 0 {.cargo => rust/.cargo}/config | 0 {.changelogs => rust/.changelogs}/.gitignore | 0 .../.changelogs}/2021-10-03-server-storage.md | 0 .../.changelogs}/2021-10-11-issue23-client.md | 0 .../.changelogs}/2021-10-16-issue299.md | 0 .../.changelogs}/2021-10-25-issue23-integration.md | 0 {.github => rust/.github}/CODEOWNERS | 0 {.github => rust/.github}/dependabot.yml | 0 {.github => rust/.github}/workflows/audit.yml | 0 {.github => rust/.github}/workflows/checks.yml | 0 .../.github}/workflows/publish-docs.yml | 0 {.github => rust/.github}/workflows/tests.yml | 0 .gitignore => rust/.gitignore | 0 CHANGELOG.md => rust/CHANGELOG.md | 0 CODE_OF_CONDUCT.md => rust/CODE_OF_CONDUCT.md | 0 CONTRIBUTING.md => rust/CONTRIBUTING.md | 0 Cargo.lock => rust/Cargo.lock | 0 Cargo.toml => rust/Cargo.toml | 0 LICENSE => rust/LICENSE | 0 POLICY.md => rust/POLICY.md | 0 README.md => rust/README.md | 0 RELEASING.md => rust/RELEASING.md | 0 SECURITY.md => rust/SECURITY.md | 0 build-docs.sh => rust/build-docs.sh | 0 {cli => rust/cli}/Cargo.toml | 0 {cli => rust/cli}/build.rs | 0 {cli => rust/cli}/src/argparse/args/arg_matching.rs | 0 {cli => rust/cli}/src/argparse/args/colon.rs | 0 {cli => rust/cli}/src/argparse/args/idlist.rs | 0 {cli => rust/cli}/src/argparse/args/misc.rs | 0 {cli => rust/cli}/src/argparse/args/mod.rs | 0 {cli => rust/cli}/src/argparse/args/tags.rs | 0 {cli => rust/cli}/src/argparse/args/time.rs | 0 {cli => rust/cli}/src/argparse/command.rs | 0 {cli => rust/cli}/src/argparse/config.rs | 0 {cli => rust/cli}/src/argparse/filter.rs | 0 {cli => rust/cli}/src/argparse/mod.rs | 0 {cli => rust/cli}/src/argparse/modification.rs | 0 {cli => rust/cli}/src/argparse/subcommand.rs | 0 {cli => rust/cli}/src/bin/ta.rs | 0 {cli => rust/cli}/src/bin/usage-docs.rs | 0 {cli => rust/cli}/src/errors.rs | 0 {cli => rust/cli}/src/invocation/cmd/add.rs | 0 {cli => rust/cli}/src/invocation/cmd/completed.data | 0 {cli => rust/cli}/src/invocation/cmd/config.rs | 0 {cli => rust/cli}/src/invocation/cmd/gc.rs | 0 {cli => rust/cli}/src/invocation/cmd/help.rs | 0 {cli => rust/cli}/src/invocation/cmd/import_tdb2.rs | 0 {cli => rust/cli}/src/invocation/cmd/import_tw.rs | 0 {cli => rust/cli}/src/invocation/cmd/info.rs | 0 {cli => rust/cli}/src/invocation/cmd/mod.rs | 0 {cli => rust/cli}/src/invocation/cmd/modify.rs | 0 {cli => rust/cli}/src/invocation/cmd/pending.data | 0 {cli => rust/cli}/src/invocation/cmd/report.rs | 0 {cli => rust/cli}/src/invocation/cmd/sync.rs | 0 {cli => rust/cli}/src/invocation/cmd/undo.rs | 0 {cli => rust/cli}/src/invocation/cmd/version.rs | 0 {cli => rust/cli}/src/invocation/filter.rs | 0 {cli => rust/cli}/src/invocation/mod.rs | 0 {cli => rust/cli}/src/invocation/modify.rs | 0 {cli => rust/cli}/src/invocation/report.rs | 0 {cli => rust/cli}/src/invocation/test.rs | 0 {cli => rust/cli}/src/invocation/util.rs | 0 {cli => rust/cli}/src/lib.rs | 0 {cli => rust/cli}/src/macros.rs | 0 {cli => rust/cli}/src/settings/mod.rs | 0 {cli => rust/cli}/src/settings/report.rs | 0 {cli => rust/cli}/src/settings/settings.rs | 0 {cli => rust/cli}/src/settings/util.rs | 0 {cli => rust/cli}/src/table.rs | 0 {cli => rust/cli}/src/tdb2/mod.rs | 0 {cli => rust/cli}/src/tdb2/test.data | 0 {cli => rust/cli}/src/usage.rs | 0 {cli => rust/cli}/tests/cli.rs | 0 {docs => rust/docs}/.gitignore | 0 {docs => rust/docs}/README.md | 0 {docs => rust/docs}/assets/cgi/LICENSE.md | 0 .../assets/cgi/icon_rounded/icon_rounded_1024.png | Bin .../assets/cgi/icon_rounded/icon_rounded_128.png | Bin .../assets/cgi/icon_rounded/icon_rounded_16.png | Bin .../assets/cgi/icon_rounded/icon_rounded_256.png | Bin .../assets/cgi/icon_rounded/icon_rounded_32.png | Bin .../assets/cgi/icon_rounded/icon_rounded_512.png | Bin .../assets/cgi/icon_rounded/icon_rounded_64.png | Bin .../assets/cgi/icon_square/icon_square_1024.png | Bin .../assets/cgi/icon_square/icon_square_128.png | Bin .../docs}/assets/cgi/icon_square/icon_square_16.png | Bin .../assets/cgi/icon_square/icon_square_256.png | Bin .../docs}/assets/cgi/icon_square/icon_square_32.png | Bin .../assets/cgi/icon_square/icon_square_512.png | Bin .../docs}/assets/cgi/icon_square/icon_square_64.png | Bin {docs => rust/docs}/assets/cgi/logo/logo_1024.png | Bin {docs => rust/docs}/assets/cgi/logo/logo_128.png | Bin {docs => rust/docs}/assets/cgi/logo/logo_16.png | Bin {docs => rust/docs}/assets/cgi/logo/logo_256.png | Bin {docs => rust/docs}/assets/cgi/logo/logo_32.png | Bin {docs => rust/docs}/assets/cgi/logo/logo_512.png | Bin {docs => rust/docs}/assets/cgi/logo/logo_64.png | Bin {docs => rust/docs}/book.toml | 0 {docs => rust/docs}/src/SUMMARY.md | 0 {docs => rust/docs}/src/config-file.md | 0 {docs => rust/docs}/src/data-model.md | 0 {docs => rust/docs}/src/environment.md | 0 {docs => rust/docs}/src/filters.md | 0 {docs => rust/docs}/src/images/name_timestamp.png | Bin {docs => rust/docs}/src/installation.md | 0 {docs => rust/docs}/src/internals.md | 0 {docs => rust/docs}/src/modifications.md | 0 {docs => rust/docs}/src/plans.md | 0 {docs => rust/docs}/src/reports.md | 0 {docs => rust/docs}/src/running-sync-server.md | 0 {docs => rust/docs}/src/snapshots.md | 0 {docs => rust/docs}/src/storage.md | 0 {docs => rust/docs}/src/sync-model.md | 0 {docs => rust/docs}/src/sync-protocol.md | 0 {docs => rust/docs}/src/sync.md | 0 {docs => rust/docs}/src/tags.md | 0 {docs => rust/docs}/src/task-sync.md | 0 {docs => rust/docs}/src/taskdb.md | 0 {docs => rust/docs}/src/tasks.md | 0 {docs => rust/docs}/src/time.md | 0 {docs => rust/docs}/src/undo.md | 0 {docs => rust/docs}/src/using-task-command.md | 0 {docs => rust/docs}/src/welcome.md | 0 .../integration-tests}/.gitignore | 0 .../integration-tests}/Cargo.toml | 0 .../integration-tests}/README.md | 0 .../integration-tests}/build.rs | 0 .../integration-tests}/src/bindings_tests/mod.rs | 0 .../integration-tests}/src/bindings_tests/replica.c | 0 .../integration-tests}/src/bindings_tests/string.c | 0 .../integration-tests}/src/bindings_tests/task.c | 0 .../integration-tests}/src/bindings_tests/test.c | 0 .../src/bindings_tests/unity/LICENSE.txt | 0 .../src/bindings_tests/unity/README.md | 0 .../src/bindings_tests/unity/unity.c | 0 .../src/bindings_tests/unity/unity.h | 0 .../src/bindings_tests/unity/unity_internals.h | 0 .../integration-tests}/src/bindings_tests/uuid.c | 0 .../integration-tests}/src/lib.rs | 0 .../integration-tests}/tests/bindings.rs | 0 .../integration-tests}/tests/cross-sync.rs | 0 .../integration-tests}/tests/snapshots.rs | 0 .../tests/update-and-delete-sync.rs | 0 {lib => rust/lib}/Cargo.toml | 0 {lib => rust/lib}/Makefile | 0 {lib => rust/lib}/header-intro.h | 0 {lib => rust/lib}/src/annotation.rs | 0 {lib => rust/lib}/src/atomic.rs | 0 {lib => rust/lib}/src/kv.rs | 0 {lib => rust/lib}/src/lib.rs | 0 {lib => rust/lib}/src/replica.rs | 0 {lib => rust/lib}/src/result.rs | 0 {lib => rust/lib}/src/server.rs | 0 {lib => rust/lib}/src/status.rs | 0 {lib => rust/lib}/src/string.rs | 0 {lib => rust/lib}/src/task.rs | 0 {lib => rust/lib}/src/traits.rs | 0 {lib => rust/lib}/src/uda.rs | 0 {lib => rust/lib}/src/util.rs | 0 {lib => rust/lib}/src/uuid.rs | 0 {lib => rust/lib}/src/workingset.rs | 0 {lib => rust/lib}/taskchampion.h | 0 {scripts => rust/scripts}/changelog.py | 0 {sync-server => rust/sync-server}/Cargo.toml | 0 .../sync-server}/src/api/add_snapshot.rs | 0 .../sync-server}/src/api/add_version.rs | 0 .../sync-server}/src/api/get_child_version.rs | 0 .../sync-server}/src/api/get_snapshot.rs | 0 {sync-server => rust/sync-server}/src/api/mod.rs | 0 .../src/bin/taskchampion-sync-server.rs | 0 {sync-server => rust/sync-server}/src/lib.rs | 0 {sync-server => rust/sync-server}/src/server.rs | 0 .../sync-server}/src/storage/inmemory.rs | 0 .../sync-server}/src/storage/mod.rs | 0 .../sync-server}/src/storage/sqlite.rs | 0 {taskchampion => rust/taskchampion}/Cargo.toml | 0 {taskchampion => rust/taskchampion}/src/depmap.rs | 0 {taskchampion => rust/taskchampion}/src/errors.rs | 0 {taskchampion => rust/taskchampion}/src/lib.rs | 0 {taskchampion => rust/taskchampion}/src/macros.rs | 0 {taskchampion => rust/taskchampion}/src/replica.rs | 0 .../taskchampion}/src/server/config.rs | 0 .../taskchampion}/src/server/crypto.rs | 0 .../taskchampion}/src/server/generate-test-data.py | 0 .../taskchampion}/src/server/local.rs | 0 .../taskchampion}/src/server/mod.rs | 0 .../taskchampion}/src/server/op.rs | 0 .../taskchampion}/src/server/remote/mod.rs | 0 .../taskchampion}/src/server/test-bad-app-id.data | 0 .../src/server/test-bad-client-key.data | 0 .../taskchampion}/src/server/test-bad-secret.data | 0 .../src/server/test-bad-version-id.data | 0 .../taskchampion}/src/server/test-bad-version.data | 0 .../src/server/test-bad-version_id.data | 0 .../taskchampion}/src/server/test-good.data | 0 .../taskchampion}/src/server/test.rs | 0 .../taskchampion}/src/server/types.rs | 0 .../taskchampion}/src/storage/config.rs | 0 .../taskchampion}/src/storage/inmemory.rs | 0 .../taskchampion}/src/storage/mod.rs | 0 .../taskchampion}/src/storage/op.rs | 0 .../taskchampion}/src/storage/sqlite.rs | 0 .../taskchampion}/src/task/annotation.rs | 0 {taskchampion => rust/taskchampion}/src/task/mod.rs | 0 .../taskchampion}/src/task/status.rs | 0 {taskchampion => rust/taskchampion}/src/task/tag.rs | 0 .../taskchampion}/src/task/task.rs | 0 .../taskchampion}/src/taskdb/apply.rs | 0 .../taskchampion}/src/taskdb/mod.rs | 0 .../taskchampion}/src/taskdb/snapshot.rs | 0 .../taskchampion}/src/taskdb/sync.rs | 0 .../taskchampion}/src/taskdb/undo.rs | 0 .../taskchampion}/src/taskdb/working_set.rs | 0 {taskchampion => rust/taskchampion}/src/utils.rs | 0 .../taskchampion}/src/workingset.rs | 0 {xtask => rust/xtask}/Cargo.toml | 0 {xtask => rust/xtask}/src/main.rs | 0 219 files changed, 0 insertions(+), 0 deletions(-) rename {.cargo => rust/.cargo}/audit.toml (100%) rename {.cargo => rust/.cargo}/config (100%) rename {.changelogs => rust/.changelogs}/.gitignore (100%) rename {.changelogs => rust/.changelogs}/2021-10-03-server-storage.md (100%) rename {.changelogs => rust/.changelogs}/2021-10-11-issue23-client.md (100%) rename {.changelogs => rust/.changelogs}/2021-10-16-issue299.md (100%) rename {.changelogs => rust/.changelogs}/2021-10-25-issue23-integration.md (100%) rename {.github => rust/.github}/CODEOWNERS (100%) rename {.github => rust/.github}/dependabot.yml (100%) rename {.github => rust/.github}/workflows/audit.yml (100%) rename {.github => rust/.github}/workflows/checks.yml (100%) rename {.github => rust/.github}/workflows/publish-docs.yml (100%) rename {.github => rust/.github}/workflows/tests.yml (100%) rename .gitignore => rust/.gitignore (100%) rename CHANGELOG.md => rust/CHANGELOG.md (100%) rename CODE_OF_CONDUCT.md => rust/CODE_OF_CONDUCT.md (100%) rename CONTRIBUTING.md => rust/CONTRIBUTING.md (100%) rename Cargo.lock => rust/Cargo.lock (100%) rename Cargo.toml => rust/Cargo.toml (100%) rename LICENSE => rust/LICENSE (100%) rename POLICY.md => rust/POLICY.md (100%) rename README.md => rust/README.md (100%) rename RELEASING.md => rust/RELEASING.md (100%) rename SECURITY.md => rust/SECURITY.md (100%) rename build-docs.sh => rust/build-docs.sh (100%) rename {cli => rust/cli}/Cargo.toml (100%) rename {cli => rust/cli}/build.rs (100%) rename {cli => rust/cli}/src/argparse/args/arg_matching.rs (100%) rename {cli => rust/cli}/src/argparse/args/colon.rs (100%) rename {cli => rust/cli}/src/argparse/args/idlist.rs (100%) rename {cli => rust/cli}/src/argparse/args/misc.rs (100%) rename {cli => rust/cli}/src/argparse/args/mod.rs (100%) rename {cli => rust/cli}/src/argparse/args/tags.rs (100%) rename {cli => rust/cli}/src/argparse/args/time.rs (100%) rename {cli => rust/cli}/src/argparse/command.rs (100%) rename {cli => rust/cli}/src/argparse/config.rs (100%) rename {cli => rust/cli}/src/argparse/filter.rs (100%) rename {cli => rust/cli}/src/argparse/mod.rs (100%) rename {cli => rust/cli}/src/argparse/modification.rs (100%) rename {cli => rust/cli}/src/argparse/subcommand.rs (100%) rename {cli => rust/cli}/src/bin/ta.rs (100%) rename {cli => rust/cli}/src/bin/usage-docs.rs (100%) rename {cli => rust/cli}/src/errors.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/add.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/completed.data (100%) rename {cli => rust/cli}/src/invocation/cmd/config.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/gc.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/help.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/import_tdb2.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/import_tw.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/info.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/mod.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/modify.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/pending.data (100%) rename {cli => rust/cli}/src/invocation/cmd/report.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/sync.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/undo.rs (100%) rename {cli => rust/cli}/src/invocation/cmd/version.rs (100%) rename {cli => rust/cli}/src/invocation/filter.rs (100%) rename {cli => rust/cli}/src/invocation/mod.rs (100%) rename {cli => rust/cli}/src/invocation/modify.rs (100%) rename {cli => rust/cli}/src/invocation/report.rs (100%) rename {cli => rust/cli}/src/invocation/test.rs (100%) rename {cli => rust/cli}/src/invocation/util.rs (100%) rename {cli => rust/cli}/src/lib.rs (100%) rename {cli => rust/cli}/src/macros.rs (100%) rename {cli => rust/cli}/src/settings/mod.rs (100%) rename {cli => rust/cli}/src/settings/report.rs (100%) rename {cli => rust/cli}/src/settings/settings.rs (100%) rename {cli => rust/cli}/src/settings/util.rs (100%) rename {cli => rust/cli}/src/table.rs (100%) rename {cli => rust/cli}/src/tdb2/mod.rs (100%) rename {cli => rust/cli}/src/tdb2/test.data (100%) rename {cli => rust/cli}/src/usage.rs (100%) rename {cli => rust/cli}/tests/cli.rs (100%) rename {docs => rust/docs}/.gitignore (100%) rename {docs => rust/docs}/README.md (100%) rename {docs => rust/docs}/assets/cgi/LICENSE.md (100%) rename {docs => rust/docs}/assets/cgi/icon_rounded/icon_rounded_1024.png (100%) rename {docs => rust/docs}/assets/cgi/icon_rounded/icon_rounded_128.png (100%) rename {docs => rust/docs}/assets/cgi/icon_rounded/icon_rounded_16.png (100%) rename {docs => rust/docs}/assets/cgi/icon_rounded/icon_rounded_256.png (100%) rename {docs => rust/docs}/assets/cgi/icon_rounded/icon_rounded_32.png (100%) rename {docs => rust/docs}/assets/cgi/icon_rounded/icon_rounded_512.png (100%) rename {docs => rust/docs}/assets/cgi/icon_rounded/icon_rounded_64.png (100%) rename {docs => rust/docs}/assets/cgi/icon_square/icon_square_1024.png (100%) rename {docs => rust/docs}/assets/cgi/icon_square/icon_square_128.png (100%) rename {docs => rust/docs}/assets/cgi/icon_square/icon_square_16.png (100%) rename {docs => rust/docs}/assets/cgi/icon_square/icon_square_256.png (100%) rename {docs => rust/docs}/assets/cgi/icon_square/icon_square_32.png (100%) rename {docs => rust/docs}/assets/cgi/icon_square/icon_square_512.png (100%) rename {docs => rust/docs}/assets/cgi/icon_square/icon_square_64.png (100%) rename {docs => rust/docs}/assets/cgi/logo/logo_1024.png (100%) rename {docs => rust/docs}/assets/cgi/logo/logo_128.png (100%) rename {docs => rust/docs}/assets/cgi/logo/logo_16.png (100%) rename {docs => rust/docs}/assets/cgi/logo/logo_256.png (100%) rename {docs => rust/docs}/assets/cgi/logo/logo_32.png (100%) rename {docs => rust/docs}/assets/cgi/logo/logo_512.png (100%) rename {docs => rust/docs}/assets/cgi/logo/logo_64.png (100%) rename {docs => rust/docs}/book.toml (100%) rename {docs => rust/docs}/src/SUMMARY.md (100%) rename {docs => rust/docs}/src/config-file.md (100%) rename {docs => rust/docs}/src/data-model.md (100%) rename {docs => rust/docs}/src/environment.md (100%) rename {docs => rust/docs}/src/filters.md (100%) rename {docs => rust/docs}/src/images/name_timestamp.png (100%) rename {docs => rust/docs}/src/installation.md (100%) rename {docs => rust/docs}/src/internals.md (100%) rename {docs => rust/docs}/src/modifications.md (100%) rename {docs => rust/docs}/src/plans.md (100%) rename {docs => rust/docs}/src/reports.md (100%) rename {docs => rust/docs}/src/running-sync-server.md (100%) rename {docs => rust/docs}/src/snapshots.md (100%) rename {docs => rust/docs}/src/storage.md (100%) rename {docs => rust/docs}/src/sync-model.md (100%) rename {docs => rust/docs}/src/sync-protocol.md (100%) rename {docs => rust/docs}/src/sync.md (100%) rename {docs => rust/docs}/src/tags.md (100%) rename {docs => rust/docs}/src/task-sync.md (100%) rename {docs => rust/docs}/src/taskdb.md (100%) rename {docs => rust/docs}/src/tasks.md (100%) rename {docs => rust/docs}/src/time.md (100%) rename {docs => rust/docs}/src/undo.md (100%) rename {docs => rust/docs}/src/using-task-command.md (100%) rename {docs => rust/docs}/src/welcome.md (100%) rename {integration-tests => rust/integration-tests}/.gitignore (100%) rename {integration-tests => rust/integration-tests}/Cargo.toml (100%) rename {integration-tests => rust/integration-tests}/README.md (100%) rename {integration-tests => rust/integration-tests}/build.rs (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/mod.rs (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/replica.c (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/string.c (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/task.c (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/test.c (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/unity/LICENSE.txt (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/unity/README.md (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/unity/unity.c (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/unity/unity.h (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/unity/unity_internals.h (100%) rename {integration-tests => rust/integration-tests}/src/bindings_tests/uuid.c (100%) rename {integration-tests => rust/integration-tests}/src/lib.rs (100%) rename {integration-tests => rust/integration-tests}/tests/bindings.rs (100%) rename {integration-tests => rust/integration-tests}/tests/cross-sync.rs (100%) rename {integration-tests => rust/integration-tests}/tests/snapshots.rs (100%) rename {integration-tests => rust/integration-tests}/tests/update-and-delete-sync.rs (100%) rename {lib => rust/lib}/Cargo.toml (100%) rename {lib => rust/lib}/Makefile (100%) rename {lib => rust/lib}/header-intro.h (100%) rename {lib => rust/lib}/src/annotation.rs (100%) rename {lib => rust/lib}/src/atomic.rs (100%) rename {lib => rust/lib}/src/kv.rs (100%) rename {lib => rust/lib}/src/lib.rs (100%) rename {lib => rust/lib}/src/replica.rs (100%) rename {lib => rust/lib}/src/result.rs (100%) rename {lib => rust/lib}/src/server.rs (100%) rename {lib => rust/lib}/src/status.rs (100%) rename {lib => rust/lib}/src/string.rs (100%) rename {lib => rust/lib}/src/task.rs (100%) rename {lib => rust/lib}/src/traits.rs (100%) rename {lib => rust/lib}/src/uda.rs (100%) rename {lib => rust/lib}/src/util.rs (100%) rename {lib => rust/lib}/src/uuid.rs (100%) rename {lib => rust/lib}/src/workingset.rs (100%) rename {lib => rust/lib}/taskchampion.h (100%) rename {scripts => rust/scripts}/changelog.py (100%) rename {sync-server => rust/sync-server}/Cargo.toml (100%) rename {sync-server => rust/sync-server}/src/api/add_snapshot.rs (100%) rename {sync-server => rust/sync-server}/src/api/add_version.rs (100%) rename {sync-server => rust/sync-server}/src/api/get_child_version.rs (100%) rename {sync-server => rust/sync-server}/src/api/get_snapshot.rs (100%) rename {sync-server => rust/sync-server}/src/api/mod.rs (100%) rename {sync-server => rust/sync-server}/src/bin/taskchampion-sync-server.rs (100%) rename {sync-server => rust/sync-server}/src/lib.rs (100%) rename {sync-server => rust/sync-server}/src/server.rs (100%) rename {sync-server => rust/sync-server}/src/storage/inmemory.rs (100%) rename {sync-server => rust/sync-server}/src/storage/mod.rs (100%) rename {sync-server => rust/sync-server}/src/storage/sqlite.rs (100%) rename {taskchampion => rust/taskchampion}/Cargo.toml (100%) rename {taskchampion => rust/taskchampion}/src/depmap.rs (100%) rename {taskchampion => rust/taskchampion}/src/errors.rs (100%) rename {taskchampion => rust/taskchampion}/src/lib.rs (100%) rename {taskchampion => rust/taskchampion}/src/macros.rs (100%) rename {taskchampion => rust/taskchampion}/src/replica.rs (100%) rename {taskchampion => rust/taskchampion}/src/server/config.rs (100%) rename {taskchampion => rust/taskchampion}/src/server/crypto.rs (100%) rename {taskchampion => rust/taskchampion}/src/server/generate-test-data.py (100%) rename {taskchampion => rust/taskchampion}/src/server/local.rs (100%) rename {taskchampion => rust/taskchampion}/src/server/mod.rs (100%) rename {taskchampion => rust/taskchampion}/src/server/op.rs (100%) rename {taskchampion => rust/taskchampion}/src/server/remote/mod.rs (100%) rename {taskchampion => rust/taskchampion}/src/server/test-bad-app-id.data (100%) rename {taskchampion => rust/taskchampion}/src/server/test-bad-client-key.data (100%) rename {taskchampion => rust/taskchampion}/src/server/test-bad-secret.data (100%) rename {taskchampion => rust/taskchampion}/src/server/test-bad-version-id.data (100%) rename {taskchampion => rust/taskchampion}/src/server/test-bad-version.data (100%) rename {taskchampion => rust/taskchampion}/src/server/test-bad-version_id.data (100%) rename {taskchampion => rust/taskchampion}/src/server/test-good.data (100%) rename {taskchampion => rust/taskchampion}/src/server/test.rs (100%) rename {taskchampion => rust/taskchampion}/src/server/types.rs (100%) rename {taskchampion => rust/taskchampion}/src/storage/config.rs (100%) rename {taskchampion => rust/taskchampion}/src/storage/inmemory.rs (100%) rename {taskchampion => rust/taskchampion}/src/storage/mod.rs (100%) rename {taskchampion => rust/taskchampion}/src/storage/op.rs (100%) rename {taskchampion => rust/taskchampion}/src/storage/sqlite.rs (100%) rename {taskchampion => rust/taskchampion}/src/task/annotation.rs (100%) rename {taskchampion => rust/taskchampion}/src/task/mod.rs (100%) rename {taskchampion => rust/taskchampion}/src/task/status.rs (100%) rename {taskchampion => rust/taskchampion}/src/task/tag.rs (100%) rename {taskchampion => rust/taskchampion}/src/task/task.rs (100%) rename {taskchampion => rust/taskchampion}/src/taskdb/apply.rs (100%) rename {taskchampion => rust/taskchampion}/src/taskdb/mod.rs (100%) rename {taskchampion => rust/taskchampion}/src/taskdb/snapshot.rs (100%) rename {taskchampion => rust/taskchampion}/src/taskdb/sync.rs (100%) rename {taskchampion => rust/taskchampion}/src/taskdb/undo.rs (100%) rename {taskchampion => rust/taskchampion}/src/taskdb/working_set.rs (100%) rename {taskchampion => rust/taskchampion}/src/utils.rs (100%) rename {taskchampion => rust/taskchampion}/src/workingset.rs (100%) rename {xtask => rust/xtask}/Cargo.toml (100%) rename {xtask => rust/xtask}/src/main.rs (100%) diff --git a/.cargo/audit.toml b/rust/.cargo/audit.toml similarity index 100% rename from .cargo/audit.toml rename to rust/.cargo/audit.toml diff --git a/.cargo/config b/rust/.cargo/config similarity index 100% rename from .cargo/config rename to rust/.cargo/config diff --git a/.changelogs/.gitignore b/rust/.changelogs/.gitignore similarity index 100% rename from .changelogs/.gitignore rename to rust/.changelogs/.gitignore diff --git a/.changelogs/2021-10-03-server-storage.md b/rust/.changelogs/2021-10-03-server-storage.md similarity index 100% rename from .changelogs/2021-10-03-server-storage.md rename to rust/.changelogs/2021-10-03-server-storage.md diff --git a/.changelogs/2021-10-11-issue23-client.md b/rust/.changelogs/2021-10-11-issue23-client.md similarity index 100% rename from .changelogs/2021-10-11-issue23-client.md rename to rust/.changelogs/2021-10-11-issue23-client.md diff --git a/.changelogs/2021-10-16-issue299.md b/rust/.changelogs/2021-10-16-issue299.md similarity index 100% rename from .changelogs/2021-10-16-issue299.md rename to rust/.changelogs/2021-10-16-issue299.md diff --git a/.changelogs/2021-10-25-issue23-integration.md b/rust/.changelogs/2021-10-25-issue23-integration.md similarity index 100% rename from .changelogs/2021-10-25-issue23-integration.md rename to rust/.changelogs/2021-10-25-issue23-integration.md diff --git a/.github/CODEOWNERS b/rust/.github/CODEOWNERS similarity index 100% rename from .github/CODEOWNERS rename to rust/.github/CODEOWNERS diff --git a/.github/dependabot.yml b/rust/.github/dependabot.yml similarity index 100% rename from .github/dependabot.yml rename to rust/.github/dependabot.yml diff --git a/.github/workflows/audit.yml b/rust/.github/workflows/audit.yml similarity index 100% rename from .github/workflows/audit.yml rename to rust/.github/workflows/audit.yml diff --git a/.github/workflows/checks.yml b/rust/.github/workflows/checks.yml similarity index 100% rename from .github/workflows/checks.yml rename to rust/.github/workflows/checks.yml diff --git a/.github/workflows/publish-docs.yml b/rust/.github/workflows/publish-docs.yml similarity index 100% rename from .github/workflows/publish-docs.yml rename to rust/.github/workflows/publish-docs.yml diff --git a/.github/workflows/tests.yml b/rust/.github/workflows/tests.yml similarity index 100% rename from .github/workflows/tests.yml rename to rust/.github/workflows/tests.yml diff --git a/.gitignore b/rust/.gitignore similarity index 100% rename from .gitignore rename to rust/.gitignore diff --git a/CHANGELOG.md b/rust/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to rust/CHANGELOG.md diff --git a/CODE_OF_CONDUCT.md b/rust/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to rust/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/rust/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to rust/CONTRIBUTING.md diff --git a/Cargo.lock b/rust/Cargo.lock similarity index 100% rename from Cargo.lock rename to rust/Cargo.lock diff --git a/Cargo.toml b/rust/Cargo.toml similarity index 100% rename from Cargo.toml rename to rust/Cargo.toml diff --git a/LICENSE b/rust/LICENSE similarity index 100% rename from LICENSE rename to rust/LICENSE diff --git a/POLICY.md b/rust/POLICY.md similarity index 100% rename from POLICY.md rename to rust/POLICY.md diff --git a/README.md b/rust/README.md similarity index 100% rename from README.md rename to rust/README.md diff --git a/RELEASING.md b/rust/RELEASING.md similarity index 100% rename from RELEASING.md rename to rust/RELEASING.md diff --git a/SECURITY.md b/rust/SECURITY.md similarity index 100% rename from SECURITY.md rename to rust/SECURITY.md diff --git a/build-docs.sh b/rust/build-docs.sh similarity index 100% rename from build-docs.sh rename to rust/build-docs.sh diff --git a/cli/Cargo.toml b/rust/cli/Cargo.toml similarity index 100% rename from cli/Cargo.toml rename to rust/cli/Cargo.toml diff --git a/cli/build.rs b/rust/cli/build.rs similarity index 100% rename from cli/build.rs rename to rust/cli/build.rs diff --git a/cli/src/argparse/args/arg_matching.rs b/rust/cli/src/argparse/args/arg_matching.rs similarity index 100% rename from cli/src/argparse/args/arg_matching.rs rename to rust/cli/src/argparse/args/arg_matching.rs diff --git a/cli/src/argparse/args/colon.rs b/rust/cli/src/argparse/args/colon.rs similarity index 100% rename from cli/src/argparse/args/colon.rs rename to rust/cli/src/argparse/args/colon.rs diff --git a/cli/src/argparse/args/idlist.rs b/rust/cli/src/argparse/args/idlist.rs similarity index 100% rename from cli/src/argparse/args/idlist.rs rename to rust/cli/src/argparse/args/idlist.rs diff --git a/cli/src/argparse/args/misc.rs b/rust/cli/src/argparse/args/misc.rs similarity index 100% rename from cli/src/argparse/args/misc.rs rename to rust/cli/src/argparse/args/misc.rs diff --git a/cli/src/argparse/args/mod.rs b/rust/cli/src/argparse/args/mod.rs similarity index 100% rename from cli/src/argparse/args/mod.rs rename to rust/cli/src/argparse/args/mod.rs diff --git a/cli/src/argparse/args/tags.rs b/rust/cli/src/argparse/args/tags.rs similarity index 100% rename from cli/src/argparse/args/tags.rs rename to rust/cli/src/argparse/args/tags.rs diff --git a/cli/src/argparse/args/time.rs b/rust/cli/src/argparse/args/time.rs similarity index 100% rename from cli/src/argparse/args/time.rs rename to rust/cli/src/argparse/args/time.rs diff --git a/cli/src/argparse/command.rs b/rust/cli/src/argparse/command.rs similarity index 100% rename from cli/src/argparse/command.rs rename to rust/cli/src/argparse/command.rs diff --git a/cli/src/argparse/config.rs b/rust/cli/src/argparse/config.rs similarity index 100% rename from cli/src/argparse/config.rs rename to rust/cli/src/argparse/config.rs diff --git a/cli/src/argparse/filter.rs b/rust/cli/src/argparse/filter.rs similarity index 100% rename from cli/src/argparse/filter.rs rename to rust/cli/src/argparse/filter.rs diff --git a/cli/src/argparse/mod.rs b/rust/cli/src/argparse/mod.rs similarity index 100% rename from cli/src/argparse/mod.rs rename to rust/cli/src/argparse/mod.rs diff --git a/cli/src/argparse/modification.rs b/rust/cli/src/argparse/modification.rs similarity index 100% rename from cli/src/argparse/modification.rs rename to rust/cli/src/argparse/modification.rs diff --git a/cli/src/argparse/subcommand.rs b/rust/cli/src/argparse/subcommand.rs similarity index 100% rename from cli/src/argparse/subcommand.rs rename to rust/cli/src/argparse/subcommand.rs diff --git a/cli/src/bin/ta.rs b/rust/cli/src/bin/ta.rs similarity index 100% rename from cli/src/bin/ta.rs rename to rust/cli/src/bin/ta.rs diff --git a/cli/src/bin/usage-docs.rs b/rust/cli/src/bin/usage-docs.rs similarity index 100% rename from cli/src/bin/usage-docs.rs rename to rust/cli/src/bin/usage-docs.rs diff --git a/cli/src/errors.rs b/rust/cli/src/errors.rs similarity index 100% rename from cli/src/errors.rs rename to rust/cli/src/errors.rs diff --git a/cli/src/invocation/cmd/add.rs b/rust/cli/src/invocation/cmd/add.rs similarity index 100% rename from cli/src/invocation/cmd/add.rs rename to rust/cli/src/invocation/cmd/add.rs diff --git a/cli/src/invocation/cmd/completed.data b/rust/cli/src/invocation/cmd/completed.data similarity index 100% rename from cli/src/invocation/cmd/completed.data rename to rust/cli/src/invocation/cmd/completed.data diff --git a/cli/src/invocation/cmd/config.rs b/rust/cli/src/invocation/cmd/config.rs similarity index 100% rename from cli/src/invocation/cmd/config.rs rename to rust/cli/src/invocation/cmd/config.rs diff --git a/cli/src/invocation/cmd/gc.rs b/rust/cli/src/invocation/cmd/gc.rs similarity index 100% rename from cli/src/invocation/cmd/gc.rs rename to rust/cli/src/invocation/cmd/gc.rs diff --git a/cli/src/invocation/cmd/help.rs b/rust/cli/src/invocation/cmd/help.rs similarity index 100% rename from cli/src/invocation/cmd/help.rs rename to rust/cli/src/invocation/cmd/help.rs diff --git a/cli/src/invocation/cmd/import_tdb2.rs b/rust/cli/src/invocation/cmd/import_tdb2.rs similarity index 100% rename from cli/src/invocation/cmd/import_tdb2.rs rename to rust/cli/src/invocation/cmd/import_tdb2.rs diff --git a/cli/src/invocation/cmd/import_tw.rs b/rust/cli/src/invocation/cmd/import_tw.rs similarity index 100% rename from cli/src/invocation/cmd/import_tw.rs rename to rust/cli/src/invocation/cmd/import_tw.rs diff --git a/cli/src/invocation/cmd/info.rs b/rust/cli/src/invocation/cmd/info.rs similarity index 100% rename from cli/src/invocation/cmd/info.rs rename to rust/cli/src/invocation/cmd/info.rs diff --git a/cli/src/invocation/cmd/mod.rs b/rust/cli/src/invocation/cmd/mod.rs similarity index 100% rename from cli/src/invocation/cmd/mod.rs rename to rust/cli/src/invocation/cmd/mod.rs diff --git a/cli/src/invocation/cmd/modify.rs b/rust/cli/src/invocation/cmd/modify.rs similarity index 100% rename from cli/src/invocation/cmd/modify.rs rename to rust/cli/src/invocation/cmd/modify.rs diff --git a/cli/src/invocation/cmd/pending.data b/rust/cli/src/invocation/cmd/pending.data similarity index 100% rename from cli/src/invocation/cmd/pending.data rename to rust/cli/src/invocation/cmd/pending.data diff --git a/cli/src/invocation/cmd/report.rs b/rust/cli/src/invocation/cmd/report.rs similarity index 100% rename from cli/src/invocation/cmd/report.rs rename to rust/cli/src/invocation/cmd/report.rs diff --git a/cli/src/invocation/cmd/sync.rs b/rust/cli/src/invocation/cmd/sync.rs similarity index 100% rename from cli/src/invocation/cmd/sync.rs rename to rust/cli/src/invocation/cmd/sync.rs diff --git a/cli/src/invocation/cmd/undo.rs b/rust/cli/src/invocation/cmd/undo.rs similarity index 100% rename from cli/src/invocation/cmd/undo.rs rename to rust/cli/src/invocation/cmd/undo.rs diff --git a/cli/src/invocation/cmd/version.rs b/rust/cli/src/invocation/cmd/version.rs similarity index 100% rename from cli/src/invocation/cmd/version.rs rename to rust/cli/src/invocation/cmd/version.rs diff --git a/cli/src/invocation/filter.rs b/rust/cli/src/invocation/filter.rs similarity index 100% rename from cli/src/invocation/filter.rs rename to rust/cli/src/invocation/filter.rs diff --git a/cli/src/invocation/mod.rs b/rust/cli/src/invocation/mod.rs similarity index 100% rename from cli/src/invocation/mod.rs rename to rust/cli/src/invocation/mod.rs diff --git a/cli/src/invocation/modify.rs b/rust/cli/src/invocation/modify.rs similarity index 100% rename from cli/src/invocation/modify.rs rename to rust/cli/src/invocation/modify.rs diff --git a/cli/src/invocation/report.rs b/rust/cli/src/invocation/report.rs similarity index 100% rename from cli/src/invocation/report.rs rename to rust/cli/src/invocation/report.rs diff --git a/cli/src/invocation/test.rs b/rust/cli/src/invocation/test.rs similarity index 100% rename from cli/src/invocation/test.rs rename to rust/cli/src/invocation/test.rs diff --git a/cli/src/invocation/util.rs b/rust/cli/src/invocation/util.rs similarity index 100% rename from cli/src/invocation/util.rs rename to rust/cli/src/invocation/util.rs diff --git a/cli/src/lib.rs b/rust/cli/src/lib.rs similarity index 100% rename from cli/src/lib.rs rename to rust/cli/src/lib.rs diff --git a/cli/src/macros.rs b/rust/cli/src/macros.rs similarity index 100% rename from cli/src/macros.rs rename to rust/cli/src/macros.rs diff --git a/cli/src/settings/mod.rs b/rust/cli/src/settings/mod.rs similarity index 100% rename from cli/src/settings/mod.rs rename to rust/cli/src/settings/mod.rs diff --git a/cli/src/settings/report.rs b/rust/cli/src/settings/report.rs similarity index 100% rename from cli/src/settings/report.rs rename to rust/cli/src/settings/report.rs diff --git a/cli/src/settings/settings.rs b/rust/cli/src/settings/settings.rs similarity index 100% rename from cli/src/settings/settings.rs rename to rust/cli/src/settings/settings.rs diff --git a/cli/src/settings/util.rs b/rust/cli/src/settings/util.rs similarity index 100% rename from cli/src/settings/util.rs rename to rust/cli/src/settings/util.rs diff --git a/cli/src/table.rs b/rust/cli/src/table.rs similarity index 100% rename from cli/src/table.rs rename to rust/cli/src/table.rs diff --git a/cli/src/tdb2/mod.rs b/rust/cli/src/tdb2/mod.rs similarity index 100% rename from cli/src/tdb2/mod.rs rename to rust/cli/src/tdb2/mod.rs diff --git a/cli/src/tdb2/test.data b/rust/cli/src/tdb2/test.data similarity index 100% rename from cli/src/tdb2/test.data rename to rust/cli/src/tdb2/test.data diff --git a/cli/src/usage.rs b/rust/cli/src/usage.rs similarity index 100% rename from cli/src/usage.rs rename to rust/cli/src/usage.rs diff --git a/cli/tests/cli.rs b/rust/cli/tests/cli.rs similarity index 100% rename from cli/tests/cli.rs rename to rust/cli/tests/cli.rs diff --git a/docs/.gitignore b/rust/docs/.gitignore similarity index 100% rename from docs/.gitignore rename to rust/docs/.gitignore diff --git a/docs/README.md b/rust/docs/README.md similarity index 100% rename from docs/README.md rename to rust/docs/README.md diff --git a/docs/assets/cgi/LICENSE.md b/rust/docs/assets/cgi/LICENSE.md similarity index 100% rename from docs/assets/cgi/LICENSE.md rename to rust/docs/assets/cgi/LICENSE.md diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_1024.png b/rust/docs/assets/cgi/icon_rounded/icon_rounded_1024.png similarity index 100% rename from docs/assets/cgi/icon_rounded/icon_rounded_1024.png rename to rust/docs/assets/cgi/icon_rounded/icon_rounded_1024.png diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_128.png b/rust/docs/assets/cgi/icon_rounded/icon_rounded_128.png similarity index 100% rename from docs/assets/cgi/icon_rounded/icon_rounded_128.png rename to rust/docs/assets/cgi/icon_rounded/icon_rounded_128.png diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_16.png b/rust/docs/assets/cgi/icon_rounded/icon_rounded_16.png similarity index 100% rename from docs/assets/cgi/icon_rounded/icon_rounded_16.png rename to rust/docs/assets/cgi/icon_rounded/icon_rounded_16.png diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_256.png b/rust/docs/assets/cgi/icon_rounded/icon_rounded_256.png similarity index 100% rename from docs/assets/cgi/icon_rounded/icon_rounded_256.png rename to rust/docs/assets/cgi/icon_rounded/icon_rounded_256.png diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_32.png b/rust/docs/assets/cgi/icon_rounded/icon_rounded_32.png similarity index 100% rename from docs/assets/cgi/icon_rounded/icon_rounded_32.png rename to rust/docs/assets/cgi/icon_rounded/icon_rounded_32.png diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_512.png b/rust/docs/assets/cgi/icon_rounded/icon_rounded_512.png similarity index 100% rename from docs/assets/cgi/icon_rounded/icon_rounded_512.png rename to rust/docs/assets/cgi/icon_rounded/icon_rounded_512.png diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_64.png b/rust/docs/assets/cgi/icon_rounded/icon_rounded_64.png similarity index 100% rename from docs/assets/cgi/icon_rounded/icon_rounded_64.png rename to rust/docs/assets/cgi/icon_rounded/icon_rounded_64.png diff --git a/docs/assets/cgi/icon_square/icon_square_1024.png b/rust/docs/assets/cgi/icon_square/icon_square_1024.png similarity index 100% rename from docs/assets/cgi/icon_square/icon_square_1024.png rename to rust/docs/assets/cgi/icon_square/icon_square_1024.png diff --git a/docs/assets/cgi/icon_square/icon_square_128.png b/rust/docs/assets/cgi/icon_square/icon_square_128.png similarity index 100% rename from docs/assets/cgi/icon_square/icon_square_128.png rename to rust/docs/assets/cgi/icon_square/icon_square_128.png diff --git a/docs/assets/cgi/icon_square/icon_square_16.png b/rust/docs/assets/cgi/icon_square/icon_square_16.png similarity index 100% rename from docs/assets/cgi/icon_square/icon_square_16.png rename to rust/docs/assets/cgi/icon_square/icon_square_16.png diff --git a/docs/assets/cgi/icon_square/icon_square_256.png b/rust/docs/assets/cgi/icon_square/icon_square_256.png similarity index 100% rename from docs/assets/cgi/icon_square/icon_square_256.png rename to rust/docs/assets/cgi/icon_square/icon_square_256.png diff --git a/docs/assets/cgi/icon_square/icon_square_32.png b/rust/docs/assets/cgi/icon_square/icon_square_32.png similarity index 100% rename from docs/assets/cgi/icon_square/icon_square_32.png rename to rust/docs/assets/cgi/icon_square/icon_square_32.png diff --git a/docs/assets/cgi/icon_square/icon_square_512.png b/rust/docs/assets/cgi/icon_square/icon_square_512.png similarity index 100% rename from docs/assets/cgi/icon_square/icon_square_512.png rename to rust/docs/assets/cgi/icon_square/icon_square_512.png diff --git a/docs/assets/cgi/icon_square/icon_square_64.png b/rust/docs/assets/cgi/icon_square/icon_square_64.png similarity index 100% rename from docs/assets/cgi/icon_square/icon_square_64.png rename to rust/docs/assets/cgi/icon_square/icon_square_64.png diff --git a/docs/assets/cgi/logo/logo_1024.png b/rust/docs/assets/cgi/logo/logo_1024.png similarity index 100% rename from docs/assets/cgi/logo/logo_1024.png rename to rust/docs/assets/cgi/logo/logo_1024.png diff --git a/docs/assets/cgi/logo/logo_128.png b/rust/docs/assets/cgi/logo/logo_128.png similarity index 100% rename from docs/assets/cgi/logo/logo_128.png rename to rust/docs/assets/cgi/logo/logo_128.png diff --git a/docs/assets/cgi/logo/logo_16.png b/rust/docs/assets/cgi/logo/logo_16.png similarity index 100% rename from docs/assets/cgi/logo/logo_16.png rename to rust/docs/assets/cgi/logo/logo_16.png diff --git a/docs/assets/cgi/logo/logo_256.png b/rust/docs/assets/cgi/logo/logo_256.png similarity index 100% rename from docs/assets/cgi/logo/logo_256.png rename to rust/docs/assets/cgi/logo/logo_256.png diff --git a/docs/assets/cgi/logo/logo_32.png b/rust/docs/assets/cgi/logo/logo_32.png similarity index 100% rename from docs/assets/cgi/logo/logo_32.png rename to rust/docs/assets/cgi/logo/logo_32.png diff --git a/docs/assets/cgi/logo/logo_512.png b/rust/docs/assets/cgi/logo/logo_512.png similarity index 100% rename from docs/assets/cgi/logo/logo_512.png rename to rust/docs/assets/cgi/logo/logo_512.png diff --git a/docs/assets/cgi/logo/logo_64.png b/rust/docs/assets/cgi/logo/logo_64.png similarity index 100% rename from docs/assets/cgi/logo/logo_64.png rename to rust/docs/assets/cgi/logo/logo_64.png diff --git a/docs/book.toml b/rust/docs/book.toml similarity index 100% rename from docs/book.toml rename to rust/docs/book.toml diff --git a/docs/src/SUMMARY.md b/rust/docs/src/SUMMARY.md similarity index 100% rename from docs/src/SUMMARY.md rename to rust/docs/src/SUMMARY.md diff --git a/docs/src/config-file.md b/rust/docs/src/config-file.md similarity index 100% rename from docs/src/config-file.md rename to rust/docs/src/config-file.md diff --git a/docs/src/data-model.md b/rust/docs/src/data-model.md similarity index 100% rename from docs/src/data-model.md rename to rust/docs/src/data-model.md diff --git a/docs/src/environment.md b/rust/docs/src/environment.md similarity index 100% rename from docs/src/environment.md rename to rust/docs/src/environment.md diff --git a/docs/src/filters.md b/rust/docs/src/filters.md similarity index 100% rename from docs/src/filters.md rename to rust/docs/src/filters.md diff --git a/docs/src/images/name_timestamp.png b/rust/docs/src/images/name_timestamp.png similarity index 100% rename from docs/src/images/name_timestamp.png rename to rust/docs/src/images/name_timestamp.png diff --git a/docs/src/installation.md b/rust/docs/src/installation.md similarity index 100% rename from docs/src/installation.md rename to rust/docs/src/installation.md diff --git a/docs/src/internals.md b/rust/docs/src/internals.md similarity index 100% rename from docs/src/internals.md rename to rust/docs/src/internals.md diff --git a/docs/src/modifications.md b/rust/docs/src/modifications.md similarity index 100% rename from docs/src/modifications.md rename to rust/docs/src/modifications.md diff --git a/docs/src/plans.md b/rust/docs/src/plans.md similarity index 100% rename from docs/src/plans.md rename to rust/docs/src/plans.md diff --git a/docs/src/reports.md b/rust/docs/src/reports.md similarity index 100% rename from docs/src/reports.md rename to rust/docs/src/reports.md diff --git a/docs/src/running-sync-server.md b/rust/docs/src/running-sync-server.md similarity index 100% rename from docs/src/running-sync-server.md rename to rust/docs/src/running-sync-server.md diff --git a/docs/src/snapshots.md b/rust/docs/src/snapshots.md similarity index 100% rename from docs/src/snapshots.md rename to rust/docs/src/snapshots.md diff --git a/docs/src/storage.md b/rust/docs/src/storage.md similarity index 100% rename from docs/src/storage.md rename to rust/docs/src/storage.md diff --git a/docs/src/sync-model.md b/rust/docs/src/sync-model.md similarity index 100% rename from docs/src/sync-model.md rename to rust/docs/src/sync-model.md diff --git a/docs/src/sync-protocol.md b/rust/docs/src/sync-protocol.md similarity index 100% rename from docs/src/sync-protocol.md rename to rust/docs/src/sync-protocol.md diff --git a/docs/src/sync.md b/rust/docs/src/sync.md similarity index 100% rename from docs/src/sync.md rename to rust/docs/src/sync.md diff --git a/docs/src/tags.md b/rust/docs/src/tags.md similarity index 100% rename from docs/src/tags.md rename to rust/docs/src/tags.md diff --git a/docs/src/task-sync.md b/rust/docs/src/task-sync.md similarity index 100% rename from docs/src/task-sync.md rename to rust/docs/src/task-sync.md diff --git a/docs/src/taskdb.md b/rust/docs/src/taskdb.md similarity index 100% rename from docs/src/taskdb.md rename to rust/docs/src/taskdb.md diff --git a/docs/src/tasks.md b/rust/docs/src/tasks.md similarity index 100% rename from docs/src/tasks.md rename to rust/docs/src/tasks.md diff --git a/docs/src/time.md b/rust/docs/src/time.md similarity index 100% rename from docs/src/time.md rename to rust/docs/src/time.md diff --git a/docs/src/undo.md b/rust/docs/src/undo.md similarity index 100% rename from docs/src/undo.md rename to rust/docs/src/undo.md diff --git a/docs/src/using-task-command.md b/rust/docs/src/using-task-command.md similarity index 100% rename from docs/src/using-task-command.md rename to rust/docs/src/using-task-command.md diff --git a/docs/src/welcome.md b/rust/docs/src/welcome.md similarity index 100% rename from docs/src/welcome.md rename to rust/docs/src/welcome.md diff --git a/integration-tests/.gitignore b/rust/integration-tests/.gitignore similarity index 100% rename from integration-tests/.gitignore rename to rust/integration-tests/.gitignore diff --git a/integration-tests/Cargo.toml b/rust/integration-tests/Cargo.toml similarity index 100% rename from integration-tests/Cargo.toml rename to rust/integration-tests/Cargo.toml diff --git a/integration-tests/README.md b/rust/integration-tests/README.md similarity index 100% rename from integration-tests/README.md rename to rust/integration-tests/README.md diff --git a/integration-tests/build.rs b/rust/integration-tests/build.rs similarity index 100% rename from integration-tests/build.rs rename to rust/integration-tests/build.rs diff --git a/integration-tests/src/bindings_tests/mod.rs b/rust/integration-tests/src/bindings_tests/mod.rs similarity index 100% rename from integration-tests/src/bindings_tests/mod.rs rename to rust/integration-tests/src/bindings_tests/mod.rs diff --git a/integration-tests/src/bindings_tests/replica.c b/rust/integration-tests/src/bindings_tests/replica.c similarity index 100% rename from integration-tests/src/bindings_tests/replica.c rename to rust/integration-tests/src/bindings_tests/replica.c diff --git a/integration-tests/src/bindings_tests/string.c b/rust/integration-tests/src/bindings_tests/string.c similarity index 100% rename from integration-tests/src/bindings_tests/string.c rename to rust/integration-tests/src/bindings_tests/string.c diff --git a/integration-tests/src/bindings_tests/task.c b/rust/integration-tests/src/bindings_tests/task.c similarity index 100% rename from integration-tests/src/bindings_tests/task.c rename to rust/integration-tests/src/bindings_tests/task.c diff --git a/integration-tests/src/bindings_tests/test.c b/rust/integration-tests/src/bindings_tests/test.c similarity index 100% rename from integration-tests/src/bindings_tests/test.c rename to rust/integration-tests/src/bindings_tests/test.c diff --git a/integration-tests/src/bindings_tests/unity/LICENSE.txt b/rust/integration-tests/src/bindings_tests/unity/LICENSE.txt similarity index 100% rename from integration-tests/src/bindings_tests/unity/LICENSE.txt rename to rust/integration-tests/src/bindings_tests/unity/LICENSE.txt diff --git a/integration-tests/src/bindings_tests/unity/README.md b/rust/integration-tests/src/bindings_tests/unity/README.md similarity index 100% rename from integration-tests/src/bindings_tests/unity/README.md rename to rust/integration-tests/src/bindings_tests/unity/README.md diff --git a/integration-tests/src/bindings_tests/unity/unity.c b/rust/integration-tests/src/bindings_tests/unity/unity.c similarity index 100% rename from integration-tests/src/bindings_tests/unity/unity.c rename to rust/integration-tests/src/bindings_tests/unity/unity.c diff --git a/integration-tests/src/bindings_tests/unity/unity.h b/rust/integration-tests/src/bindings_tests/unity/unity.h similarity index 100% rename from integration-tests/src/bindings_tests/unity/unity.h rename to rust/integration-tests/src/bindings_tests/unity/unity.h diff --git a/integration-tests/src/bindings_tests/unity/unity_internals.h b/rust/integration-tests/src/bindings_tests/unity/unity_internals.h similarity index 100% rename from integration-tests/src/bindings_tests/unity/unity_internals.h rename to rust/integration-tests/src/bindings_tests/unity/unity_internals.h diff --git a/integration-tests/src/bindings_tests/uuid.c b/rust/integration-tests/src/bindings_tests/uuid.c similarity index 100% rename from integration-tests/src/bindings_tests/uuid.c rename to rust/integration-tests/src/bindings_tests/uuid.c diff --git a/integration-tests/src/lib.rs b/rust/integration-tests/src/lib.rs similarity index 100% rename from integration-tests/src/lib.rs rename to rust/integration-tests/src/lib.rs diff --git a/integration-tests/tests/bindings.rs b/rust/integration-tests/tests/bindings.rs similarity index 100% rename from integration-tests/tests/bindings.rs rename to rust/integration-tests/tests/bindings.rs diff --git a/integration-tests/tests/cross-sync.rs b/rust/integration-tests/tests/cross-sync.rs similarity index 100% rename from integration-tests/tests/cross-sync.rs rename to rust/integration-tests/tests/cross-sync.rs diff --git a/integration-tests/tests/snapshots.rs b/rust/integration-tests/tests/snapshots.rs similarity index 100% rename from integration-tests/tests/snapshots.rs rename to rust/integration-tests/tests/snapshots.rs diff --git a/integration-tests/tests/update-and-delete-sync.rs b/rust/integration-tests/tests/update-and-delete-sync.rs similarity index 100% rename from integration-tests/tests/update-and-delete-sync.rs rename to rust/integration-tests/tests/update-and-delete-sync.rs diff --git a/lib/Cargo.toml b/rust/lib/Cargo.toml similarity index 100% rename from lib/Cargo.toml rename to rust/lib/Cargo.toml diff --git a/lib/Makefile b/rust/lib/Makefile similarity index 100% rename from lib/Makefile rename to rust/lib/Makefile diff --git a/lib/header-intro.h b/rust/lib/header-intro.h similarity index 100% rename from lib/header-intro.h rename to rust/lib/header-intro.h diff --git a/lib/src/annotation.rs b/rust/lib/src/annotation.rs similarity index 100% rename from lib/src/annotation.rs rename to rust/lib/src/annotation.rs diff --git a/lib/src/atomic.rs b/rust/lib/src/atomic.rs similarity index 100% rename from lib/src/atomic.rs rename to rust/lib/src/atomic.rs diff --git a/lib/src/kv.rs b/rust/lib/src/kv.rs similarity index 100% rename from lib/src/kv.rs rename to rust/lib/src/kv.rs diff --git a/lib/src/lib.rs b/rust/lib/src/lib.rs similarity index 100% rename from lib/src/lib.rs rename to rust/lib/src/lib.rs diff --git a/lib/src/replica.rs b/rust/lib/src/replica.rs similarity index 100% rename from lib/src/replica.rs rename to rust/lib/src/replica.rs diff --git a/lib/src/result.rs b/rust/lib/src/result.rs similarity index 100% rename from lib/src/result.rs rename to rust/lib/src/result.rs diff --git a/lib/src/server.rs b/rust/lib/src/server.rs similarity index 100% rename from lib/src/server.rs rename to rust/lib/src/server.rs diff --git a/lib/src/status.rs b/rust/lib/src/status.rs similarity index 100% rename from lib/src/status.rs rename to rust/lib/src/status.rs diff --git a/lib/src/string.rs b/rust/lib/src/string.rs similarity index 100% rename from lib/src/string.rs rename to rust/lib/src/string.rs diff --git a/lib/src/task.rs b/rust/lib/src/task.rs similarity index 100% rename from lib/src/task.rs rename to rust/lib/src/task.rs diff --git a/lib/src/traits.rs b/rust/lib/src/traits.rs similarity index 100% rename from lib/src/traits.rs rename to rust/lib/src/traits.rs diff --git a/lib/src/uda.rs b/rust/lib/src/uda.rs similarity index 100% rename from lib/src/uda.rs rename to rust/lib/src/uda.rs diff --git a/lib/src/util.rs b/rust/lib/src/util.rs similarity index 100% rename from lib/src/util.rs rename to rust/lib/src/util.rs diff --git a/lib/src/uuid.rs b/rust/lib/src/uuid.rs similarity index 100% rename from lib/src/uuid.rs rename to rust/lib/src/uuid.rs diff --git a/lib/src/workingset.rs b/rust/lib/src/workingset.rs similarity index 100% rename from lib/src/workingset.rs rename to rust/lib/src/workingset.rs diff --git a/lib/taskchampion.h b/rust/lib/taskchampion.h similarity index 100% rename from lib/taskchampion.h rename to rust/lib/taskchampion.h diff --git a/scripts/changelog.py b/rust/scripts/changelog.py similarity index 100% rename from scripts/changelog.py rename to rust/scripts/changelog.py diff --git a/sync-server/Cargo.toml b/rust/sync-server/Cargo.toml similarity index 100% rename from sync-server/Cargo.toml rename to rust/sync-server/Cargo.toml diff --git a/sync-server/src/api/add_snapshot.rs b/rust/sync-server/src/api/add_snapshot.rs similarity index 100% rename from sync-server/src/api/add_snapshot.rs rename to rust/sync-server/src/api/add_snapshot.rs diff --git a/sync-server/src/api/add_version.rs b/rust/sync-server/src/api/add_version.rs similarity index 100% rename from sync-server/src/api/add_version.rs rename to rust/sync-server/src/api/add_version.rs diff --git a/sync-server/src/api/get_child_version.rs b/rust/sync-server/src/api/get_child_version.rs similarity index 100% rename from sync-server/src/api/get_child_version.rs rename to rust/sync-server/src/api/get_child_version.rs diff --git a/sync-server/src/api/get_snapshot.rs b/rust/sync-server/src/api/get_snapshot.rs similarity index 100% rename from sync-server/src/api/get_snapshot.rs rename to rust/sync-server/src/api/get_snapshot.rs diff --git a/sync-server/src/api/mod.rs b/rust/sync-server/src/api/mod.rs similarity index 100% rename from sync-server/src/api/mod.rs rename to rust/sync-server/src/api/mod.rs diff --git a/sync-server/src/bin/taskchampion-sync-server.rs b/rust/sync-server/src/bin/taskchampion-sync-server.rs similarity index 100% rename from sync-server/src/bin/taskchampion-sync-server.rs rename to rust/sync-server/src/bin/taskchampion-sync-server.rs diff --git a/sync-server/src/lib.rs b/rust/sync-server/src/lib.rs similarity index 100% rename from sync-server/src/lib.rs rename to rust/sync-server/src/lib.rs diff --git a/sync-server/src/server.rs b/rust/sync-server/src/server.rs similarity index 100% rename from sync-server/src/server.rs rename to rust/sync-server/src/server.rs diff --git a/sync-server/src/storage/inmemory.rs b/rust/sync-server/src/storage/inmemory.rs similarity index 100% rename from sync-server/src/storage/inmemory.rs rename to rust/sync-server/src/storage/inmemory.rs diff --git a/sync-server/src/storage/mod.rs b/rust/sync-server/src/storage/mod.rs similarity index 100% rename from sync-server/src/storage/mod.rs rename to rust/sync-server/src/storage/mod.rs diff --git a/sync-server/src/storage/sqlite.rs b/rust/sync-server/src/storage/sqlite.rs similarity index 100% rename from sync-server/src/storage/sqlite.rs rename to rust/sync-server/src/storage/sqlite.rs diff --git a/taskchampion/Cargo.toml b/rust/taskchampion/Cargo.toml similarity index 100% rename from taskchampion/Cargo.toml rename to rust/taskchampion/Cargo.toml diff --git a/taskchampion/src/depmap.rs b/rust/taskchampion/src/depmap.rs similarity index 100% rename from taskchampion/src/depmap.rs rename to rust/taskchampion/src/depmap.rs diff --git a/taskchampion/src/errors.rs b/rust/taskchampion/src/errors.rs similarity index 100% rename from taskchampion/src/errors.rs rename to rust/taskchampion/src/errors.rs diff --git a/taskchampion/src/lib.rs b/rust/taskchampion/src/lib.rs similarity index 100% rename from taskchampion/src/lib.rs rename to rust/taskchampion/src/lib.rs diff --git a/taskchampion/src/macros.rs b/rust/taskchampion/src/macros.rs similarity index 100% rename from taskchampion/src/macros.rs rename to rust/taskchampion/src/macros.rs diff --git a/taskchampion/src/replica.rs b/rust/taskchampion/src/replica.rs similarity index 100% rename from taskchampion/src/replica.rs rename to rust/taskchampion/src/replica.rs diff --git a/taskchampion/src/server/config.rs b/rust/taskchampion/src/server/config.rs similarity index 100% rename from taskchampion/src/server/config.rs rename to rust/taskchampion/src/server/config.rs diff --git a/taskchampion/src/server/crypto.rs b/rust/taskchampion/src/server/crypto.rs similarity index 100% rename from taskchampion/src/server/crypto.rs rename to rust/taskchampion/src/server/crypto.rs diff --git a/taskchampion/src/server/generate-test-data.py b/rust/taskchampion/src/server/generate-test-data.py similarity index 100% rename from taskchampion/src/server/generate-test-data.py rename to rust/taskchampion/src/server/generate-test-data.py diff --git a/taskchampion/src/server/local.rs b/rust/taskchampion/src/server/local.rs similarity index 100% rename from taskchampion/src/server/local.rs rename to rust/taskchampion/src/server/local.rs diff --git a/taskchampion/src/server/mod.rs b/rust/taskchampion/src/server/mod.rs similarity index 100% rename from taskchampion/src/server/mod.rs rename to rust/taskchampion/src/server/mod.rs diff --git a/taskchampion/src/server/op.rs b/rust/taskchampion/src/server/op.rs similarity index 100% rename from taskchampion/src/server/op.rs rename to rust/taskchampion/src/server/op.rs diff --git a/taskchampion/src/server/remote/mod.rs b/rust/taskchampion/src/server/remote/mod.rs similarity index 100% rename from taskchampion/src/server/remote/mod.rs rename to rust/taskchampion/src/server/remote/mod.rs diff --git a/taskchampion/src/server/test-bad-app-id.data b/rust/taskchampion/src/server/test-bad-app-id.data similarity index 100% rename from taskchampion/src/server/test-bad-app-id.data rename to rust/taskchampion/src/server/test-bad-app-id.data diff --git a/taskchampion/src/server/test-bad-client-key.data b/rust/taskchampion/src/server/test-bad-client-key.data similarity index 100% rename from taskchampion/src/server/test-bad-client-key.data rename to rust/taskchampion/src/server/test-bad-client-key.data diff --git a/taskchampion/src/server/test-bad-secret.data b/rust/taskchampion/src/server/test-bad-secret.data similarity index 100% rename from taskchampion/src/server/test-bad-secret.data rename to rust/taskchampion/src/server/test-bad-secret.data diff --git a/taskchampion/src/server/test-bad-version-id.data b/rust/taskchampion/src/server/test-bad-version-id.data similarity index 100% rename from taskchampion/src/server/test-bad-version-id.data rename to rust/taskchampion/src/server/test-bad-version-id.data diff --git a/taskchampion/src/server/test-bad-version.data b/rust/taskchampion/src/server/test-bad-version.data similarity index 100% rename from taskchampion/src/server/test-bad-version.data rename to rust/taskchampion/src/server/test-bad-version.data diff --git a/taskchampion/src/server/test-bad-version_id.data b/rust/taskchampion/src/server/test-bad-version_id.data similarity index 100% rename from taskchampion/src/server/test-bad-version_id.data rename to rust/taskchampion/src/server/test-bad-version_id.data diff --git a/taskchampion/src/server/test-good.data b/rust/taskchampion/src/server/test-good.data similarity index 100% rename from taskchampion/src/server/test-good.data rename to rust/taskchampion/src/server/test-good.data diff --git a/taskchampion/src/server/test.rs b/rust/taskchampion/src/server/test.rs similarity index 100% rename from taskchampion/src/server/test.rs rename to rust/taskchampion/src/server/test.rs diff --git a/taskchampion/src/server/types.rs b/rust/taskchampion/src/server/types.rs similarity index 100% rename from taskchampion/src/server/types.rs rename to rust/taskchampion/src/server/types.rs diff --git a/taskchampion/src/storage/config.rs b/rust/taskchampion/src/storage/config.rs similarity index 100% rename from taskchampion/src/storage/config.rs rename to rust/taskchampion/src/storage/config.rs diff --git a/taskchampion/src/storage/inmemory.rs b/rust/taskchampion/src/storage/inmemory.rs similarity index 100% rename from taskchampion/src/storage/inmemory.rs rename to rust/taskchampion/src/storage/inmemory.rs diff --git a/taskchampion/src/storage/mod.rs b/rust/taskchampion/src/storage/mod.rs similarity index 100% rename from taskchampion/src/storage/mod.rs rename to rust/taskchampion/src/storage/mod.rs diff --git a/taskchampion/src/storage/op.rs b/rust/taskchampion/src/storage/op.rs similarity index 100% rename from taskchampion/src/storage/op.rs rename to rust/taskchampion/src/storage/op.rs diff --git a/taskchampion/src/storage/sqlite.rs b/rust/taskchampion/src/storage/sqlite.rs similarity index 100% rename from taskchampion/src/storage/sqlite.rs rename to rust/taskchampion/src/storage/sqlite.rs diff --git a/taskchampion/src/task/annotation.rs b/rust/taskchampion/src/task/annotation.rs similarity index 100% rename from taskchampion/src/task/annotation.rs rename to rust/taskchampion/src/task/annotation.rs diff --git a/taskchampion/src/task/mod.rs b/rust/taskchampion/src/task/mod.rs similarity index 100% rename from taskchampion/src/task/mod.rs rename to rust/taskchampion/src/task/mod.rs diff --git a/taskchampion/src/task/status.rs b/rust/taskchampion/src/task/status.rs similarity index 100% rename from taskchampion/src/task/status.rs rename to rust/taskchampion/src/task/status.rs diff --git a/taskchampion/src/task/tag.rs b/rust/taskchampion/src/task/tag.rs similarity index 100% rename from taskchampion/src/task/tag.rs rename to rust/taskchampion/src/task/tag.rs diff --git a/taskchampion/src/task/task.rs b/rust/taskchampion/src/task/task.rs similarity index 100% rename from taskchampion/src/task/task.rs rename to rust/taskchampion/src/task/task.rs diff --git a/taskchampion/src/taskdb/apply.rs b/rust/taskchampion/src/taskdb/apply.rs similarity index 100% rename from taskchampion/src/taskdb/apply.rs rename to rust/taskchampion/src/taskdb/apply.rs diff --git a/taskchampion/src/taskdb/mod.rs b/rust/taskchampion/src/taskdb/mod.rs similarity index 100% rename from taskchampion/src/taskdb/mod.rs rename to rust/taskchampion/src/taskdb/mod.rs diff --git a/taskchampion/src/taskdb/snapshot.rs b/rust/taskchampion/src/taskdb/snapshot.rs similarity index 100% rename from taskchampion/src/taskdb/snapshot.rs rename to rust/taskchampion/src/taskdb/snapshot.rs diff --git a/taskchampion/src/taskdb/sync.rs b/rust/taskchampion/src/taskdb/sync.rs similarity index 100% rename from taskchampion/src/taskdb/sync.rs rename to rust/taskchampion/src/taskdb/sync.rs diff --git a/taskchampion/src/taskdb/undo.rs b/rust/taskchampion/src/taskdb/undo.rs similarity index 100% rename from taskchampion/src/taskdb/undo.rs rename to rust/taskchampion/src/taskdb/undo.rs diff --git a/taskchampion/src/taskdb/working_set.rs b/rust/taskchampion/src/taskdb/working_set.rs similarity index 100% rename from taskchampion/src/taskdb/working_set.rs rename to rust/taskchampion/src/taskdb/working_set.rs diff --git a/taskchampion/src/utils.rs b/rust/taskchampion/src/utils.rs similarity index 100% rename from taskchampion/src/utils.rs rename to rust/taskchampion/src/utils.rs diff --git a/taskchampion/src/workingset.rs b/rust/taskchampion/src/workingset.rs similarity index 100% rename from taskchampion/src/workingset.rs rename to rust/taskchampion/src/workingset.rs diff --git a/xtask/Cargo.toml b/rust/xtask/Cargo.toml similarity index 100% rename from xtask/Cargo.toml rename to rust/xtask/Cargo.toml diff --git a/xtask/src/main.rs b/rust/xtask/src/main.rs similarity index 100% rename from xtask/src/main.rs rename to rust/xtask/src/main.rs

      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 Aa{vGU literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/logo/logo_32.png b/docs/assets/cgi/logo/logo_32.png new file mode 100755 index 0000000000000000000000000000000000000000..b180de372956d4ace3115791c71446f7c065e0ee GIT binary patch literal 2820 zcmV+f3;XnmP)~n;^kfunj1PHeQAP1=ENjm9?}gn zFFYnZv643&-V|=pQ`(a7v2dp;2Nb?`^q9*}4kumyaM)C%5%4w%E6HCR`^9?7%m~ZP z31-p-{pUQ+P3q&>nQ8HDG6Gmc86}LuIKxB;(aWIN5MA`qEi)>__k71sk&{?mtU(cz zWs;=G($W-b4bPkDy@=nlI%uEVwoMS?BN; z)Kz(^3)tR)4%y$h_yq)TXSuOh7p(vQ010qNS#tmYE+YT{E+YYWr9XB600}5bL_t(o zg{_!-j9pa~#(!(?bIyIt^iHSK_j9H|X(1E@fff)XSTSk<30e_M5vT};ppBTIF`~qT z011SkKmjo^L?S3+3`Rml82&)TlC~BsWlA3@ZRwOgn3+3s?%8|!$2sTTJ5O3*-JIl} zefC-3x7Pah+WTGs_|uX_uf&Nzx2ldv1S9gn)Iwq5MVsE}SgtkHGQK7?TV*$Ld z+Oxs{lmmNRf{!B+JP=7VX~_ErKM8Pv815CJ!vHmfs;64>+yGzzKELVBEt8{YZK6tq zj*t5T#_30k{DfhN6|Qs6(PAtH2$cI*DertJ0RRzhEgG{& zRheWW=9Vk=&|vkEqb1(EGS=KN39Q(>Ww1R8zdyfaGR`L$;QhgY z)7LLuy86SwpNZ_(jhOQj=TKFg_gJsDtkj0^h6Ov3v+gA)c9+89d5J0mLxVVc@AP7^ z^VE(1?H_CY?~N1&ReJ)FIRjo1plo>lO3%gPVNAHjr$o`Utzj4&RR&y4%_UO2rSyLDW2Y1S`)h=tWi9UspXclwk=<}DxdR0XK7`r5}x1E5>jam zDHH>&WzEHjjw!rm%S9-o5dj@oMqNcm(F25Zti0NB&)N67>!A5r%f! z`y0ah1}N0pItp`ekT^%^7)qwbHv`dFew;@SS7G~8_}Jq_yREIxzVRmS-usp@&iHX? zJEsghws-Epiw8P7T3XoEcZm5NQ*_6fTWGB$5{clPhmKjj9T%RyyZK#X!29O!;?>Xi z&AaP=`JiaGwSAS(_TvW&S$4)WuaFEWhi^YfX~0rVYP1##49)B({=!bmMzAKJyyP_0 zOxRvhv&Hzm|NXtyfIw1^(OAHqtIU4Wa%jhX2rSj0M$pp^ z^8=!pi_?ipLjgV=@OltUTzW5HjF5N;MrI}`%_Hi%2Z9cai5P38`g=W-0b>l#IlT8+ zYca-Tm>x$$ObtZ|fT;EX$<{Tv>aHeYIRTg9IK>o?2WiDj&?0gm0BbGQ+WLJ8?l|E0 z^Tg)IDHO|GvhXsdn|5r=B3%EjG($l%ft-YZOuRpRsHNb&+_v!+_LTPF66o2ym7kq_ zJDJe1R&dEk0;DzmxZnv1zuoc>dxO15Ej^6%@HUVYQvnBg(hPvO4BS(%&9R!#K5z7G z;1%~0smC;*bD2j-2!Qn|i97?N3q5`-n9v-Kn3yNGuf^e;91B-0T!|ovmwc$6ZIitX z$ww#P)3(lE_ddm2yoDfm1VuS3T*leuShJWDk^ z!mhsmQ1dYhD;LyL*i6D@@jtWkQM~bp3M$|cuAH}uhzN@vPJ2X_>zy#yh@v$cy` zI|J)*2(7k@FE6@-4m-C_A{XA@_Wh3S@fILG2YQrq3g@uA^;|4@?OmR-hQu-wX&UF5&(B4Ehx;SbwxV_$W1U3h=lv6g-g0tQPO{TJIWWu~2#rGA|KWaKie zn+Jnnv79|?6(uu?Agfadc;Cyj+keEa>Uy^KzsBbN7YUIx$qHT30E>hj5g0e z^D-n~hFL4NSZ0*ZWW~at(;Bv6G6Ess+!3C8=coK*_pgX>1emO_!sWBCAs`qxdf&VZ zn>A3kjD%p)G88|>=eq8sEu2P}m7)l!@24-`MTC%^WIfAT&gG2qvhi!*3?>aRHVF7p zBZLIBMT=Rn@Gjb-SrkzUKsmdLKnbYWNql9&^}{>fSk{#O;d?;_ZpUcDAtB|klM9c# zhpDAb0z74t0RttJ3#MMmG&{4=@g}k?^)5dp4+^4!UpzKhZbFH6qp05Mw-OZJ09y1b9jmnLquz%$e>oVwO-HhIkC% zW^*uf2t)`a{lhXP5Cy`3rVH5!e*Xs8dB22H^jC WM6|H*-&f-R0000GF`O^D( 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

    6sIElixLvw)3zy_u3(Tpt}SU!i^5Y28}= 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 Aa{vGU literal 0 HcmV?d00001 diff --git a/docs/assets/cgi/logo/logo_32.png b/docs/assets/cgi/logo/logo_32.png new file mode 100755 index 0000000000000000000000000000000000000000..b180de372956d4ace3115791c71446f7c065e0ee GIT binary patch literal 2820 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 WM6|H*-&f-R0000GF`O^D( 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

6sIElixLvw)3zy_u3(Tpt}SU!i^5Y28}= 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